Interaction
The mouse state machine driving click-to-move/attack/loot and targeting, plus the orbit camera.
Objects in the world don’t decide what a click means themselves — they just report input events
to MouseManager, which delegates to whichever MouseState is currently active. This keeps
“what does clicking the ground do right now” in one place instead of scattered across every
clickable node.
MouseManager
src/Manager/mouse_manager.gd is an autoload holding current_state: MouseState. Clickable things report discrete events —
object_clicked, on_object_hover, on_ground_input_event — and any code that needs “what’s
under the mouse right now” can call get_floor_hit_at_mouse() (a raycast filtered to the
"floor" group). It also owns entity selection (select_entity, entity_selected signal) and
swaps the OS cursor for the active targeting mode.
MouseState subclasses
Defined under src/Manager/MouseState/, an abstract base (mouse_state.gd) defines enter/exit/process_state/handle_object_clicked/
handle_ground_input_event/handle_right_click/handle_cancel. Three concrete states exist:
MouseStateDefault— the everyday state: click the ground to move, click aBestiaVisualto attack it, click anItemVisualto loot it.MouseStateSkillTargeting— entered when the player picks a skill that needs a target. Branches on the skill’starget_type:GROUND/AOE_GROUNDshows a floor-following indicator sized to the skill’saoe_radius;ENEMY/FRIENDLYsnaps to the closest matchingEntitywithinSettingsManager.skill_target_snap_distance(holding Shift inverts the friend/enemy filter). Confirming callsConnectionManager.activate_skill(skill_id, level, position, [entity_id]).MouseStateItemTargeting— the equivalent flow for a scripted item (ItemUse. on_targeting_click), entered fromItemResource.use_itemwhen the item needs a target instead of firing immediately.
Worked example: click to attack
sequenceDiagram participant Area as BestiaVisual Area3D participant MM as MouseManager participant State as MouseStateDefault participant CM as ConnectionManager participant Net as BnetSocket Area->>MM: object_clicked(self, event, position) MM->>State: handle_object_clicked(mgr, object, event, position) State->>State: object is BestiaVisual → select_entity(object) State->>CM: send_attack_entity(entity_id, 0, 1) CM->>Net: SendMessage(AttackEntityCMSG)
- Input: the target’s
Area3Dpicks up the click via Godot physics picking (bestia_visual.gd):func _on_area_3d_input_event(_camera, event, event_position, _normal, _shape_idx) -> void: MouseManager.object_clicked(self, event, event_position) - Dispatch:
MouseManager.object_clicked()forwards to the active state’shandle_object_clicked. - Default-state handling (
mouse_state_default.gd): confirms the click was thenormal_actioninput action, that the target isn’t a plainInteractable, and if it’s aBestiaVisual, selects it and fires the attack:if object is BestiaVisual: mgr.select_entity(object) ConnectionManager.send_attack_entity(object.get_bestia_entity_id(), 0, 1) - Send:
ConnectionManager.send_attack_entity()builds anAttackEntityCMSGand sends it — see Networking for how that reacheszone-server.
Skill-based attacks follow the same shape through MouseStateSkillTargeting instead of the
default state, ending in ConnectionManager.activate_skill() /ActivateSkillCMSG rather than
send_attack_entity().
MouseMarker
src/Game/MouseMarker/mouse_marker.gd is the always-visible ground-tile cursor, snapped to the 1m tile under the mouse. Only shown in the
default mouse state — targeting states replace it with their own indicator
(AOECastIndicator/EntitySnapIndicator, see World & Terrain for
how the underlying floor raycast works and UI Overview for VFX like
the AOE indicator).
Camera
A third-person orbit camera under src/Game/SpringArmCamera/, split into two scripts:
camera_spring_arm.gdrotates aNode3D(yaw/pitch, clamped) while the right mouse button is held and dragged past a small pixel threshold. A clean press-and-release without dragging past that threshold instead firesMouseManager.right_clicked(context menu) and cancels any active skill/item targeting rather than rotating the camera. Scroll wheel adjustsSpringArm3D. spring_lengthfor zoom.camera_follow.gd, on the actualCamera3D, lerps its position toward the spring arm’s position every frame — decoupling render-smoothing from the arm’s own collision-driven length changes (the spring arm itself can snap shorter instantly when it hits geometry; the camera node smooths that out visually).
Last updated 19 Sep 2026, 01:24 +0200 .