diff options
| author | mithe24 <mithe24@student.sdu.dk> | 2025-05-20 18:26:53 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-05-20 18:26:53 +0200 |
| commit | 8c8a4023c98e076ae6c635b36734dbe28782ed69 (patch) | |
| tree | d85fddfd3ec5db1a2d138573399733922e0209e1 /pacman | |
| parent | bf53777577aea4615559b56b319bfc8b24ed3fa0 (diff) | |
| download | pacman-8c8a4023c98e076ae6c635b36734dbe28782ed69.tar.gz pacman-8c8a4023c98e076ae6c635b36734dbe28782ed69.zip | |
view module overhaul (#26)
* chore(game assets): Removed unneeded pacman assets
Removed unneeded pacman assets, as a single asset just can be rotated.
* chore(): Updating to newest javafx version
Updated javafx to newest version.
Addded transitivity to modules.
* chore(GameController): Removes duplicate class GameController
* feat(camara): Adds camara abstraction and overhauls view
* fix(sprite): Removed exception from setRotation
No exception should be thrown if setting rotation in the Sprite class to
a number below 0 or above 360
Diffstat (limited to 'pacman')
21 files changed, 857 insertions, 225 deletions
diff --git a/pacman/controller/src/main/java/com/gr15/pacman/controller/GameApp.java b/pacman/controller/src/main/java/com/gr15/pacman/controller/GameApp.java index 98a8c5e..9c07798 100644 --- a/pacman/controller/src/main/java/com/gr15/pacman/controller/GameApp.java +++ b/pacman/controller/src/main/java/com/gr15/pacman/controller/GameApp.java @@ -1,44 +1,65 @@ package com.gr15.pacman.controller; -import com.gr15.pacman.model.GameState; -import com.gr15.pacman.model.GameStateBuilder; import com.gr15.pacman.view.ViewManager; -import com.gr15.pacman.view.screen.GameView; import com.gr15.pacman.view.screen.MainMenuView; -import com.gr15.pacman.view.screen.PauseView; import com.gr15.pacman.view.ViewManager.ViewKeys; -import com.gr15.pacman.controller.screen.GameController; import com.gr15.pacman.controller.screen.MainMenuController; -import com.gr15.pacman.controller.screen.PauseController; import javafx.application.Application; import javafx.scene.Scene; import javafx.stage.Stage; /** - * GameApp + * The {@code GameApp} class is the entry point of the Pac-Man application. + * + * <p>It sets up the primary JavaFX stage, initializes the main menu view, + * and connects it to the corresponding controller. It also manages the + * view system using {@link ViewManager} and sets the application window properties.</p> + * + * <p>This class extends {@link javafx.application.Application} and uses + * JavaFX's lifecycle methods.</p> */ public class GameApp extends Application { + /** Manages the different views (scenes) of the application. */ private ViewManager viewManager = new ViewManager(); + + /** The main menu view shown when the application starts. */ private MainMenuView mainMenuView = new MainMenuView(); + /** + * Starts the JavaFX application and initializes the primary stage. + * + * <p>This method sets up the window properties, adds the main menu view + * to the {@link ViewManager}, and creates the {@link MainMenuController} + * to handle user interaction.</p> + * + * @param primaryStage the primary stage for this application + * @throws Exception if an error occurs during startup + */ @Override public void start(Stage primaryStage) throws Exception { + /* window properties */ primaryStage.setTitle("Pac-Man"); primaryStage.setResizable(false); primaryStage.setFullScreen(true); + /* Adding main menu, and instantiate controller */ viewManager.addView(ViewKeys.MAIN_MENU, mainMenuView); viewManager.showView(ViewKeys.MAIN_MENU); new MainMenuController(viewManager, mainMenuView); - Scene scene = new Scene(viewManager, 1920, 1080); + Scene scene = new Scene(viewManager.getRoot(), 1920, 1080); primaryStage.setScene(scene); primaryStage.show(); } - + + /** + * Launches the JavaFX application. + * + * @param args the command-line arguments passed to the application + */ public static void main(String[] args) { launch(args); } diff --git a/pacman/controller/src/main/java/com/gr15/pacman/controller/GameController.java b/pacman/controller/src/main/java/com/gr15/pacman/controller/GameController.java deleted file mode 100644 index a334576..0000000 --- a/pacman/controller/src/main/java/com/gr15/pacman/controller/GameController.java +++ /dev/null @@ -1,63 +0,0 @@ -package com.gr15.pacman.controller; - -import com.gr15.pacman.model.GameState; -import com.gr15.pacman.model.entities.Entity.Direction; -import com.gr15.pacman.view.GameView; - -import javafx.animation.AnimationTimer; - -/** - * GameController - */ -public class GameController { - - private GameState gameState; - private GameView gameView; - - private AnimationTimer gameLoop; - private long lastUpdate = 0; - - public GameController(GameState gameState, GameView gameView) { - this.gameState = gameState; - this.gameView = gameView; - - setupEventHandlers(); - gameLoop = new AnimationTimer() { - - @Override - public void handle(long now) { - if (lastUpdate == 0) { - lastUpdate = now; - return; /* returning early, since no time have elapsed */ - } - - double deltaSeconds = (now - lastUpdate) / 1_000_000_000.0; - lastUpdate = now; - - gameState.update(deltaSeconds); - gameView.renderGame(deltaSeconds); - } - }; - } - - private void setupEventHandlers() { - gameView.setOnKeyPressed(event -> { - switch (event.getCode()) { - case UP -> gameState.getPacman().setDirection(Direction.UP); - case DOWN -> gameState.getPacman().setDirection(Direction.DOWN); - case LEFT -> gameState.getPacman().setDirection(Direction.LEFT); - case RIGHT -> gameState.getPacman().setDirection(Direction.RIGHT); - default -> {} - } - }); - } - - public void startGameLoop() { - gameLoop.start(); - } - - public void stopGameLoop() { - lastUpdate = 0; - gameLoop.stop(); - } -} diff --git a/pacman/controller/src/main/java/com/gr15/pacman/controller/screen/GameController.java b/pacman/controller/src/main/java/com/gr15/pacman/controller/screen/GameController.java index 0bb4e87..585247f 100644 --- a/pacman/controller/src/main/java/com/gr15/pacman/controller/screen/GameController.java +++ b/pacman/controller/src/main/java/com/gr15/pacman/controller/screen/GameController.java @@ -10,22 +10,47 @@ import javafx.animation.AnimationTimer; import javafx.scene.input.KeyEvent; /** - * GameController + * The {@code GameController} class manages the core game loop, + * user input handling, and interaction between the game state, + * view, and view manager. + * + * <p>It uses a JavaFX {@link AnimationTimer} to continuously update + * the game state and render the game view at regular intervals.</p> */ public class GameController { + /** Manages switching between different views/screens. */ private final ViewManager viewManager; + + /** Reference to the current game state. */ private final GameState gameState; + + /** The view responsible for rendering the game. */ private final GameView gameView; + /** The main game loop running. */ private final AnimationTimer gameLoop; + + /** Timestamp of the last update, used to calculate elapsed time. */ private long lastUpdate = 0; - public GameController(GameState gameState, GameView gameView, ViewManager viewManager) { + /** + * Constructs a new {@code GameController} with the specified game state, + * game view, and view manager. + * + * @param gameState the state of the game + * @param gameView the view that renders the game + * @param viewManager the manager responsible for switching views + */ + public GameController(GameState gameState, GameView gameView, + ViewManager viewManager) { + this.viewManager = viewManager; this.gameState = gameState; this.gameView = gameView; + gameView.setOnKeyPressed(this::handleKeyEvent); + gameLoop = new AnimationTimer() { @Override @@ -44,12 +69,20 @@ public class GameController { }; } - public void handleKeyEvent(KeyEvent event) { + /** + * Handles keyboard input for controlling the game, including + * Pac-Man's movement and other interactions like zoom or pausing. + * + * @param event the key event triggered by user input + */ + private void handleKeyEvent(KeyEvent event) { switch (event.getCode()) { case UP -> gameState.getPacman().setDirection(Direction.UP); case DOWN -> gameState.getPacman().setDirection(Direction.DOWN); case LEFT -> gameState.getPacman().setDirection(Direction.LEFT); case RIGHT -> gameState.getPacman().setDirection(Direction.RIGHT); + case PAGE_UP -> gameView.changeZoom(0.1); + case PAGE_DOWN -> gameView.changeZoom(-0.1); case ESCAPE -> { viewManager.showView(ViewKeys.PAUSE); stopGameLoop(); @@ -58,13 +91,16 @@ public class GameController { } } + /** + * Starts the game loop, beginning the update and render cycle. + */ public void startGameLoop() { - gameView.requestFocusForInput(); - gameView.setOnKeyPressed(event -> handleKeyEvent(event)); - gameLoop.start(); } + /** + * Stops the game loop and resets the last update timestamp. + */ public void stopGameLoop() { lastUpdate = 0; gameLoop.stop(); diff --git a/pacman/controller/src/main/java/com/gr15/pacman/controller/screen/MainMenuController.java b/pacman/controller/src/main/java/com/gr15/pacman/controller/screen/MainMenuController.java index 5fa6140..58f4d60 100644 --- a/pacman/controller/src/main/java/com/gr15/pacman/controller/screen/MainMenuController.java +++ b/pacman/controller/src/main/java/com/gr15/pacman/controller/screen/MainMenuController.java @@ -6,42 +6,123 @@ import com.gr15.pacman.view.ViewManager; import com.gr15.pacman.view.screen.GameView; import com.gr15.pacman.view.screen.MainMenuView; import com.gr15.pacman.view.screen.PauseView; + +import javafx.event.ActionEvent; + import com.gr15.pacman.view.ViewManager.ViewKeys; /** - * MainMenuController + * The {@code MainMenuController} handles interactions on the main menu screen. + * + * <p>This includes starting a new game, resuming an existing game, + * or exiting the application. + * It also manages setting up new + * game views and controllers when a new game is launched.</p> */ public class MainMenuController { + /** Manages switching between different views/screens in the application. */ private final ViewManager viewManager; + + /** The main menu view UI. */ private final MainMenuView mainMenuView; + /** The active game view instance, created when starting or resuming a game. */ + private GameView gameView; + + /** The active game controller instance, used to control the game loop. */ + GameController gameController; + + /** + * Constructs a new {@code MainMenuController} with the given + * view manager and main menu view. + * + * @param viewManager the manager responsible for view navigation + * @param mainMenuView the view representing the main menu UI + */ public MainMenuController(ViewManager viewManager, MainMenuView mainMenuView) { this.viewManager = viewManager; this.mainMenuView = mainMenuView; initializeButtons(); } + /** + * Initializes event handlers for the main menu buttons. + * + * <p>This includes setting actions for + * starting a new game, resuming a game, and exiting.</p> + */ private void initializeButtons() { - mainMenuView.getNewGameButton().setOnAction((event) -> { - GameState gameState = GameStateBuilder.fromJson( - this.getClass().getResourceAsStream("/testGameState.json")); - int tileWidth = gameState.getBoard().getWidth(); - int tileHeight = gameState.getBoard().getHeight(); - GameView gameView = new GameView(gameState, tileWidth, tileHeight); - PauseView pauseView = new PauseView(); - GameController gameController = new GameController(gameState, - gameView, viewManager); - new PauseController(pauseView, gameController, viewManager); - viewManager.removeView(ViewKeys.GAME_VIEW); - viewManager.removeView(ViewKeys.PAUSE); - viewManager.addView(ViewKeys.GAME_VIEW, gameView); - viewManager.addView(ViewKeys.PAUSE, pauseView); - viewManager.showView(ViewKeys.GAME_VIEW); - gameController.startGameLoop(); - }); - mainMenuView.getQuitButton().setOnAction((event) -> { - System.exit(0); - }); + mainMenuView.getResumeButton().setDisable(true); + mainMenuView.getNewGameButton().setOnAction(this::startNewGame); + mainMenuView.getExitButton().setOnAction(this::exitGame); + mainMenuView.getResumeButton().setOnAction(this::resumeGame); + } + + /** + * Handles the event when the "Resume" button is clicked. + * + * <p>This resumes the game by switching back to the game view + * and starting the game loop.</p> + * + * @param event the action event triggered by the button + */ + private void resumeGame(ActionEvent event) { + if (!viewManager.hasView(ViewKeys.GAME_VIEW) || gameController == null) { + startNewGame(event); + } + + viewManager.showView(ViewKeys.GAME_VIEW); + gameController.startGameLoop(); + } + + /** + * Starts a new game by creating a new game state, views, and controllers. + * + * <p>Loads the initial game state from a JSON file, + * initializes all related components, and switches to the game view.</p> + * + * @param event the action event triggered by the button + */ + private void startNewGame(ActionEvent event) { + GameState gameState = GameStateBuilder.fromJson( + this.getClass().getResourceAsStream("/testGameState.json")); + + /* Creating new views */ + gameView = new GameView(gameState); + + /* Creaing new controllers */ + gameController = new GameController(gameState, + gameView, viewManager); + + /* Removing potentiel old views */ + viewManager.removeView(ViewKeys.GAME_VIEW); + viewManager.removeView(ViewKeys.PAUSE); + + /* Adding new views */ + viewManager.addView(ViewKeys.GAME_VIEW, gameView); + + /* Enabling resume button */ + mainMenuView.getResumeButton().setDisable(false); + + /* Adding pause menu */ + PauseView pauseView = new PauseView(); + new PauseController(pauseView, gameController, viewManager); + viewManager.addView(ViewKeys.PAUSE, pauseView); + + /* Showing game view and starting game */ + viewManager.showView(ViewKeys.GAME_VIEW); + gameController.startGameLoop(); + } + + /** + * Handles the event when the "Quit" button is clicked. + * + * <p>This will exit the application immediately.</p> + * + * @param event the action event triggered by the button + */ + private void exitGame(ActionEvent event) { + System.exit(0); } } diff --git a/pacman/controller/src/main/java/com/gr15/pacman/controller/screen/PauseController.java b/pacman/controller/src/main/java/com/gr15/pacman/controller/screen/PauseController.java index 88b60ea..553455a 100644 --- a/pacman/controller/src/main/java/com/gr15/pacman/controller/screen/PauseController.java +++ b/pacman/controller/src/main/java/com/gr15/pacman/controller/screen/PauseController.java @@ -4,31 +4,101 @@ import com.gr15.pacman.view.ViewManager; import com.gr15.pacman.view.ViewManager.ViewKeys; import com.gr15.pacman.view.screen.PauseView; +import javafx.event.ActionEvent; +import javafx.scene.input.KeyEvent; + /** - * PauseController + * The {@code PauseController} handles user interactions on the game's pause screen. + * + * <p>This includes resuming the game, quitting to the main menu, + * or exiting the application entirely. + * It also manages key input while the pause view is active, + * such as using the ESC key to resume the game.</p> */ public class PauseController { + /** The view representing the pause screen UI. */ private final PauseView pauseView; + + /** The controller managing the main game loop. */ private final GameController gameController; + + /** The view manager responsible for switching between different scenes. */ private final ViewManager viewManager; + /** + * Constructs a new {@code PauseController} with the specified + * view, game controller, and view manager. + * + * @param pauseView the pause screen UI + * @param gameController the game controller to resume or control the game state + * @param viewManager the view manager for switching between views + */ public PauseController(PauseView pauseView, GameController gameController, ViewManager viewManager) { this.gameController = gameController; this.pauseView = pauseView; this.viewManager = viewManager; - initialize(); + setupEventHandlers(); + } + + /** + * Initializes event handlers for pause screen buttons and keyboard input. + */ + private void setupEventHandlers() { + pauseView.getQuitButton().setOnAction(this::exitGame); + pauseView.getMainMenuButton().setOnAction(this::quitToMainMenu); + pauseView.getResumeButton().setOnAction(this::resumeGame); + pauseView.setOnKeyPressed(this::handleKeyEvent); + } + + /** + * Handles key events while the pause view is active. + * + * <p>Pressing ESC will resume the game and return to the game view.</p> + * + * @param event the key event triggered by user input + */ + private void handleKeyEvent(KeyEvent event) { + switch (event.getCode()) { + case ESCAPE -> { + viewManager.showView(ViewKeys.GAME_VIEW); + gameController.startGameLoop(); + } + default -> {} + } + } + + /** + * Handles the event when the "Main Menu" button is clicked. + * + * @param event the action event triggered by the button + */ + private void quitToMainMenu(ActionEvent event) { + viewManager.showView(ViewKeys.MAIN_MENU); + } + + /** + * Handles the event when the "Quit" button is clicked. + * + * <p>This will exit the application immediately.</p> + * + * @param event the action event triggered by the button + */ + private void exitGame(ActionEvent event) { + System.exit(0); } - private void initialize() { - pauseView.getQuitButton().setOnAction((e) -> { System.exit(0); }); - pauseView.getMainMenuButton().setOnAction((e) -> { - viewManager.showView(ViewKeys.MAIN_MENU); - }); - pauseView.getResumeButton().setOnAction((e) -> { - viewManager.showView(ViewKeys.GAME_VIEW); - gameController.startGameLoop(); - }); + /** + * Handles the event when the "Resume" button is clicked. + * + * <p>This resumes the game by switching back to the game view + * and starting the game loop.</p> + * + * @param event the action event triggered by the button + */ + private void resumeGame(ActionEvent evnet) { + viewManager.showView(ViewKeys.GAME_VIEW); + gameController.startGameLoop(); } } diff --git a/pacman/controller/src/main/java/module-info.java b/pacman/controller/src/main/java/module-info.java index c240f2c..71af35c 100644 --- a/pacman/controller/src/main/java/module-info.java +++ b/pacman/controller/src/main/java/module-info.java @@ -3,8 +3,8 @@ */ module com.gr15.pacman.controller { requires javafx.controls; - requires javafx.graphics; - requires com.gr15.pacman.view; + requires transitive javafx.graphics; + requires transitive com.gr15.pacman.view; requires com.gr15.pacman.model; exports com.gr15.pacman.controller; diff --git a/pacman/pom.xml b/pacman/pom.xml index f9c7d0c..53f9442 100644 --- a/pacman/pom.xml +++ b/pacman/pom.xml @@ -11,7 +11,7 @@ <properties> <maven.compiler.release>24</maven.compiler.release> - <javafx.version>17.0.2</javafx.version> + <javafx.version>25-ea+17</javafx.version> </properties> <modules> diff --git a/pacman/view/src/main/java/com/gr15/pacman/view/AnimatedSprite.java b/pacman/view/src/main/java/com/gr15/pacman/view/AnimatedSprite.java index 763c876..0e82a56 100644 --- a/pacman/view/src/main/java/com/gr15/pacman/view/AnimatedSprite.java +++ b/pacman/view/src/main/java/com/gr15/pacman/view/AnimatedSprite.java @@ -3,29 +3,84 @@ package com.gr15.pacman.view; import javafx.scene.canvas.GraphicsContext; import javafx.scene.image.Image; +/** + * Represents an animated sprite that cycles through multiple image frames over time. + * + * <p>This class extends {@link Sprite} and adds support for animation by switching + * between a sequence of images (frames) at a fixed interval (frame time). + * The animation progresses by calling the {@link #update(double)} method + * with the time delta since the last update.</p> + * + * <p>The sprite is rendered using the current frame image when + * {@link #render(GraphicsContext)} is called.</p> + */ public class AnimatedSprite extends Sprite { + + /** The array of frames (images) used for the animation. */ private Image[] frames; + + /** The index of the current frame in the animation. */ private int currentFrame = 0; - private double frameTime = 0.1; // seconds per frame + + /** The duration (in seconds) each frame is shown. */ + private double frameTime = 0.5; + + /** The time (in seconds) since the last frame update. */ private double timeSinceLastFrame = 0.0; - public AnimatedSprite(Image[] frames, double x, double y, double width, double height) { + /** Boolean to keep track if going forward or backwards through array */ + private boolean forward = true; + + /** + * Constructs a new {@code AnimatedSprite} with the specified + * animation frames, position, and size. + * + * @param frames the array of {@link Image} objects representing animation frames + * @param x the X-coordinate of the sprite's top-left corner + * @param y the Y-coordinate of the sprite's top-left corner + * @param width the width of the sprite + * @param height the height of the sprite + * + * @throws IllegalArgumentException if {@code frames} is null or empty + */ + public AnimatedSprite(Image[] frames, double x, double y, + double width, double height) { super(frames[0], x, y, width, height); + if (frames == null || frames.length == 0) { + throw new IllegalArgumentException(""" + Animation frames must not be null or empty"""); + } this.frames = frames; } + /** + * Updates the animation using a ping-pong effect (forward and backward loop). + * + * <p>If the accumulated time since the last frame exceeds the {@code frameTime}, + * the current frame index is incremented to show the next frame in the sequence.</p> + * + * @param deltaSeconds the time in seconds since the last update call + */ public void update(double deltaSeconds) { timeSinceLastFrame += deltaSeconds; if (timeSinceLastFrame >= frameTime) { - currentFrame = (currentFrame + 1) % frames.length; + if (forward) { + currentFrame++; + if (currentFrame >= frames.length - 1) { + currentFrame = frames.length - 1; + forward = false; + } + } else { + currentFrame--; + if (currentFrame <= 0) { + currentFrame = 0; + forward = true; + } + } + super.setImage(frames[currentFrame]); timeSinceLastFrame = 0.0; } } - - @Override - public void render(GraphicsContext gc) { - super.render(gc); - } } diff --git a/pacman/view/src/main/java/com/gr15/pacman/view/Sprite.java b/pacman/view/src/main/java/com/gr15/pacman/view/Sprite.java index f397e7d..74be172 100644 --- a/pacman/view/src/main/java/com/gr15/pacman/view/Sprite.java +++ b/pacman/view/src/main/java/com/gr15/pacman/view/Sprite.java @@ -3,33 +3,172 @@ package com.gr15.pacman.view; import javafx.scene.canvas.GraphicsContext; import javafx.scene.image.Image; +/** + * Represents a sprite that can be drawn onto a {@link GraphicsContext} + * in a specific position with a specified size and rotation. + * This class is typically used for visual entities in the game. + * + * <p>A sprite is an image that can be + * rendered, rotated, and positioned on the screen.</p> + */ public class Sprite { + + /** The {@link Image} of the sprite */ private Image image; - private double x, y; - private double width, height; + /** The X-coordinate of the sprite's top-left corner. */ + private double x; + + /** The Y-coordinate of the sprite's top-left corner. */ + private double y; + + /** The width of the sprite */ + private double width; + + /** The height of the sprite */ + private double height; + + /** The rotation of the sprite in degrees (0-360). */ + private double rotation; + + /** + * Constructs a new {@code Sprite} with the specified + * image, position, size, and rotation. + * + * @param image the image to be used as the sprite's texture + * @param x the X-coordinate of the sprite's top-left corner + * @param y the Y-coordinate of the sprite's top-left corner + * @param width the width of the sprite + * @param height the height of the sprite + */ public Sprite(Image image, double x, double y, double width, double height) { this.image = image; this.x = x; this.y = y; this.width = width; this.height = height; + this.rotation = 0; } + /** + * Renders the sprite onto the provided {@link GraphicsContext} + * with the current position, size, and rotation. + * + * <p>The sprite will be drawn at its current position + * with its current rotation and size. + * The transformation for rotation is applied around the sprite's center.</p> + * + * @param gc the {@link GraphicsContext} to render the sprite to + */ public void render(GraphicsContext gc) { + /* Saving the current transformation */ + gc.save(); + + /* Getting center of sprite */ + double centerX = x + width / 2; + double centerY = y + height / 2; + + /* Center on sprite */ + gc.translate(centerX, centerY); + + /* Applying rotation */ + gc.rotate(rotation); + + /* Moving origin back before to orignal position */ + gc.translate(-centerX, -centerY); + + /* Drawing sprite */ gc.drawImage(image, x, y, width, height); + + /* Restoring transformation */ + gc.restore(); } - /* Getters and setters */ + /** + * Returns the image used for this sprite. + * + * @return the sprite's image + */ public Image getImage() { return this.image; } + + /** + * Returns the X-coordinate of the sprite's position. + * + * @return the X-coordinate + */ public double getX() { return this.x; } + + /** + * Returns the Y-coordinate of the sprite's position. + * + * @return the Y-coordinate + */ public double getY() { return this.y; } + + /** + * Returns the width of the sprite. + * + * @return the width of the sprite + */ public double getWidth() { return this.width; } - public double getHeigt() { return this.height; } + /** + * Returns the height of the sprite. + * + * @return the height of the sprite + */ + public double getHeight() { return this.height; } + + /** + * Returns the current rotation of the sprite in degrees. + * + * @return the rotation in degrees + */ + public double getRotation() { return this.rotation; } + + /** + * Sets the rotation of the sprite. The rotation is specified in degrees, + * and should be between 0 and 360. + * + * @param newRotation the new rotation in degrees + * @throws IllegalArgumentException if the rotation is outside the range (0, 360) + */ + public void setRotation(double newRotation) { + this.rotation = newRotation; + } + + /** + * Sets a new image for the sprite. + * + * @param newImage the new image to set + */ public void setImage(Image newImage) { this.image = newImage; } + + /** + * Sets a new image for the sprite. + * + * @param newImage the new image to set + */ public void setX(double newX) { this.x = newX; } + + /** + * Sets a new Y-coordinate for the sprite's position. + * + * @param newY the new Y-coordinate + */ public void setY(double newY) { this.y = newY; } + + /** + * Sets a new width for the sprite. + * + * @param newWidth the new width + */ public void setWidth(double newWidth) { this.width = newWidth; } + + /** + * Sets a new height for the sprite. + * + * @param newHeight the new height + */ public void setHeight(double newHeight) { this.height = newHeight; } } 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 index 2f1eb3a..b648786 100644 --- a/pacman/view/src/main/java/com/gr15/pacman/view/ViewManager.java +++ b/pacman/view/src/main/java/com/gr15/pacman/view/ViewManager.java @@ -3,6 +3,8 @@ package com.gr15.pacman.view; import java.util.HashMap; import java.util.Map; +import com.gr15.pacman.view.screen.BaseView; + import javafx.geometry.Insets; import javafx.scene.Parent; import javafx.scene.layout.Background; @@ -12,12 +14,16 @@ 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. + * Manages multiple views in a JavaFX application by allowing switching between + * them using unique keys. Each view is expected to extend {@link BaseView}. */ -public class ViewManager - extends StackPane { +public class ViewManager { + + /** The root container holding the currently displayed view. */ + private StackPane rootPane = new StackPane(); + + /** The currently active view. */ + private BaseView currentView; /** * Enumeration of all possible view keys used to identify different views. @@ -27,13 +33,13 @@ public class ViewManager /** * A map that stores views associated with their corresponding keys. */ - private Map<ViewKeys, Parent> views = new HashMap<>(); + private Map<ViewKeys, BaseView> views = new HashMap<>(); /** * Constructs a new {@code ViewManager} with a black background. */ public ViewManager() { - setBackground(new Background(new BackgroundFill( + rootPane.setBackground(new Background(new BackgroundFill( Color.BLACK, CornerRadii.EMPTY, Insets.EMPTY @@ -47,7 +53,7 @@ public class ViewManager * @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) { + public void addView(ViewKeys key, BaseView view) { if (views.containsKey(key)) { throw new IllegalArgumentException( "View with key " + key + " already exists."); @@ -58,6 +64,7 @@ public class ViewManager /** * Displays the view associated with the specified key. + * Calls {@code onExit} on the current view (if any) and {@code onEnter} on the new view. * * @param key the key of the view to be shown. * @throws IllegalArgumentException if no view exists for the specified key. @@ -66,9 +73,16 @@ public class ViewManager if (!views.containsKey(key)) { throw new IllegalArgumentException( "No view with key " + key + " exists."); - } else { - getChildren().setAll(views.get(key)); } + + if (currentView != null) { + currentView.onExit(); + rootPane.getChildren().remove(currentView); + } + + currentView = views.get(key); + rootPane.getChildren().add(currentView); + currentView.onEnter(); } /** @@ -89,4 +103,14 @@ public class ViewManager public void removeView(ViewKeys key) { views.remove(key); } + + /** + * Returns the root node that contains the currently active view. + * This root can be added to the JavaFX scene graph. + * + * @return the root {@code Parent} node. + */ + public Parent getRoot() { + return rootPane; + } } diff --git a/pacman/view/src/main/java/com/gr15/pacman/view/screen/BaseView.java b/pacman/view/src/main/java/com/gr15/pacman/view/screen/BaseView.java new file mode 100644 index 0000000..89dfc56 --- /dev/null +++ b/pacman/view/src/main/java/com/gr15/pacman/view/screen/BaseView.java @@ -0,0 +1,23 @@ +package com.gr15.pacman.view.screen; + +import javafx.scene.Parent; + +/** + * An abstract base class for all views managed by the {@link ViewManager}. + * Each view must implement the lifecycle methods {@code onEnter} and {@code onExit}. + */ +public abstract class BaseView + extends Parent { + + /** + * Called when the view becomes visible and is added to the scene. + * Implement this method to initialize or refresh the view content. + */ + public abstract void onEnter(); + + /** + * Called when the view is about to be removed from the scene. + * Implement this method to handle any cleanup or state-saving tasks. + */ + public abstract void onExit(); +} 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 index c0c6cba..1c333d7 100644 --- 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 @@ -1,114 +1,237 @@ 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 com.gr15.pacman.model.entities.Entity; +import com.gr15.pacman.model.entities.Ghost; +import com.gr15.pacman.model.entities.Pacman; +import com.gr15.pacman.view.AnimatedSprite; +import com.gr15.pacman.view.ResourceManager; +import com.gr15.pacman.view.Sprite; 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; +import javafx.scene.layout.VBox; +import javafx.scene.transform.Affine; /** - * GameView + * Represents the main in-game view where the gameplay takes place. + * + * This class is responsible for rendering the game board, entities such as Pacman + * and ghosts, and applying camera transformations. It uses a {@link Canvas} for rendering + * with a fixed logical resolution. + * This view extends {@link BaseView}. */ public class GameView - extends VBox { + extends BaseView { - /* Utils */ + /** The root layout container for this view. */ + private VBox root = new VBox(); + + /** Canvas used to draw the game graphics. */ private Canvas canvas; + + /************************************************************* + * CONSTANTS * + *************************************************************/ + + /** Logical width of the canvas for consistent rendering. */ + private static final double LOGICAL_WIDTH = 1920; + + /** Logical width of the canvas for consistent rendering. */ + private static final double LOGICAL_HEIGHT = 1080; + + /** Reference to the current game state for rendering entities and board. */ + private static final int TILE_SIZE = 16; + + /************************************************************* + * UTILITIES * + *************************************************************/ + + /** Reference to the {@link ResourceManager} singleton instance. */ + private ResourceManager resourceManager = ResourceManager.getInstance(); + + /** Reference to the current game state for rendering entities and board. */ + private final GameState gameState; + + /** Graphics context used for rendering on the {@link Canvas}. */ private GraphicsContext gc; - private GameState gameState; - private float scaleX = 3.0f; - private float scaleY = 3.0f; + /** Affine transformation for simulating camera movement and zooming. */ + private Affine camara = new Affine(); - private int tileWidth; - private int tileHeight; + /** Current zoom factor. */ + private double currentZoom = 1; - private Map<TileType, Image> tileTextures = new HashMap<>(); - private Map<Entity, Image> entityTextures = new HashMap<>(); - private Map<Items, Image> itemTextures = new HashMap<>(); + /************************************************************* + * SPRITES * + *************************************************************/ - /* UI elements */ - private Sprite pacmanSprite; + /** Pacman sprite for rendering the player's character. */ + private AnimatedSprite pacman; - 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); + private Sprite redGhost; + private Sprite blueGhost; + private Sprite pinkGhost; + private Sprite orangeGhost; - this.tileHeight = tileHeight; - this.tileWidth = tileWidth; + /** + * Constructs a new {@code GameView} with the given game state. + * Initializes the rendering canvas and adds it to the scene graph. + * + * @param gameState the current game state to be rendered + */ + public GameView(GameState gameState) { 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); + + canvas = new Canvas(LOGICAL_WIDTH, LOGICAL_HEIGHT); + gc = canvas.getGraphicsContext2D(); + + root.setAlignment(Pos.TOP_CENTER); + root.getChildren().add(canvas); + getChildren().add(root); + + /* Setting up animated sprites */ + + Image[] pacmanFrames = { + resourceManager.getTexture("/gameAssets/pacman1.png"), + resourceManager.getTexture("/gameAssets/pacman2.png"), + resourceManager.getTexture("/gameAssets/pacman3.png") + }; + pacman = new AnimatedSprite(pacmanFrames, 0, 0, TILE_SIZE, TILE_SIZE); } + /************************************************************* + * METHODS * + *************************************************************/ + + /** + * Renders the game frame. This includes clearing the screen, + * updating the camera transformation, drawing the game board, and + * rendering all game entities (e.g., Pacman and ghosts). + * + * @param deltaSeconds time since the last frame, used for animation timing + */ public void renderGame(double deltaSeconds) { - gc.clearRect(0, 0, tileWidth * 16 * scaleX, tileHeight * 16 * scaleY); + gc.clearRect(0, 0, canvas.getWidth(), canvas.getHeight()); + updateCamara(); + renderBoard(); + + for (Ghost ghost : gameState.getGhosts()) { + /* Update each ghost sprite */ + } + + /* Rotating pacman sprite */ + Pacman pacmanEntity = gameState.getPacman(); + switch (pacmanEntity.getDirection()) { + case UP -> pacman.setRotation(0); + case DOWN -> pacman.setRotation(180); + case LEFT -> pacman.setRotation(270); + case RIGHT -> pacman.setRotation(90); + default -> {} + } + pacman.update(deltaSeconds); + renderEntity(pacmanEntity, pacman); + } + + /** + * Updates the camera transform based on Pacman's position, + * centering the camera and applying zoom. + */ + private void updateCamara() { + camara.setToIdentity(); + double screenWidth = canvas.getWidth(); + double screenHeight = canvas.getHeight(); + double scaleX = (screenWidth / LOGICAL_WIDTH) * currentZoom; + double scaleY = (screenHeight / LOGICAL_HEIGHT) * currentZoom; + double scale = Math.min(scaleX, scaleY); + + /* Define center of camara */ + double centerX = gameState.getPacman().getPositionX() * TILE_SIZE; + double centerY = gameState.getPacman().getPositionY() * TILE_SIZE; + + camara.appendTranslation(screenWidth / 2, screenHeight / 2); + camara.appendScale(scale, scale); + camara.appendTranslation(-centerX, -centerY); + + gc.setTransform(camara); + } + + /** + * Renders the game board based on tile types from the current game state. + * Draws wall textures and leaves empty tiles blank. + */ + private void renderBoard() { 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); + TileType tile = tileBoard[y][x]; + double worldX = x * TILE_SIZE; + double worldY = y * TILE_SIZE; + + switch (tile) { + case WALL -> { + gc.drawImage(ResourceManager.getInstance() + .getTexture("/gameAssets/wall.png"), + worldX, worldY, TILE_SIZE, TILE_SIZE + ); + } + case EMPTY -> {} } } } + } - pacmanSprite.setX((gameState.getPacman().getPositionX() - - 0.5) * 16 * scaleX); - pacmanSprite.setY((gameState.getPacman().getPositionY() - - 0.5) * 16 * scaleY); - pacmanSprite.render(gc); + /** + * Renders a given entity (e.g., Pacman or ghost), + * using its corresponding sprite. + * + * @param entity the game entity to render + * @param sprite the sprite to use for rendering the entity + */ + private void renderEntity(Entity entity, Sprite sprite) { + double spriteX = entity.getPositionX() * TILE_SIZE; + double spriteY = entity.getPositionY() * TILE_SIZE; - List<Entity> 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); - } - } + sprite.setX(spriteX - sprite.getWidth() / 2); + sprite.setY(spriteY - sprite.getHeight() / 2); - 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); - } - }); + sprite.render(gc); + } + + /** + * Adjusts the current zoom level of the camera. + * + * @param deltaZoom the amount to change the zoom level by + */ + public void changeZoom(double deltaZoom) { + currentZoom += deltaZoom; + } + + /** + * Called when this view becomes active. Requests focus for input handling. + */ + @Override + public void onEnter() { + this.requestFocus(); + } + + /** + * Called when this view is deactivated or switched away from. + */ + @Override + public void onExit() { + /* No specific behavior on exit */ } - public void requestFocusForInput() { - requestFocus(); + /** + * Returns the root container of this view. + * + * @return the root VBox + */ + public VBox getRoot() { + return root; } } 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 index c849fb9..912ec7b 100644 --- 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 @@ -2,26 +2,89 @@ 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 + * Represents the main menu view of the application, containing UI elements + * such as buttons for starting a new game, resuming a game, and exiting. + * + * This view extends {@link BaseView} and lays out the buttons vertically + * centered using a {@link VBox}. */ public class MainMenuView - extends VBox { + extends BaseView { + /** The root layout container for this view. */ + private VBox root = new VBox(); + + /************************************************************* + * UI ELEMENTS * + *************************************************************/ + + /** Button to start a new game. */ private Button newGameButton = new Button("New Game"); + + /** Button resume an existing game. */ private Button resumeButton = new Button("Resume"); - private Button quitButton = new Button("Exit Game"); + /** Button to exit the game. */ + private Button exitButton = new Button("Exit Game"); + + /** + * Constructs a new {@code MainMenuView} and initializes the layout with + * three main buttons: Resume, New Game, and Exit Game. The layout is + * vertically centered. + */ public MainMenuView() { - resumeButton.setDisable(true); - getChildren().addAll(resumeButton, newGameButton, quitButton); - setAlignment(Pos.CENTER); + root.setAlignment(Pos.CENTER); + root.getChildren().addAll(resumeButton, newGameButton, exitButton); + getChildren().add(root); + } + + /************************************************************* + * METHODS * + *************************************************************/ + + /** + * Called when the view is entered. This method is part of the + * view lifecycle and can be overridden to implement specific behavior. + */ + @Override + public void onEnter() { + /* No specific behavior yet. */ + } + + /** + * Called when the view is exited. This method is part of the + * view lifecycle and can be overridden to implement specific behavior. + */ + @Override + public void onExit() { + /* No specific behavior yet. */ } + /************************************************************* + * GETTERS AND SETTERS * + *************************************************************/ + + /** + * Returns the button used to start a new game. + * + * @return the new game button + */ public Button getNewGameButton() { return this.newGameButton; } + + /** + * Returns the button used to resume an existing game. + * + * @return the resume button + */ public Button getResumeButton() { return this.resumeButton; } - public Button getQuitButton() { return this.quitButton; } + + /** + * Returns the button used to exit the game. + * + * @return the exit button + */ + public Button getExitButton() { return this.exitButton; } } 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 index 7dd46e2..fdf32b9 100644 --- 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 @@ -5,21 +5,81 @@ import javafx.scene.control.Button; import javafx.scene.layout.VBox; /** - * PauseView + * Represents the main menu view of the application, containing UI elements + * such as buttons for starting a new game, resuming a game, and exiting. + * + * This view extends {@link BaseView} and lays out the buttons vertically + * centered using a {@link VBox}. */ public class PauseView - extends VBox { + extends BaseView { + /** The root layout container for this view. */ + private VBox root = new VBox(); + + /************************************************************* + * UI ELEMENTS * + *************************************************************/ + + /** Button resume an existing game. */ private Button resumeButton = new Button("Resume"); + + /** Button for exiting to main menu */ private Button mainMenuButton = new Button("Main Menu"); + + /** Button to exit the game. */ private Button quitButton = new Button("Quit"); + /** + * Constructs a new {@code PauseView} and initializes the layout with + * three main buttons: Resume, Main Menu, and Exit Game. The layout is + * vertically centered. + */ public PauseView() { - getChildren().addAll(resumeButton, mainMenuButton, quitButton); - setAlignment(Pos.CENTER); + root.getChildren().addAll(resumeButton, mainMenuButton, quitButton); + root.setAlignment(Pos.CENTER); + getChildren().add(root); + } + + /** + * Called when this view becomes active. Requests focus for input handling. + */ + @Override + public void onEnter() { + this.requestFocus(); } + /** + * Called when the view is exited. This method is part of the + * view lifecycle and can be overridden to implement specific behavior. + */ + @Override + public void onExit() { + /* No behavior on exit */ + } + + /************************************************************* + * GETTERS * + *************************************************************/ + + /** + * Returns the button used to resume an existing game. + * + * @return the resume button + */ public Button getResumeButton() { return this.resumeButton; } + + /** + * Returns the button used to quit to main menu. + * + * @return the quit button + */ public Button getMainMenuButton() { return this.mainMenuButton; } + + /** + * Returns the button used to exit the game. + * + * @return the quit button + */ public Button getQuitButton() { return this.quitButton; } } diff --git a/pacman/view/src/main/java/module-info.java b/pacman/view/src/main/java/module-info.java index f91361e..c3e2178 100644 --- a/pacman/view/src/main/java/module-info.java +++ b/pacman/view/src/main/java/module-info.java @@ -2,9 +2,9 @@ * This acts as the manifest for the module. */ module com.gr15.pacman.view { - requires javafx.controls; - requires javafx.graphics; - requires com.gr15.pacman.model; + requires transitive javafx.controls; + requires transitive javafx.graphics; + requires transitive com.gr15.pacman.model; exports com.gr15.pacman.view.screen; exports com.gr15.pacman.view; diff --git a/pacman/view/src/main/resources/gameAssets/pacman1.png b/pacman/view/src/main/resources/gameAssets/pacman1.png Binary files differnew file mode 100644 index 0000000..743fe35 --- /dev/null +++ b/pacman/view/src/main/resources/gameAssets/pacman1.png diff --git a/pacman/view/src/main/resources/gameAssets/pacmanUp.png b/pacman/view/src/main/resources/gameAssets/pacman2.png Binary files differindex 9f4a39b..9f4a39b 100644 --- a/pacman/view/src/main/resources/gameAssets/pacmanUp.png +++ b/pacman/view/src/main/resources/gameAssets/pacman2.png diff --git a/pacman/view/src/main/resources/gameAssets/pacman3.png b/pacman/view/src/main/resources/gameAssets/pacman3.png Binary files differnew file mode 100644 index 0000000..27a4a26 --- /dev/null +++ b/pacman/view/src/main/resources/gameAssets/pacman3.png diff --git a/pacman/view/src/main/resources/gameAssets/pacmanDown.png b/pacman/view/src/main/resources/gameAssets/pacmanDown.png Binary files differdeleted file mode 100644 index 2c24dbe..0000000 --- a/pacman/view/src/main/resources/gameAssets/pacmanDown.png +++ /dev/null diff --git a/pacman/view/src/main/resources/gameAssets/pacmanLeft.png b/pacman/view/src/main/resources/gameAssets/pacmanLeft.png Binary files differdeleted file mode 100644 index 168f495..0000000 --- a/pacman/view/src/main/resources/gameAssets/pacmanLeft.png +++ /dev/null diff --git a/pacman/view/src/main/resources/gameAssets/pacmanRight.png b/pacman/view/src/main/resources/gameAssets/pacmanRight.png Binary files differdeleted file mode 100644 index 7e370a0..0000000 --- a/pacman/view/src/main/resources/gameAssets/pacmanRight.png +++ /dev/null |