Skip to content

Articles

How to make a farming system in Godot

Grow crops on Grid Placement soil plots with Calendar Time ageing: one growth tick per game day, seasons from the calendar, save-safe harvests.

This recipe builds a Stardew-style farming loop from two plugins with a clean split between them. Calendar Time owns crop ageing: each game day ticks every planted crop forward one growth stage. Grid Placement owns the soil: each tilled plot is a placed object with a stable identity the crop state hangs off. Your game owns the farming rules in the middle: what each crop needs, how long each stage takes, and what harvest pays.

1. Till soil as placed objects

Author a soil plot as a ScenePlacementEntry and place one per farm tile through Grid Placement GRID placement on your GridMap. Each committed plot carries a stable placement_instance_id, which becomes the crop's address: plot 7 holds one crop, and that link survives save and load because the id is stable.

Use CELL placement for soil. Plots must align to tiles, never to free world-space positions, so SMOOTH placement is the wrong mode here. The placement lifecycle (select entry, target cell, preview, confirm, record occupancy) is documented in the Grid Placement 3D Object Placement guide, and the rules your farm needs (costs, protected zones, unlock order) belong in game-authored rules as described in Placement Rules.

2. Age crops one stage per game day

Crops want discrete calendar-date ageing ("takes 3 game-days to mature"), not continuous clock-time ageing ("lasts N game-seconds"). Listen to clock.signal_bus.date_changed and advance one growth stage per tick. Each calendar day fires exactly once no matter how fast the clock runs, so speeding up time makes harvest arrive sooner without ever skipping a stage:

func _ready() -> void:
    clock.signal_bus.date_changed.connect(_on_date_changed)

var growth_stage: int = 0
const MAX_STAGE: int = 3

func _on_date_changed(_current: DateTime, _previous: DateTime) -> void:
    if growth_stage < MAX_STAGE:
        growth_stage += 1
        _update_sprite(growth_stage)

Store per-plot crop state (crop id, stage, watered flag, planted date) in a game-owned dictionary keyed by placement_instance_id. The two ageing models and when to pick each are documented in Ageing: Two Time Models. Ready-made nodes such as DateChangeEvent and DateChangeSpawner cover common date-triggered behaviors if you prefer scene composition over script.

3. Keep farming rules game-owned

The plugins answer when time passes and where plots sit. Your game decides everything agronomic:

  • Which crops exist and how many stages each takes.
  • Whether a stage needs water that day, and what happens without it.
  • Season gating: read the current date from clock.date_time() and refuse out-of-season planting instead of teaching the calendar about parsnips.
  • Harvest payouts and replant behavior.

Put this policy in one game-owned service that subscribes to the clock, the way shop hours do in Advanced: Game-Owned Schedules and World Rules. The service owns decisions and emits game-local signals; Calendar Time never learns that crops exist, and Grid Placement never learns what grows on its plots.

4. Save plots first, crops second

Restore order follows the dependency chain. Restore placed plots through the Grid Placement save path first (stable ids, transforms, occupancy as in Save and Load), then restore the Calendar Time clock through TimeHost.load_state(), then restore the game-owned crop dictionary keyed by plot id. A crop entry whose plot id no longer exists is a removed plot: discard it instead of resurrecting soil the player demolished.

Never persist a duplicate copy of the current date or a derived "ready to harvest" flag. Recompute readiness from the stored planted date and stage on load; derived state goes stale the moment any clock changes without it.

Common mistakes

  • Ageing crops with _process(delta) accumulation. Pausing the clock should pause growth, and only clock-driven ageing does that.
  • Keying crops by node path or array index. Demolish one plot and every index shifts. Key by placement_instance_id.
  • Putting crop concepts into either addon runtime. Both plugins stay crop-free; the game owns the farm.
  • Watering checks that read wall-clock time. Read the calendar date from the clock so save, load, and timescale all behave.
  • Placing soil with SMOOTH placement and snapping sprites by hand. Use GRID CELL placement and let occupancy prevent double-tilling.

Try the demos

Both halves of this recipe run live in the plugin demos: date-driven ageing and day pacing in the Calendar Time demo, and GRID object placement with previews and save-safe identities in the Grid Placement demo. Try each in your browser before combining them.