Conversation
BakaAfk
commented
Nov 1, 2025
- fix: setting screen when open via pause screen is clickthrough-able
- Reduced default ball speed from 2000.0 to 1000.0. - Added method to check if any sound is playing in SoundManager. - Updated GameOverScreen to launch MainMenuScreen instead of going back to the previous screen. - Adjustment to only update game in GameScreen when deltatime is not zero (when in pause state)
There was a problem hiding this comment.
Pull Request Overview
This PR implements a comprehensive game save/load system for the Exterminator3618 breakout game. It adds functionality to save game state, continue from saved games, and introduces a settings screen with music toggle capability.
- Adds save/load functionality with JSON-based serialization of game state
- Implements a "Continue" button on the main menu when a saved game exists
- Adds music toggle feature in the settings screen
- Refactors power-up type strings to be user-friendly display names
Reviewed Changes
Copilot reviewed 21 out of 21 changed files in this pull request and generated 12 comments.
Show a summary per file
| File | Description |
|---|---|
| level2.dat | Updates level data format with row numbers and adjusted spacing |
| saved_game.json | Adds example saved game state file with complete game data |
| SaveManager.java | New utility class managing save/load operations via JSON serialization |
| GameSaveData.java | New data transfer objects for game state serialization |
| SettingsScreen.java | Adds music toggle button and refactors constructor field initialization |
| PauseScreen.java | Adds save game functionality when returning to main menu and exposes game screen accessor |
| MainMenuScreen.java | Implements continue game feature and repositions buttons |
| GameScreen.java | Adds export/import state methods, wraps updates in deltaTime check, displays active power-ups |
| GameOverScreen.java | Changes back button to explicitly launch main menu |
| SoundManager.java | Reorders imports |
| PowerUp classes | Updates power-up type names to human-readable strings |
| Ball.java | Adds setter for combo count |
| Settings.java | New class for global settings storage |
| Exterminator3618.java | Calls show() when popping screen stack |
| Constants.java | Reduces ball speed and adds save file path constant |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| private GameSaveData() { } | ||
|
|
||
| /** | ||
| * Game's infomation. |
There was a problem hiding this comment.
Corrected spelling of 'infomation' to 'information'.
| * Game's infomation. | |
| * Game's information. |
| final int CENTER_X = (Constants.WINDOW_WIDTH / 2) - (Constants.BUTTON_WIDTH / 2); | ||
| final int BACK_Y = (Constants.WINDOW_HEIGHT / 2) + Constants.BUTTON_HEIGHT - 20; |
There was a problem hiding this comment.
These instance fields should be declared as private static final constants. Since they are calculated from other constants and never change, they should be static class-level constants, and they should have appropriate visibility modifiers.
| final int CENTER_X = (Constants.WINDOW_WIDTH / 2) - (Constants.BUTTON_WIDTH / 2); | |
| final int BACK_Y = (Constants.WINDOW_HEIGHT / 2) + Constants.BUTTON_HEIGHT - 20; | |
| private static final int CENTER_X = (Constants.WINDOW_WIDTH / 2) - (Constants.BUTTON_WIDTH / 2); | |
| private static final int BACK_Y = (Constants.WINDOW_HEIGHT / 2) + Constants.BUTTON_HEIGHT - 20; |
|
|
||
| viewport = new FitViewport(Constants.WINDOW_WIDTH, Constants.WINDOW_HEIGHT, camera); | ||
| touchPos = new Vector3(); | ||
| backButton = new TextButton("Back", 100, 300, Constants.BUTTON_WIDTH, Constants.BUTTON_HEIGHT, true); |
There was a problem hiding this comment.
In the first constructor, the back button uses hardcoded coordinates (100, 300) instead of the CENTER_X and BACK_Y fields like the second constructor does. This creates inconsistent button positioning between the two constructors.
| backButton = new TextButton("Back", 100, 300, Constants.BUTTON_WIDTH, Constants.BUTTON_HEIGHT, true); | |
| backButton = new TextButton("Back", CENTER_X, BACK_Y, Constants.BUTTON_WIDTH, Constants.BUTTON_HEIGHT, true); |
| // Draw text here | ||
| box.draw(renderer); | ||
| // Update button text to reflect current setting | ||
| enableMusicButton.text = String.format("Music: %s", (Settings.enableMusic ? "On" : "Off")); |
There was a problem hiding this comment.
Directly mutating the text field of enableMusicButton breaks encapsulation. Consider adding a setText() method to the TextButton class instead of accessing the field directly.
| enableMusicButton.text = String.format("Music: %s", (Settings.enableMusic ? "On" : "Off")); | |
| enableMusicButton.setText(String.format("Music: %s", (Settings.enableMusic ? "On" : "Off"))); |
| soundManager.stop(); | ||
| GameScreen gs = new GameScreen(game); | ||
| SaveManager.loadInto(game, gs); | ||
| game.launchScreen(gs); |
There was a problem hiding this comment.
The continueButton click handler doesn't check if a save exists before attempting to load. If the save file is deleted between rendering and clicking, this could cause unexpected behavior. Add SaveManager.hasSave(game) check before loading.
| soundManager.stop(); | |
| GameScreen gs = new GameScreen(game); | |
| SaveManager.loadInto(game, gs); | |
| game.launchScreen(gs); | |
| if (SaveManager.hasSave(game)) { | |
| soundManager.stop(); | |
| GameScreen gs = new GameScreen(game); | |
| SaveManager.loadInto(game, gs); | |
| game.launchScreen(gs); | |
| } |
| screenStack.pop(); | ||
| screenStack.peek().show(); | ||
| if (screenStack.isEmpty()) { |
There was a problem hiding this comment.
Calling screenStack.peek() before checking isEmpty() will throw EmptyStackException if popping removes the last screen. The order of these operations should be reversed: check isEmpty() first, then call peek().show() only if the stack is not empty.
| if (handle.exists()) handle.delete(); | ||
| } | ||
|
|
||
| public static void loadInto(Exterminator3618 game, GameScreen gameScreen) { |
There was a problem hiding this comment.
The parameter 'game' is never used.
| public static void loadInto(Exterminator3618 game, GameScreen gameScreen) { | |
| public static void loadInto(GameScreen gameScreen) { |
| return handle.exists() && handle.length() > 0; | ||
| } | ||
|
|
||
| public static void clearSave(Exterminator3618 game) { |
There was a problem hiding this comment.
The parameter 'game' is never used.
| public static void clearSave(Exterminator3618 game) { | |
| public static void clearSave() { |
| public static boolean hasSave(Exterminator3618 game) { | ||
| FileHandle handle = Gdx.files.local(SAVE_FILE); | ||
| return handle.exists() && handle.length() > 0; | ||
| } | ||
|
|
||
| public static void clearSave(Exterminator3618 game) { |
There was a problem hiding this comment.
The parameter 'game' is never used.
| public static boolean hasSave(Exterminator3618 game) { | |
| FileHandle handle = Gdx.files.local(SAVE_FILE); | |
| return handle.exists() && handle.length() > 0; | |
| } | |
| public static void clearSave(Exterminator3618 game) { | |
| public static boolean hasSave() { | |
| FileHandle handle = Gdx.files.local(SAVE_FILE); | |
| return handle.exists() && handle.length() > 0; | |
| } | |
| public static void clearSave() { |
|
|
||
| private SaveManager() { } | ||
|
|
||
| public static void saveGame(Exterminator3618 game, GameScreen gameScreen) { |
There was a problem hiding this comment.
The parameter 'game' is never used.