5.5 KiB
Dawn Godot — Claude Code Guide
Project
Godot 4.4 RPG prototype, GDScript only. Viewport 480×270 (scaled 3× to 1440×810), GL Compatibility renderer.
Code Style
Naming
- camelCase for variables and functions:
fighterMap,startBattle(),getFullParty() - PascalCase for class names, enums, and enum values:
BattleFighter,FighterTeam,ALLY - SCREAMING_SNAKE_CASE for constants and static data:
CUTSCENE_CONTINUE,ITEM_DATA,PARTY_JOHN - No trailing underscores on private methods — use a leading underscore only for internal helpers that shouldn't be called externally:
_onConversationInteract(),_applyGravity()
Formatting
- 2-space indent (not 4, not tabs)
- Type annotations on all variable declarations and function signatures:
var health:int,func damage(amount:int, crit:bool) -> void: - No space between variable name and type:
var foo:intnotvar foo : int - Blank lines between logical sections within a file; comment headers (
# Health,# Signals) to label groups
General Rules
assert()for invariants and preconditions — prefer it over silent failuresparams:Dictionarypattern for multi-argument constructors/callables; access with.get('key', default)or.has('key')guardsmatchfor enum dispatch;if/elifchains for non-exhaustive checkscontinue/ earlyreturnto reduce nesting rather than deep else-branches- Avoid long inline lambdas; extract named static functions where logic is non-trivial
Architecture
Singletons (Autoloads)
All globally accessible by their handle. Never instantiate these — access via the global name.
| Handle | Role |
|---|---|
SCENE |
Current scene state; call SCENE.setScene(SceneSingleton.SceneType.X) to switch |
TRANSITION |
Fade in/out; TRANSITION.fade(FadeType, duration, color) |
BATTLE |
Battle state and fighter map |
PARTY |
Party members (PARTY.getFullParty()) and PARTY.BACKPACK inventory |
OVERWORLD |
Map switching with threaded loading |
COOKING |
Cooking mini-game lifecycle |
SAVE |
Persistence (stub) |
QUEST |
Quest management (stub) |
CUTSCENE |
Cutscene global (stub) |
UI |
Root UI accessor — UI.TEXTBOX, UI.DEBUG_MENU |
Scene Graph
RootScene (Node3D)
└─ overworld / battle / cooking / initial ← one shown at a time
RootUI (Control, always visible)
└─ VNTextbox, DebugMenu
RootScene listens to SCENE.sceneChanged and shows/hides the appropriate sub-tree.
Cutscene / Event Queue
Cutscene is the universal sequencing engine. It holds an Array[Dictionary] queue; each entry has a "function": Callable plus arbitrary data keys.
Return codes from a callable:
Cutscene.CUTSCENE_CONTINUE— advance to next itemCutscene.CUTSCENE_END— stop the cutscene- An integer index — jump to that position
Position constants when adding:
Cutscene.CUTSCENE_ADD_END— append (default)Cutscene.CUTSCENE_ADD_NEXT— insert immediately after current
Callable pattern — every action class exposes a pair:
# The actual callable (static, takes params:Dictionary, returns int)
static func myCallable(params:Dictionary) -> int:
...
return Cutscene.CUTSCENE_CONTINUE
# Factory that builds the dictionary for addCallable()
static func getMyCallable(arg) -> Dictionary:
return { "function": myCallable, "myArg": arg }
Data Registry Pattern
Static registries (Item, Recipe) follow this pattern:
enum Id { NULL, ... }— typed identifierstatic var DATA:Array = []— indexed by Id valuestatic func define(params) -> Dictionary— called at class load to populateDATAstatic var FOO = define({...})— registers the entry as a static varstatic func get*(id) -> *— typed accessors
_init(params:Dictionary) Pattern
Non-Node data classes (BattleFighter, BattleDecision, ItemStack, etc.) use a single params dictionary constructor with .get('key', default) for optional fields.
System Conventions
Battle
- Fighters live in
BATTLE.fighterMap: Dictionary[BattlePosition, BattleFighter] BattleFighteris pure data (no Node);BattleFighterSceneis the 3D visual- Battle flow is driven by
BattleCutsceneAction.playerDecisionCallablewhich loops viaCUTSCENE_ADD_END - New moves go in battle/fighter/BattleMove.gd as static presets; add corresponding
perform()logic in battle/action/BattleMove.gd
Entities (Overworld)
- All interactable world objects extend
Entity(CharacterBody3D) - Interaction type is set via
@export var interactType:InteractType - Interaction routing lives in
EntityInteractableArea.onInteract()— add newInteractTypevalues there
Items
- Register new items in item/Item.gd — add to
Idenum and callitemDefine() - Item ID order in the enum must match insertion order into
ITEM_DATA
UI
UI.TEXTBOX.setTextAndWait(text)— show dialogue and await player dismiss (useawait)- Movement is blocked automatically when
UI.TEXTBOXis visible (EntityMovement._canMove()checks this) - Menus extend
ClosableMenufor open/close/toggle +closed/openedsignals
What's Incomplete / Stub
Save.gd— no actual serialization yetQuest.gd— empty singletonCutsceneSingleton.gd— minimal stubBattleFighter.getAIDecision()— always returnsnullBattleDecision.execute()— stubBattleItem.perform()— stubCookingScene.tscn— placeholder UI onlyPause.gd— commented-out logic