-
Notifications
You must be signed in to change notification settings - Fork 0
Save and Load System
Saving and Loading features are crucial to any game which utilises multiple levels, similar to the format of Ragnarok Racer. The importance of this feature lies in its ability to allow players to save their progress, leave the game and return at a later time. In a longer game, being forced to restart over and over again from the beginning will likely discourage the player from finishing. By allowing players to save their progress allows a personalised self-paced experience. Another situation may involve the user having difficulty overcoming certain obstacles, so the save feature can allow them to leave and return to complete the level at another time.
The load screen was designed to align with the Meteor Man start screen for consistency. The background load menu features the circuit pattern and green digital screens. The user will have the opportunity to store four save files, where the green boxes will display brief level and score information.
UPDATE: After further consideration, it was decided that the load screen was no longer necessary. It was removed and replaced with a CONTINUE button, which loads a singular saved file. This background graphic was featured on the settings page instead.
The implementation in saving saw the creation of new class called SaveData
. The constructors of the class can be seen below:
For a single-save file implementation
public SaveData(GdxGame game, Entity player) {
this.game = game;
this.player = player;
saveFile = new File("save/save.txt");
//savePlayerData(player);
}
For a multi-save file implementation
public SaveData(GdxGame game, Entity player, int saveFileNumber) {
this.game = game;
this.player = player;
saveFile = new File(String.format("save/File %d.txt", saveFileNumber));
//savePlayerData(player);
}
This class has 2 methods; a getter method for the player which represents the saved data
and a savePlayerData
method, the code of which is included below
(note this method is called in the construction; the creation of the object automatically saves the player):
public void savePlayerData() {
if (player != null) {
try {
if (saveFile.createNewFile()) {
logger.info("New Save created!");
} else {
logger.info("File already exists");
}
FileWriter fileWriter = new FileWriter(saveFile);
fileWriter.write("Level:");
switch ((game.getScreenType().name())) {
case "MAIN_GAME":
case "RESPAWN1":
fileWriter.write("levelOne");
break;
case "LEVEL_TWO_GAME":
case "RESPAWN2":
fileWriter.write("levelTwo");
break;
case "LEVEL_THREE_GAME":
case "RESPAWN3":
fileWriter.write("levelThree");
break;
}
fileWriter.write("\nSCORE:");
fileWriter.write(String.valueOf(player.getComponent(ScoreComponent.class).getScore()));
fileWriter.write("\nLIVES:");
fileWriter.write(String.valueOf(player.getComponent(LivesComponent.class).getLives()));
fileWriter.write("\nHEALTH:");
fileWriter.write(String.valueOf(player.getComponent(CombatStatsComponent.class).getHealth()));
fileWriter.write("\nSPRINT:");
fileWriter.write(String.valueOf(player.getComponent(SprintComponent.class).getSprint()));
fileWriter.write("\nX:");
fileWriter.write(String.valueOf((int) Math.floor(player.getCenterPosition().x*2)));
fileWriter.write("\nY:");
fileWriter.write(String.valueOf((int) Math.floor(player.getCenterPosition().y*2)));
fileWriter.close();
logger.info("Successfully saved player data");
} catch (IOException e) {
e.printStackTrace();
}
}
}
As can be seen above, the implementation of this method writes the player's data to a file, which can then be read by the load system. Information pertinent to the save/load process is up for debate, and may be changed as progress continues.
The loading implementation saw changes being made to GameArea
and MainMenuActions
; both these classes utilize a BufferedReader to read from a dedicated save file. The implementation for each can be seen below:
private void loadData() {
GdxGame.ScreenType screenType = null;
logger.info("Load game");
try(BufferedReader br = new BufferedReader(new FileReader("saves/saveOne.txt"))) {
String line = br.readLine();
if (br.readLine() == null) {
onStart();
} else {
String[] values = line.split(":");
switch (values[1]) {
default:
case "levelOne":
screenType = GdxGame.ScreenType.MAIN_GAME;
break;
case "levelTwo":
screenType = GdxGame.ScreenType.LEVEL_TWO_GAME;
break;
case "levelThree":
screenType = GdxGame.ScreenType.LEVEL_THREE_GAME;
break;
}
game.setScreenType(screenType, "saves/saveOne.txt");
game.setScreen(GdxGame.ScreenType.LOADING);
}
} catch (IOException e) {
e.printStackTrace();
}
}
As can be seen in the loadData method above from the MAinGameAction
class (which loads from the continue button on the Main Menu screen), the load feature reads saveOne.txt
file, extracting the first line which will tell the reader which level screen to set the game to. This implementation is furthered by the one present in GameArea
class;
protected void loadSave(Entity player, String saveState) {
Entity newPLayer;
if (this.player != null) {
newPLayer = this.player;
} else {
newPLayer = player;
}
int x = 18, y = 12;
try(BufferedReader br = new BufferedReader(new FileReader(saveState))) {
String line = br.readLine();
// parse file to load the floor
while (line != null) {
String[] values = line.split(":");
switch (values[0]) {
case "SCORE":
newPLayer.getComponent(ScoreComponent.class).setScore(Integer.parseInt(values[1]));
case "LIVES":
newPLayer.getComponent(LivesComponent.class).setLives(Integer.parseInt(values[1]));
case "HEALTH":
player.getComponent(CombatStatsComponent.class).setFullHeal();
case "SPRINT":
newPLayer.getComponent(SprintComponent.class).setSprint(Integer.parseInt(values[1]));
case "X":
x = Integer.parseInt(values[1]);
case "Y":
y = Integer.parseInt(values[1]);
}
line = br.readLine();
}
} catch (IOException e) {
e.printStackTrace();
}
GridPoint2 spawnPoint = new GridPoint2(x, y);
spawnEntityAt(newPLayer, spawnPoint, true, true);
newPLayer.getComponent(ProgressComponent.class).setProgress();
}
The above method reads the file from the second line down, extracting each player component that was saved in the save implementation. These components result in the formation of a new player with the information from the previous one, which allows the player to continue from where the game previously saved.
The key components of this implementation are the save file, the player entity, SaveData.java
, SaveData.java
and MainGameActions.java
. These elements all dictated the
- As the entire level system was overhauled, less calls were needed to save player data; as such, the class only needs to be saved a few times
- The save and load system is used by the player to allow their progressed to be maintained across sessions. This is essential for giving player flexibility when player; otherwise, the game would need to be played in one sitting, which is not necessarily feasible for all players.
- Originally, if no save data was present in the file and the player clicked continue the game would crash. This was fixed so that an empty file would still simply create a new game from the Intro
- Player UI
- Popup Menus
- Obstacles
- Boss Enemies
- Progress Tracker
- Checkpoint Design and Functionality
- Score System
- Lives System
- Game Background
- Multiple game-level
- Visual Improvements
- Tutorial Level
- Character Design and Animations
- Character Damage Animations
- Player Animation Functionalities
- Player and Serpent Portal Transition
- Pop-up Menus
- Obstacles
- Lives & Score User Testing
- Buffs & Debuffs
- Buffs & Debuffs redesign
- Obstacle Animation
- Background Design
- Level 2 Background Appearance
- Enemy Monster User Testing
- Level 1 Floor Terrain Testing
- Introduction Screens User Testing
- Character Movement Interviews & User Testing
- Sound user testing
- Level 2 Obstacles and enemy
- Story, Loading, Level 4 and Win Condition Sound Design User Testing
- Giant Bug and Purple Squid animation user testing
- General Gameplay and Tutorial Level User Testing
- Level 4 Terrain User Testing
- Game Outro User Testing