Skip to content

Save and Load System

Yee edited this page Oct 18, 2021 · 11 revisions

Purpose

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.

Design Process

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.

Implementation

Saving

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.

Loading

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.

Key Components

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

Improvements

  • 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

Usage

  • 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.

Issues

  • 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

UML Diagram

Table of Contents

Home

Introduction

Main Menu

Main Game Screen

Gameplay

Player Movement

Character Animations

Enemy Monster Design and Animations

Game basic functionalities

User Testing

GitHub Wiki Tutorial

Game Engine

Getting Started

Documentation

Entities and Components

Service Locator

Loading Resources

Logging

Unit Testing

Debug Terminal

Input Handling

UI

Animations

Audio

AI

Physics

Game Screens and Areas

Terrain

Concurrency & Threading

Settings

Troubleshooting

MacOS Setup Guide

Clone this wiki locally