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#
| System | State | Where |
|---|---|---|
| Player movement | Works — WASD, run, gravity, turns to face direction | entities/player/player_controller.gd |
| State machine | Works — generic, nestable (StateMachine extends State) | scripts/fsm/ |
| Grid | Works — world↔cell at 1 m, floori correct across the origin | scripts/world/farm.gd |
| Planting | Works — data-driven, one crop per cell | farm.gd, entities/crop/ |
| Growth | Works — days_grown / days_per_stage → stage → mesh swap | farm.advance_day() |
| Debug registry | Works — release-gated key bindings | scripts/debug/debug_actions.gd |
| Camera | Works — Phantom Camera follow | scenes/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.tresper crop type) — the definition:id,display_name,days_per_stage, and astage_meshesarray. Immutable, shared by every plant of that kind.Plot(RefCounted, one per planted cell) — the state: whichCropData, currentstage,days_grown. Mutable.Crop(Node3Dscene, 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().
| Key | Action |
|---|---|
| F1 | Advance one day (grows every planted crop) |
| F2 | Clear 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.