From e7563be943667b6aad5813090989b9e28424b052 Mon Sep 17 00:00:00 2001 From: mithe24 Date: Wed, 14 May 2025 12:30:20 +0200 Subject: feat/view-manager (#20) * feat(View & Controller): Added screens for game, main menu and pause menu Added screens: - MainMenuView & MainMenuController. - GameView & GameController. - PauseView & PauseController. * refactor(MainMenuController): MainMenuController constructs game MainMenuController constructs games and allows the creation of a new game without terminating the process: * feat(View): Resource Manager for loading game textures Add ResourceManager for centralized texture loading. Implemented a singleton ResourceManager to handle loading of game textures. Ensures each texture is loaded only once, improving performance and memory efficiency. Includes fallback logic to return a default "missing texture" image when a resource cannot be found. Prevents accidental instantiation by enforcing the singleton pattern. --- .../main/java/com/gr15/pacman/view/GameView.java | 113 --------------------- .../java/com/gr15/pacman/view/ResourceManager.java | 77 ++++++++++++++ .../java/com/gr15/pacman/view/ViewManager.java | 92 +++++++++++++++++ .../java/com/gr15/pacman/view/screen/GameView.java | 113 +++++++++++++++++++++ .../com/gr15/pacman/view/screen/MainMenuView.java | 27 +++++ .../com/gr15/pacman/view/screen/PauseView.java | 25 +++++ 6 files changed, 334 insertions(+), 113 deletions(-) delete mode 100644 pacman/view/src/main/java/com/gr15/pacman/view/GameView.java create mode 100644 pacman/view/src/main/java/com/gr15/pacman/view/ResourceManager.java create mode 100644 pacman/view/src/main/java/com/gr15/pacman/view/ViewManager.java create mode 100644 pacman/view/src/main/java/com/gr15/pacman/view/screen/GameView.java create mode 100644 pacman/view/src/main/java/com/gr15/pacman/view/screen/MainMenuView.java create mode 100644 pacman/view/src/main/java/com/gr15/pacman/view/screen/PauseView.java (limited to 'pacman/view/src/main/java/com/gr15') diff --git a/pacman/view/src/main/java/com/gr15/pacman/view/GameView.java b/pacman/view/src/main/java/com/gr15/pacman/view/GameView.java deleted file mode 100644 index d9235f6..0000000 --- a/pacman/view/src/main/java/com/gr15/pacman/view/GameView.java +++ /dev/null @@ -1,113 +0,0 @@ -package com.gr15.pacman.view; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import com.gr15.pacman.model.GameState; -import com.gr15.pacman.model.Board.TileType; - -import javafx.scene.Scene; -import javafx.scene.canvas.Canvas; -import javafx.scene.canvas.GraphicsContext; -import javafx.scene.image.Image; -import javafx.scene.layout.StackPane; -import javafx.scene.paint.Color; - -import com.gr15.pacman.model.entities.Entity; -import com.gr15.pacman.model.entities.Items; - -/** - * GameView - */ -public class GameView - extends Scene { - - /* Utils */ - private Canvas canvas; - private GraphicsContext gc; - private GameState gameState; - private StackPane root; - - private float scaleX = 4.0f; - private float scaleY = 4.0f; - private int tileWidth; - private int tileHeight; - - private Map tileTextures = new HashMap<>(); - private Map entityTextures = new HashMap<>(); - private Map itemTextures = new HashMap<>(); - - /* UI elements */ - private Sprite pacmanSprite; - - public GameView(GameState gameState, - int tileWidth, int tileHeight) { - super(new StackPane()); - root = (StackPane)super.getRoot(); - - canvas = new Canvas(tileWidth * 16 * scaleX, tileHeight * 16 * scaleY); - gc = canvas.getGraphicsContext2D(); - root.getChildren().add(canvas); - - this.tileHeight = tileHeight; - this.tileWidth = tileWidth; - this.gameState = gameState; - this.setFill(Color.BLACK); - - tileTextures.put(TileType.WALL, new Image( - this.getClass().getResourceAsStream("/gameAssets/wall.png"))); - pacmanSprite = new Sprite(new Image( - this.getClass().getResourceAsStream("/gameAssets/pacmanRight.png")), - gameState.getPacman().getPositionX() - 0.5, - gameState.getPacman().getPositionY() - 0.5, - 16 * scaleX, 16 * scaleY); - } - - public void renderGame(double deltaSeconds) { - gc.clearRect(0, 0, tileWidth * 16 * scaleX, tileHeight * 16 * scaleY); - TileType[][] tileBoard = gameState.getBoard().getTileBoard(); - for (int y = 0; y < tileBoard.length; y++) { - for (int x = 0; x < tileBoard[y].length; x++) { - Image texture = tileTextures.get(tileBoard[y][x]); - if (texture != null) { - gc.drawImage(texture, - x * 16 * scaleX, - y * 16 * scaleY, - texture.getWidth() * scaleX, - texture.getHeight() * scaleY); - } - } - } - - pacmanSprite.setX((gameState.getPacman().getPositionX() - - 0.5) * 16 * scaleX); - pacmanSprite.setY((gameState.getPacman().getPositionY() - - 0.5) * 16 * scaleY); - pacmanSprite.render(gc); - - List entities = gameState.getEntities(); - for (Entity entity : entities) { - Image texture = entityTextures.get(entity); - if (texture != null) { - gc.drawImage(texture, - entity.getPositionX() * 16 * scaleX, - entity.getPositionY() * 16 * scaleY, - texture.getWidth() * scaleX, - texture.getHeight() * scaleY); - } - } - - gameState.getItems().forEach((pos, item) -> { - Image texture = itemTextures.get(item); - if (texture != null) { - gc.drawImage(texture, - pos.x() * 16 * scaleX, - pos.y() * 16 * scaleY, - texture.getWidth() * scaleX, - texture.getHeight() * scaleY); - - } - }); - } -} diff --git a/pacman/view/src/main/java/com/gr15/pacman/view/ResourceManager.java b/pacman/view/src/main/java/com/gr15/pacman/view/ResourceManager.java new file mode 100644 index 0000000..3e7103e --- /dev/null +++ b/pacman/view/src/main/java/com/gr15/pacman/view/ResourceManager.java @@ -0,0 +1,77 @@ +package com.gr15.pacman.view; + +import java.util.HashMap; +import java.util.Map; + +import javafx.scene.image.Image; + +/** + * The {@code ResourceManager} class is a singleton responsible + * for managing {@link Image} resources used in the application. + * It provides caching to avoid loading the same {@link Image} multiple times, + * improving performance and resource efficiency. + * + * If an {@link Image} cannot be found at the specified path, + * a default "missing texture" {@link Image} is returned. + */ + +public class ResourceManager { + + /** Singleton instance of ResourceManager. */ + private static ResourceManager instance; + + /** Cache to store loaded images keyed by their file path. */ + private Map resources = new HashMap<>(); + + /** Image to be used when the requested image is not found. */ + private Image missingTexture = new Image( + this.getClass().getResourceAsStream("/gameAssets/missingTexture.png")); + + /** + * Private constructor to prevent external instantiation + * and enforce singleton pattern. + */ + private ResourceManager() {} + + /** + * Returns the singleton instance of {@code ResourceManager}. + * If it does not exist yet, it is created. + * + * @return the singleton instance of {@code ResourceManager}. + */ + public static ResourceManager getInstance() { + if (instance == null) { + instance = new ResourceManager(); + return instance; + } else { + return instance; + } + } + + /** + * Returns an {@link Image} corresponding to the specified resource path. + * If the {@link Image} has already been loaded before, + * it is returned from the cache. Otherwise, it is loaded + * and added to the cache. + * If loading fails (e.g., if the file does not exist), + * the default "missing texture" {@link Image} is returned. + * + * @param path the path to the image resource, relative to the classpath + * @return the loaded {@link Image}, + * or a default image if the resource could not be found + */ + public Image getTexture(String path) { + if (resources.containsKey(path)) { + return resources.get(path); + } else { + try { + Image texture = new Image( + this.getClass().getResourceAsStream(path)); + resources.put(path, texture); + return texture; + } catch (NullPointerException e) { + return missingTexture; + } + } + } +} diff --git a/pacman/view/src/main/java/com/gr15/pacman/view/ViewManager.java b/pacman/view/src/main/java/com/gr15/pacman/view/ViewManager.java new file mode 100644 index 0000000..2f1eb3a --- /dev/null +++ b/pacman/view/src/main/java/com/gr15/pacman/view/ViewManager.java @@ -0,0 +1,92 @@ +package com.gr15.pacman.view; + +import java.util.HashMap; +import java.util.Map; + +import javafx.geometry.Insets; +import javafx.scene.Parent; +import javafx.scene.layout.Background; +import javafx.scene.layout.BackgroundFill; +import javafx.scene.layout.CornerRadii; +import javafx.scene.layout.StackPane; +import javafx.scene.paint.Color; + +/** + * The {@code ViewManager} class is responsible for managing and switching between + * different views in a JavaFX application. It extends {@code StackPane} and provides + * methods to add, remove, check for, and display views identified by an enum key. + */ +public class ViewManager + extends StackPane { + + /** + * Enumeration of all possible view keys used to identify different views. + */ + public enum ViewKeys { MAIN_MENU, GAME_VIEW, PAUSE }; + + /** + * A map that stores views associated with their corresponding keys. + */ + private Map views = new HashMap<>(); + + /** + * Constructs a new {@code ViewManager} with a black background. + */ + public ViewManager() { + setBackground(new Background(new BackgroundFill( + Color.BLACK, + CornerRadii.EMPTY, + Insets.EMPTY + ))); + } + + /** + * Adds a new view to the manager. + * + * @param key the key to identify the view. + * @param view the view to be added. + * @throws IllegalArgumentException if a view with the same key already exists. + */ + public void addView(ViewKeys key, Parent view) { + if (views.containsKey(key)) { + throw new IllegalArgumentException( + "View with key " + key + " already exists."); + } else { + views.put(key, view); + } + } + + /** + * Displays the view associated with the specified key. + * + * @param key the key of the view to be shown. + * @throws IllegalArgumentException if no view exists for the specified key. + */ + public void showView(ViewKeys key) { + if (!views.containsKey(key)) { + throw new IllegalArgumentException( + "No view with key " + key + " exists."); + } else { + getChildren().setAll(views.get(key)); + } + } + + /** + * Checks whether a view exists for the specified key. + * + * @param key the key to check. + * @return {@code true} if a view with the key exists, {@code false} otherwise. + */ + public boolean hasView(ViewKeys key) { + return views.containsKey(key); + } + + /** + * Removes the view associated with the specified key. + * + * @param key the key of the view to remove. + */ + public void removeView(ViewKeys key) { + views.remove(key); + } +} diff --git a/pacman/view/src/main/java/com/gr15/pacman/view/screen/GameView.java b/pacman/view/src/main/java/com/gr15/pacman/view/screen/GameView.java new file mode 100644 index 0000000..33c4e2d --- /dev/null +++ b/pacman/view/src/main/java/com/gr15/pacman/view/screen/GameView.java @@ -0,0 +1,113 @@ +package com.gr15.pacman.view.screen; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.gr15.pacman.view.ResourceManager; +import com.gr15.pacman.view.Sprite; + +import com.gr15.pacman.model.GameState; +import com.gr15.pacman.model.Board.TileType; + +import javafx.geometry.Pos; +import javafx.scene.canvas.Canvas; +import javafx.scene.canvas.GraphicsContext; +import javafx.scene.image.Image; +import javafx.scene.layout.VBox; + +import com.gr15.pacman.model.entities.Entity; +import com.gr15.pacman.model.entities.Items; + +/** + * GameView + */ +public class GameView + extends VBox { + + /* Utils */ + private Canvas canvas; + private GraphicsContext gc; + private GameState gameState; + + private float scaleX = 3.0f; + private float scaleY = 3.0f; + private int tileWidth; + private int tileHeight; + + private Map tileTextures = new HashMap<>(); + private Map entityTextures = new HashMap<>(); + private Map itemTextures = new HashMap<>(); + + /* UI elements */ + private Sprite pacmanSprite; + + public GameView(GameState gameState, int tileWidth, int tileHeight) { + canvas = new Canvas(tileWidth * 16 * scaleX, tileHeight * 16 * scaleY); + gc = canvas.getGraphicsContext2D(); + getChildren().add(canvas); + setAlignment(Pos.CENTER); + + this.tileHeight = tileHeight; + this.tileWidth = tileWidth; + this.gameState = gameState; + + tileTextures.put(TileType.WALL, ResourceManager.getInstance() + .getTexture("/gameAssets/wall.png")); + pacmanSprite = new Sprite( + ResourceManager.getInstance().getTexture("/gameAssets/pacmanRight.png"), + gameState.getPacman().getPositionX() - 0.5, + gameState.getPacman().getPositionY() - 0.5, + 16 * scaleX, 16 * scaleY); + } + + public void renderGame(double deltaSeconds) { + gc.clearRect(0, 0, tileWidth * 16 * scaleX, tileHeight * 16 * scaleY); + TileType[][] tileBoard = gameState.getBoard().getTileBoard(); + for (int y = 0; y < tileBoard.length; y++) { + for (int x = 0; x < tileBoard[y].length; x++) { + Image texture = tileTextures.get(tileBoard[y][x]); + if (texture != null) { + gc.drawImage(texture, + x * 16 * scaleX, + y * 16 * scaleY, + texture.getWidth() * scaleX, + texture.getHeight() * scaleY); + } + } + } + + pacmanSprite.setX((gameState.getPacman().getPositionX() + - 0.5) * 16 * scaleX); + pacmanSprite.setY((gameState.getPacman().getPositionY() + - 0.5) * 16 * scaleY); + pacmanSprite.render(gc); + + List entities = gameState.getEntities(); + for (Entity entity : entities) { + Image texture = entityTextures.get(entity); + if (texture != null) { + gc.drawImage(texture, + entity.getPositionX() * 16 * scaleX, + entity.getPositionY() * 16 * scaleY, + texture.getWidth() * scaleX, + texture.getHeight() * scaleY); + } + } + + gameState.getItems().forEach((pos, item) -> { + Image texture = itemTextures.get(item); + if (texture != null) { + gc.drawImage(texture, + pos.x() * 16 * scaleX, + pos.y() * 16 * scaleY, + texture.getWidth() * scaleX, + texture.getHeight() * scaleY); + } + }); + } + + public void requestFocusForInput() { + requestFocus(); + } +} diff --git a/pacman/view/src/main/java/com/gr15/pacman/view/screen/MainMenuView.java b/pacman/view/src/main/java/com/gr15/pacman/view/screen/MainMenuView.java new file mode 100644 index 0000000..c849fb9 --- /dev/null +++ b/pacman/view/src/main/java/com/gr15/pacman/view/screen/MainMenuView.java @@ -0,0 +1,27 @@ +package com.gr15.pacman.view.screen; + +import javafx.geometry.Pos; +import javafx.scene.control.Button; +import javafx.scene.control.Menu; +import javafx.scene.layout.VBox; + +/** + * MainMenuView + */ +public class MainMenuView + extends VBox { + + private Button newGameButton = new Button("New Game"); + private Button resumeButton = new Button("Resume"); + private Button quitButton = new Button("Exit Game"); + + public MainMenuView() { + resumeButton.setDisable(true); + getChildren().addAll(resumeButton, newGameButton, quitButton); + setAlignment(Pos.CENTER); + } + + public Button getNewGameButton() { return this.newGameButton; } + public Button getResumeButton() { return this.resumeButton; } + public Button getQuitButton() { return this.quitButton; } +} diff --git a/pacman/view/src/main/java/com/gr15/pacman/view/screen/PauseView.java b/pacman/view/src/main/java/com/gr15/pacman/view/screen/PauseView.java new file mode 100644 index 0000000..7dd46e2 --- /dev/null +++ b/pacman/view/src/main/java/com/gr15/pacman/view/screen/PauseView.java @@ -0,0 +1,25 @@ +package com.gr15.pacman.view.screen; + +import javafx.geometry.Pos; +import javafx.scene.control.Button; +import javafx.scene.layout.VBox; + +/** + * PauseView + */ +public class PauseView + extends VBox { + + private Button resumeButton = new Button("Resume"); + private Button mainMenuButton = new Button("Main Menu"); + private Button quitButton = new Button("Quit"); + + public PauseView() { + getChildren().addAll(resumeButton, mainMenuButton, quitButton); + setAlignment(Pos.CENTER); + } + + public Button getResumeButton() { return this.resumeButton; } + public Button getMainMenuButton() { return this.mainMenuButton; } + public Button getQuitButton() { return this.quitButton; } +} -- cgit v1.2.3-70-g09d2