Saving the Character - UQcsse3200/2024-studio-1 GitHub Wiki
Objective
The objective of this task is to implement a robust saving system that captures and preserves the player's current state during gameplay. This includes the specification of player's health, collected items, and equipped weapons. By serialising this information into a JSON file, the game can easily load and restore player's progress at any point, enhancing the suer's experience by allowing seamless continuation between gaming sessions. This feature is particularly important for integrating pause/unpause functionality and providing save/load mechanism, ensuring that players do not lose progress and can resume gameplay from where they left off.
Implementation Details
Overview
The saving system is composed of three primary components that work together to capture and store the player's state:
- PlayerConfig: Define the data structure for storing player-related information
- PlayerConfigGenerator: Extracts the current state of the player from different components of an entity and construct a
PlayerConfig
object. - SavePlayerGenerator: Manages the process of writing the
PlayerConfig
object to a JSON file using a file loader utility.
These components are designed with modularity and scalability in mind, allowing for easy extension and maintenance as the game's features evolve.
Components Breakdown
1. player.json defined by PlayerConfig
Purpose: This serves as a data model that encapsulates all necessary information about the player's state that needs to be saved. It includes attributes such as health, base attack, collected items and equipped weapons.
Key Attributes:
name
: Name of the playerarmour
: The amount of armourbuff
: The amount of buffcanCrit
: ability to critcritChance
: the amount of critChancehealth
: Represents the player's current health points.baseAttack
: Indicates the player's base attack strength.items
: An array containing specification of all items collected by the player.coins
: The amount of coins player has earneddifficulty
: the difficult that game was being played intimeInvincible
: the amount of invincibilityspeed
: x and y coordinates for speed vector of playermelee
: Specification of the currently equipped melee weapon.maxHealth
: The Mac health a player can havepets
: the name of the petstextureFilename
: the texture filenametextureAtlasFilename
: the texture atlas this player usesranged
: Specification of the currently equipped ranged weapon.equipped
: Indicates the currently active weapon type (e.g. 'melee' or 'ranged')
Implementation Highlights:
- Default Values: The class initializes certain fields with default values to ensure consistency and prevent null references.
- Equality and Hashing: Overridden
equals
andhashCode
methods facilitate comparison and usage in collections, ensuring that twoPlayerConfig
instances can be accurately compared based on their state.
2. PlayerConfigGenerator Class
Purpose:
The PlayerConfigGenerator
is responsible for extracting the current state from the player's entity components and assembling a PlayerConfig
object that accurately reflects the player's status at the time of saving.
Functionality:
- State Extraction: Retrieves data from various components attached to the player entity, such as
CombatStatsComponent
andInventoryComponent
. - Data Transformation: Converts complex data structures (e.g., collections of items) into serializable formats suitable for JSON representation.
- Error Handling: Ensures robustness by handling cases where certain components or data might be missing or null, preventing crashes and data corruption.
Implementation Steps:
- Retrieve Components: Accesses the necessary components from the player entity.
- Extract Data: Gathers current values for health, attack, items, and equipped weapons.
- Transform Items: Converts the list of collected items into a string array using a helper method.
- Handle Optional Weapons: Checks for the presence of equipped melee and ranged weapons and retrieves their specifications if available.
- Assemble Config: Populates a new
PlayerConfig
instance with the extracted data.
3. SavePlayerService Class
Purpose:
SavePlayerService
orchestrates the saving process by utilising the PlayerConfigGenerator
to produce a PlayerConfig
object and then writing this configuration to a JSON file using the FileLoader
utility.
Functionality:
- Initiate Save Process: Provides a simple and straightforward method to trigger the save operation.
- File Writing: Utilizes the
FileLoader
to serialize thePlayerConfig
object into a JSON format and save it to the specified directory. - File Path Management: Determines and manages the file path where the player's state will be saved (
configs/player_save.json
).
Implementation Steps:
- Generate Config: Calls
PlayerConfigGenerator.savePlayerState()
to obtain the current player state. - Write to File: Uses
FileLoader.writeClass()
to serialize and write thePlayerConfig
to a JSON file. - Handle Exceptions: Includes necessary error handling to manage potential issues during the file writing process.
Usage
Implementing and utilizing the saving system in the game involves straightforward steps:
1. Saving the Player's State
To save the player's current state, instantiate the SavePlayerService
and call the savePlayerState
method, passing in the player entity.
Example:
// Assuming 'playerEntity' is your player's Entity instance
SavePlayerService saveService = new SavePlayerService();
saveService.savePlayerState(playerEntity);
This code will create or overwrite the player_save.json
file in the configs
directory with the current state of the player.
2. Loading the Player's State
While loading functionality is not covered in this task, a complementary service can be implemented to read the player_save.json
file and reconstruct the player's state by reversing the process.
Example Output
Upon saving, the player_save.json
file will contain structured data representing the player's state. An example content of the JSON file might look like:
{
health: 60
name: default
items: [
item:medkit
item:medkit
]
speed: {
x: 3
y: 3
}
difficulty: EASY
maxHealth: 100
pets: []
coins: 55
melee: melee:knife
ranged: ranged:shotgun
textureFilename: images/player/player.png
textureAtlasFilename: images/player/player.atlas
}
(NOTE: as items and weapons are not fully implemented, this is based on initial assumptions formed from Collectible Interface)
This JSON structure is easy to read and parse, facilitating straightforward loading and debugging processes.
UML Diagram
Sequence Diagram
Behind the Scenes
Design Considerations
1. Modularity and Separation of Concerns
The saving system is designed with clear separation between data modeling (PlayerConfig
), data extraction (PlayerConfigGenerator
), and data persistence (SavePlayerService
). This modular approach enhances maintainability and scalability, allowing each component to be developed and tested independently.
2. Use of JSON for Serialization
JSON is chosen as the serialization format due to its readability, ease of use, and widespread support. It allows for straightforward mapping between Java objects and a text-based format, facilitating easy debugging and potential manual editing if necessary.
3. Handling Optional Data
The system accounts for scenarios where certain data might be absent. For example, if the player has not equipped a melee or ranged weapon, the corresponding fields are set to empty strings. This prevents null references and ensures the integrity of the saved data.
4. Extensibility
Additional player attributes can be easily incorporated into the PlayerConfig
structure as the game evolves. For example, attributes like player level, experience points, or quest progress can be added with minimal adjustments to the existing system.
5. Error Handling and Robustness
The implementation includes checks to handle cases where certain components might be missing from the player entity. This ensures that the save operation does not fail unexpectedly and provides default values where appropriate.
Testing And Validation
To ensure reliability, comprehensive testing was conducted:
- Unit Tests: JUnit4 testing was used to validate each component individually, ensuring correct data storage.
- Integration Tests: Testing of file writing was unable to be tested in sprint 1 as gds files are not configured properly in test environment and mock testing is not covered yet.
- Edge Cases: Test scenarios with missing data, empty inventories, and maximum capacity inventories were converted to ensure robustness.
To view the comprehensive test plan, please read: Test Plan for Saving Character
Further Reading
For more information and deeper understanding, consider exploring the following resources:
-
LibGDX File Handling:
-
Design Patterns:
Conclusion
The implemented saving system provides a reliable and efficient way to preserve and restore the player's state in the game. Its modular design and use of standard serialization practices ensure that it can be easily integrated and extended as the game develops further. Proper testing and documentation facilitate maintenance and future enhancements, contributing to a robust gaming experience for users.
Note: For implementation assistance or further inquiries, please reach out to @manya-k