Character Architecture
Relevant source files
The following files were used as context for generating this wiki page:The character architecture in Souless is a modular, data-driven system designed to manage playable characters within the Phaser 3 framework. It is built around a central CharacterBase class that aggregates several specialized sub-systems for handling logic such as movement, state management, combat, and animations. This design promotes code reuse and simplifies the process of creating new, distinct characters.
Character creation is facilitated by a CharacterFactory, and character properties are defined in a centralized configuration file, characterData.js. This allows developers to define a character's physics, skills, and animations declaratively, separating data from implementation logic. The architecture also includes methods for persisting and retrieving character state, such as health, mana, and stats, through the SaveSlotManager.
Core Components
The character system is composed of several key classes that work together to bring a character to life. The CharacterBase class acts as the central hub, coordinating its various sub-systems.
This diagram illustrates the main classes and their relationships within the character architecture.
CharacterBase Class
The CharacterBase class is the abstract foundation for all playable characters. It is not instantiated directly but is extended by specific character classes (e.g., Monk, Assassin).
Key Responsibilities:
- Initialization: In its constructor, it initializes the character's sprite, physics body, and all associated sub-systems.
- System Aggregation: It holds references to
StateMachine,MovementController,AttackSystem, andAnimationManager. - Core Attributes: Manages fundamental character attributes like
health,maxHealth,mana,maxMana,strength, anddefense. - Game Loop: The
update()method orchestrates the character's behavior on each frame by processing input, updating movement, and managing state transitions. - Combat Logic: Implements core combat methods like
takeDamage(),heal(), andonDeath(). - Data Persistence: Contains
loadSavedResources()andsaveResources()methods to interact withSaveSlotManagerfor saving and loading character-specific data like HP, MP, and stats.
Sources: src/entities/characters/base/CharacterBase.js:10-33, src/entities/characters/base/CharacterBase.js:35-65, src/entities/characters/base/CharacterBase.js:424-448
CharacterFactory
The CharacterFactory is a simple static class used to instantiate character objects. It abstracts the creation logic, allowing scenes to request a character by a string identifier.
The factory's primary method is create(scene, type, x, y, options), which dynamically imports the requested character class, creates a configuration object using CharacterConfigBuilder, and returns a new instance of the character.
Sources: src/entities/characters/base/CharacterFactory.js
Character Instantiation Flow
The process of creating a character is a coordinated effort between the game scene, the factory, and the base class.
The following sequence diagram shows the steps involved in creating a new character instance.
Sub-Systems
CharacterBase delegates specific functionalities to a set of focused sub-systems.
| System | File | Description |
|---|---|---|
StateMachine | StateMachine.js | Manages the character's current state (e.g., idle, walk, attack) and controls animation playback. It can lock the state during actions to prevent interruption. |
MovementController | MovementController.js | Handles all physics-based movement, including walking, running, and jumping (with support for multiple jumps). |
AttackSystem | AttackSystem.js | Manages the logic for basic attacks, including creating and managing the lifecycle of attack hitboxes. Referenced in CharacterBase.js. |
AnimationManager | AnimationManager.js | Responsible for creating all of a character's animations in Phaser based on the configuration from characterData.js. Referenced in CharacterBase.js. |
InputHandler | InputHandler.js | Captures and normalizes player input from the keyboard. Referenced in CharacterBase.js. |
Sources: src/entities/characters/base/CharacterBase.js:142-173
State Management (StateMachine)
The StateMachine is crucial for controlling character behavior and animations. It ensures that only valid state transitions occur and that certain actions, like attacks, complete before others can begin.
Key Features:
- State Transitions: The
changeState(newState)method switches the character's state and plays the corresponding animation. - State Locking: For actions that should not be interrupted (e.g., skills, attacks), the state machine enters a "locked" mode. This lock is typically released upon animation completion or after a timeout, preventing the player from moving or starting another action prematurely.
- Duration-based Unlocking: A
setTimeoutacts as a failsafe to unlock the state if theanimationcompleteevent doesn't fire, preventing the character from getting stuck.
The diagram below shows the primary states and transitions for a character.
Character Configuration
All playable characters are defined as objects within the CharacterData export in src/config/characterData.js. This centralized, data-driven approach allows for easy modification and addition of new characters without changing core game logic.
Each character's configuration object has the following structure:
| Property | Type | Description |
|---|---|---|
sprite | Object | Defines the spritesheet key, frame dimensions, and default scale. |
base | Object | Default spawn coordinates (though typically overridden by the scene). |
physics | Object | Contains physics properties like collisionBox dimensions, walkSpeed, runSpeed, jumpPower, and maxJumps. |
combat | Object | Defines the hitbox for the basic attack and the time window for combos. |
skills | Object | A map of all available skills, including their type, animation, hitbox data, cooldown, and cost. |
animations | Array<Object> | An array defining all animations for the character, including key, frame range, frame rate, and repeat behavior. |
Sources: [src/config/characterData.js:1-581]
Below is a condensed example of the configuration for the 'monk' character.
// Source: src/config/characterData.js:2-25
export const CharacterData = {
monk: {
sprite: {
key: 'monk',
frameWidth: 288,
frameHeight: 128,
scale: 2.0,
},
physics: {
collisionBox: { width: 46, height: 76, offsetX: 268, offsetY: 170 },
walkSpeed: 200,
runSpeed: 350,
jumpPower: 300,
maxJumps: 2,
},
combat: {
attackHitbox: { width: 50, height: 55, offsetX: 35, offsetY: 70, duration: 400 },
comboWindow: 450,
},
skills: {
attack: { /* ... */ },
q_skill: { /* ... */ },
// ... other skills
},
animations: [
{ key: 'idle', frames: { start: 0, end: 5 }, frameRate: 8, repeat: -1 },
{ key: 'walk', frames: { start: 26, end: 31 }, frameRate: 8, repeat: -1 },
// ... other animations
],
},
// ... other characters
};
Sources: src/config/characterData.js:2-25, src/config/characterData.js:58-154, src/config/characterData.js:155-231
Update Loop
The CharacterBase.update() method is called by the scene on every frame. It follows a clear logical flow to update the character's state and position.
This flowchart outlines the logic within the update() method.
Data Persistence
The CharacterBase class integrates with SaveSlotManager to persist and restore critical state. This ensures that a character's health, mana, and stats are maintained between game sessions.
loadSavedResources(): Called during character initialization. It attempts to fetch the character's HP, MP, and stats from the current save slot. A "death loop" is prevented by ensuring the character respawns with at least 10% of their maximum health.saveResources(): This method is called to persist the character's current HP, MP, and stats (strength,defense,maxHealth,maxMana) to the save file.
Sources: src/entities/characters/base/CharacterBase.js:35-65, src/utils/SaveSlotManager.js:456-484
Conclusion
The character architecture in Souless is a robust and extensible system. By separating concerns into distinct sub-systems (MovementController, StateMachine, etc.) and driving character specifics from a central configuration file (characterData.js), the architecture remains clean and easy to maintain. The CharacterBase class effectively orchestrates these components, while the CharacterFactory provides a simple interface for scenes to create new character instances. This modular, data-driven design is well-suited for a game with multiple playable characters, as it significantly reduces the boilerplate code required to implement a new one.