doc 10 of 10

Alien Farming Game — Code Reference

A 3D farming prototype in Godot 4.7. Playable today: a character driven by a node-based state machine walks a grid, faces where it moves, and plants data-driven crops that grow through stages on a debug day-tick. Everything is primitive meshes — the systems are real, the art is not.

What runs today#

SystemStateWhere
Player movementWorks — WASD, run, gravity, turns to face directionentities/player/player_controller.gd
State machineWorks — generic, nestable (StateMachine extends State)scripts/fsm/
GridWorks — world↔cell at 1 m, floori correct across the originscripts/world/farm.gd
PlantingWorks — data-driven, one crop per cellfarm.gd, entities/crop/
GrowthWorks — days_grown / days_per_stage → stage → mesh swapfarm.advance_day()
Debug registryWorks — release-gated key bindingsscripts/debug/debug_actions.gd
CameraWorks — Phantom Camera followscenes/game.tscn

Not started: harvest, inventory, tools, a real day cycle, saving. Shapeshift and Synthesize exist as empty state stubs, reserved for later.

Architecture, in one idea#

Truth lives in data; the scene is a view of it. A planted cell is an entry in a Dictionary[Vector2i, Plot] — that dictionary is the authority. The Crop node you see is built from it and can be thrown away and rebuilt without losing anything. Saving, when it comes, is walking the dictionary; it never has to ask a mesh what it currently looks like.

This split runs through every system and is the reason a new plant is a data file, not code.

The crop system#

Three types, cleanly separated:

  • CropData (Resource, one .tres per crop type) — the definition: id, display_name, days_per_stage, and a stage_meshes array. Immutable, shared by every plant of that kind.
  • Plot (RefCounted, one per planted cell) — the state: which CropData, current stage, days_grown. Mutable.
  • Crop (Node3D scene, one per planted cell) — the view: shows the mesh for its plot's current stage.

The rule that keeps them honest: if two plants of the same kind could ever disagree about it, it belongs on Plot, not CropData. days_grown is per-plant state; days_per_stage is a property of the type. Adding a crop — carrot.tres was added with zero code changes — proves the pipeline is data-driven.

The state machine#

A node-based FSM in scripts/fsm/. StateMachine extends State, so a machine can nest inside another machine — the hierarchy is available without a second class. Registration keys each child State by its node name; a state requests a move with machine.travel(&"WalkState"), and an unknown name bubbles up to the parent machine.

Current states: Idle, Walk, Run, UseItem (plants the cell the player faces). The player controller exposes input_direction(), move(), and target_position(); states call those rather than reaching into physics themselves.

Two traps this codebase already hit, both recorded: a state node with no script is silently skipped at registration, and renaming a state node renames its transition key — the name is the API.

Debug#

Debug is an autoload registry. Systems bind their own keys next to the code those keys drive (Farm owns "advance a day"), and every binding is inert in a release export via OS.is_debug_build().

KeyAction
F1Advance one day (grows every planted crop)
F2Clear all crops

Conventions#

Godot 4.7, GDScript with static typing, tabs. Node wiring by @export + inspector, not get_node() paths. Input actions as &"StringName" literals. .godot/, *.import, and addons/ are generated or vendored — never hand-edited.