9.8 KiB
Per-Level GameState Architecture Refactoring Plan
Overview
Convert the current autoload-based global GameState system to a per-level instance-based architecture where each level scene has its own LevelGameState node with references to catalogues (resources) and its own save file.
Current Architecture (To Be Replaced)
project.godot autoloads:
├─ BuffDatabase (autoload)
├─ CurrencyDatabase (autoload)
├─ GameState (autoload)
└─ PrestigeManager (autoload)
tiny_sword.tscn:
└─ Uses GameState autoload directly from any node
Problems:
- Level scenes implicitly depend on global autoloads
- Cannot have multiple levels with independent state
- Catalogues auto-discover from filesystem (tightly coupled)
- Save file path is hardcoded globally
Target Architecture
tiny_sword.tscn (root node)
├─ LevelGameState (Node)
│ ├─ @export var currency_catalogue: CurrencyCatalogue (Resource)
│ ├─ @export var buff_catalogue: BuffCatalogue (Resource)
│ ├─ @export var goal_catalogue: GoalCatalogue (Resource)
│ ├─ @export var save_file_path: String = "user://tiny_sword_save.json"
│ └─ PrestigeManager (Node child)
│ └─ @export var game_state: LevelGameState
├─ World (Node2D)
│ ├─ GoldMine (CurrencyGenerator)
│ │ └─ @onready var game_state = find_parent("LevelGameState")
│ └─ ...other generators
└─ UI (Control)
├─ CurrencyTile (each: @export var game_state: LevelGameState)
└─ ...
Catalogue Resources
All catalogues are Resource subclasses with explicit arrays populated in the editor:
CurrencyCatalogue
class_name CurrencyCatalogue
extends Resource
@export var currencies: Array[Currency] = []
func get_currency_by_id(id: StringName) -> Currency
func get_all_ids() -> Array[StringName]
Location: res://docs/gyms/tiny_sword/currencies/tiny_sword_catalogue.tres
BuffCatalogue
class_name BuffCatalogue
extends Resource
@export var buffs: Array[GeneratorBuffData] = []
func get_buff_by_id(id: StringName) -> GeneratorBuffData
func get_all_ids() -> Array[StringName]
func get_buffs_for_generator(gen_id: StringName) -> Array[GeneratorBuffData]
Location: res://docs/gyms/tiny_sword/buffs/tiny_sword_buffs.tres
GoalCatalogue
class_name GoalCatalogue
extends Resource
@export var goals: Array[GoalData] = []
func get_goal_by_id(id: StringName) -> GoalData
func get_all_ids() -> Array[StringName]
Location: res://docs/gyms/tiny_sword/goals/tiny_sword_goals.tres
Implementation Steps
Step 1: Create Catalogue Classes
Files to create:
core/currency_catalogue.gdcore/buff_catalogue.gdcore/goal_catalogue.gd
Purpose: Resource classes that hold arrays of game data and provide lookup methods.
Step 2: Create LevelGameState
File to create: core/level_game_state.gd
Key changes from current game_state.gd:
| Current | New |
|---|---|
extends Node (autoload) |
extends Node (instance) |
CurrencyDatabase.get_known_currency_ids() |
currency_catalogue.get_all_ids() |
BuffDatabase calls |
buff_catalogue calls |
Static file_name |
@export var save_file_path: String |
| Filesystem goal discovery | goal_catalogue.goals array |
| Buff auto-registration from BuffDatabase | Buff registration from buff_catalogue |
New properties:
@export var currency_catalogue: CurrencyCatalogue
@export var buff_catalogue: BuffCatalogue
@export var goal_catalogue: GoalCatalogue
@export var save_file_path: String = "user://level_save.json"
Methods to port:
- All currency methods (
add_currency,spend_currency,get_currency_amount_by_id, etc.) - All generator methods (
register_generator,get_generator_state, etc.) - All buff methods (
register_buff,get_buff_level,get_effective_multiplier, etc.) - All goal methods (
register_goal,is_goal_completed,evaluate_all_goals, etc.) - Save/load (
save_game(),load_game()) with configurable path - Prestige methods (
reset_for_prestige,emit_currency_changed_for_all)
Step 3: Convert PrestigeManager
File to modify: core/prestige/prestige_manager.gd
Changes:
- Remove autoload registration from
project.godot - Add
@export var game_state: LevelGameState - Change
/root/PrestigeManagerlookup to use direct reference - Make it a child node of
LevelGameStatein scenes
Key fix in _get_prestige_multiplier():
# Old: get_node_or_null("/root/PrestigeManager")
# New: Direct access through injected reference
Step 4: Update CurrencyGenerator
File to modify: core/generator/currency_generator.gd
Changes:
- Replace all
GameState.calls withgame_state.calls - Add
@onready var game_state: LevelGameState = find_parent("LevelGameState") - Update signal connections:
game_state.currency_changed.connect(...) - Update
_get_prestige_multiplier()to use injected reference
Step 5: Update CurrencyTile
File to modify: currency_label.gd
Changes:
@export var game_state: LevelGameState # ← NEW
func _ready() -> void:
if game_state == null:
push_error("CurrencyTile '%s' missing game_state reference" % name)
return
_currency_id = game_state.get_currency_id(currency)
# ... rest of initialization
Step 6: Update project.godot
Remove autoloads:
# REMOVE these lines:
# BuffDatabase="*res://core/buff_database.gd"
# CurrencyDatabase="*res://core/currency_database.gd"
# GameState="*uid://d2j7tvlgxr2jp"
# PrestigeManager="*res://core/prestige/prestige_manager.gd"
Step 7: Update tiny_sword.tscn
Add LevelGameState node:
[node name="LevelGameState" type="Node" parent="."]
currency_catalogue = ExtResource("currency_catalogue_tres")
buff_catalogue = ExtResource("buff_catalogue_tres")
goal_catalogue = ExtResource("goal_catalogue_tres")
save_file_path = "user://tiny_sword_save.json"
Add PrestigeManager as child:
[node name="PrestigeManager" type="Node" parent="LevelGameState"]
game_state = NodePath("..") # Reference to LevelGameState
Wire up children:
- Each
CurrencyTile:game_state = LevelGameState - Each
CurrencyGenerator: auto-discovers viafind_parent()
Step 8: Create Catalogue Resources
Create .tres files via Godot editor:
-
res://docs/gyms/tiny_sword/currencies/tiny_sword_catalogue.trescurrencies = [gold, worker, food, wood]
-
res://docs/gyms/tiny_sword/buffs/tiny_sword_buffs.tresbuffs = [all relevant buffs for this level]
-
res://docs/gyms/tiny_sword/goals/tiny_sword_goals.tresgoals = [all relevant goals for this level]
Step 9: Clean Up
Files to delete:
core/currency_database.gd(replaced byCurrencyCatalogue)core/buff_database.gd(replaced byBuffCatalogue)core/game_state.gd(replaced bylevel_game_state.gd)
File Summary
Files to Create (4)
| File | Purpose |
|---|---|
core/currency_catalogue.gd |
Resource with Array[Currency] + lookup methods |
core/buff_catalogue.gd |
Resource with Array[GeneratorBuffData] + lookup methods |
core/goal_catalogue.gd |
Resource with Array[GoalData] + lookup methods |
core/level_game_state.gd |
Complete GameState implementation without autoload dependencies |
Files to Modify (4)
| File | Changes |
|---|---|
core/prestige/prestige_manager.gd |
Remove autoload, add @export var game_state |
core/generator/currency_generator.gd |
Add find_parent() lookup, replace GameState. |
currency_label.gd |
Add @export var game_state: LevelGameState |
project.godot |
Remove 4 autoloads |
Files to Delete (3)
| File | Reason |
|---|---|
core/currency_database.gd |
Replaced by CurrencyCatalogue |
core/buff_database.gd |
Replaced by BuffCatalogue |
core/game_state.gd |
Replaced by level_game_state.gd |
Scene Updates
| File | Changes |
|---|---|
tiny_sword.tscn |
Add LevelGameState node, add PrestigeManager child, wire catalogues |
Create .tres resources |
tiny_sword_catalogue.tres, tiny_sword_buffs.tres, tiny_sword_goals.tres |
Benefits
- Decoupled levels: Each level can exist independently without global dependencies
- Multiple levels: Can support multiple levels with independent state (future feature)
- Explicit dependencies: All dependencies are visible in the inspector
- Resource-based catalogues: Content is defined as editor-authorable resources
- Per-level saves: Each level has its own save file path
- Better testing: Easier to test individual levels in isolation
Risks and Considerations
- Signal connections: UI components must be manually wired to
LevelGameStateinstance - PrestigeManager access: Must ensure proper reference injection
- Catalogue population: Catalogue resources must be created and populated in editor
- Migration: Existing save files may need migration logic
Implementation Order
- Create catalogue classes (3 files)
- Create level_game_state.gd (the core refactor)
- Update prestige_manager.gd (remove autoload dependency)
- Update currency_generator.gd (add game_state injection)
- Update currency_label.gd (add game_state export)
- Update project.godot (remove autoloads)
- Create catalogue resources (.tres files via editor)
- Update tiny_sword.tscn (wire everything together)
- Delete old files (cleanup)
Status
- Step 1: Create catalogue classes
- Step 2: Create level_game_state.gd
- Step 3: Update prestige_manager.gd
- Step 4: Update currency_generator.gd
- Step 5: Update currency_label.gd
- Step 6: Update project.godot
- Step 7: Create catalogue resources (manual editor work)
- Step 8: Update tiny_sword.tscn (manual editor work)
- Step 9: Delete old files