Files
Dawn-Godot/.claude/docs/overworld.md
T
2026-06-11 22:33:33 -05:00

6.4 KiB
Raw Blame History

Overworld System

Scene structure

OverworldScene (Node3D)          ← always resident, managed by SCENE singleton
  └─ Map (Node3D)                ← emptied and repopulated on each map change

TestMap (Node3D)                 ← typical map root, extends Node3D
  ├─ Player (Entity)             ← movementType = PLAYER, entityId = "player"
  ├─ NPC/object (Entity) ...     ← interactType drives what happens on interact
  ├─ TestMapBase (StaticBody3D)  ← reusable terrain plane (200×200, collision layer 1)
  └─ Camera3D (OverworldCamera)  ← targetNode → Player

Map transitions

Use OVERWORLD.mapChange(path, destinationNodeName) to switch maps.

OVERWORLD.mapChange("res://overworld/map/SomeMap.tscn", "SpawnPoint")

Flow: fade-out begins → map loads on a background thread → when both complete, OVERWORLD.mapChanged fires → OverworldScene clears Map children and instances the new map → fade-in begins. The destinationNodeName is passed with the signal for the new map to use as a spawn point (not yet wired to player placement — see stubs).

Entity

All overworld objects (player, NPCs, items, triggers) are instances of entity/Entity.tscn with different export values.

Export Purpose
entityId:String UUID; use the Inspector button to regenerate
movementType:MovementType NONE (static), DISABLED, or PLAYER (input-driven)
interactType:InteractType What happens when the player presses Interact nearby
dialogueResource:DialogueResource .dialogue file — required for CONVERSATION
dialogueTitle:String Dialogue section to start from (default "start")
oneTimeItem:ItemResource Item granted on interact — required for ONE_TIME_ITEM
cutscene:CutsceneResource Cutscene to run — required for CUTSCENE

Interaction types

InteractType Behaviour
NONE Not interactable
CONVERSATION Runs dialogueResource from dialogueTitle via DialogueAction
ONE_TIME_ITEM Grants oneTimeItem, then frees the entity
CUTSCENE Queues and starts cutscene
BATTLE_TEST Starts a test battle (hardcoded enemy, for dev use)

To add a new interaction type: add a value to Entity.InteractType, then add the matching match branch in EntityInteractableArea.onInteract() (entity/EntityInteractableArea.gd).

Collision layers

Area Layer Mask Purpose
EntityInteractingArea 0 2 Player's reach — detects nearby interactables
EntityInteractableArea 2 0 Entity's surface — detected by other reaches

The asymmetric setup means entities never trigger themselves.

Movement

EntityMovement (a child Node under Components) handles all physics each frame:

  1. Apply gravity if airborne
  2. Apply friction (velocity.x/z *= delta * FRICTION)
  3. If _canMove() and movementType == PLAYER: read input, compute camera-relative direction, set velocity
  4. move_and_slide()

Movement is blocked (_canMove() → false) when UI.dialogueActive, UI.TEXTBOX is open, or UI.GAME_MENU.isOpen().

Camera-relative direction is derived from the active Camera3D's basis — the camera's Y-zeroed and renormalized X/Z axes map input axes to world axes. The entity faces (look_at) the movement direction each frame.

Camera

OverworldCamera orbits around targetNode using yaw/pitch angles. It operates in two modes: CameraMode.FREE (stays where placed) and CameraMode.CENTERED (eases to orbit behind the player using a velocity-based approach).

Export Default Purpose
targetNode:Node3D Node to orbit (assign Player in scene)
pivotOffset:Vector3 (0, 1.2, 0) Orbit point above entity origin
distance:float 7.0 Orbit radius
minDistance:float 2.5 Minimum camera distance (collision won't push closer)
pitchMin/Max:float -80° / 70° Vertical clamp
orbitSensitivity:float 144.0 Degrees/sec at full controller input
orbitAcceleration:float 600.0 Controller ramp speed (deg/s²)
orbitFriction:float 10.0 Controller/centering velocity decay rate
mouseSensitivity:float 0.3 Right-click drag sensitivity
centeredDelay:float 1.0 Seconds of walking + no camera input before switching to CENTERED
centeredFollowRate:float 0.75 Base centering rate; scales up with angular distance from target
centeredMaxFollowRate:float 3.0 Cap on the dynamic follow rate
centeredAcceleration:float 180.0 How fast _centerVelocity ramps up toward target (deg/s²)
centeredMaxYawDiff:float 120.0 If yaw offset exceeds this, centering is suppressed (e.g. player walking toward camera)
centeredPitch:float 30.0 Target pitch in CENTERED mode
collisionMask:int 1 Physics layers the camera avoids; entities are on layer 2 and excluded by default

Mode transitions:

  • FREE → CENTERED: after centeredDelay seconds of player movement with no camera input, or immediately via center_camera (G / LB)
  • CENTERED → FREE: any manual camera input (controller stick or right-click drag)

CENTERED mode behaviour:

  • "Behind" direction is derived from the player's velocity vector (not facing direction), so centering tracks actual movement and eases out naturally as the player decelerates
  • Yaw uses a velocity model: _centerVelocity (deg/s) accelerates toward the target rate via centeredAcceleration and friction-decays via orbitFriction when the player stops — no sudden snap
  • Pitch recenters at 3× the yaw rate via direct lerp
  • If abs(yawDiff) > centeredMaxYawDiff, centering is suppressed and _centerVelocity decays instead

Collision resolution uses a sphere-plane circle intersection so the camera satisfies both minDistance and terrain clearance simultaneously.

Adding a new map

  1. Create a new scene (Node3D root) in overworld/map/
  2. Add Entity instances, set interactType and relevant exports in the Inspector
  3. Instance TestMapBase (or your own terrain) as a child
  4. Add a Camera3D with OverworldCamera script; set targetNode to the Player entity
  5. Add a Player entity with movementType = PLAYER and entityId = "player"
  6. Switch to it with OVERWORLD.mapChange("res://overworld/map/YourMap.tscn", "")