velven

A restart button should reset the whole round

The second round is a good test of a game's architecture. If the music doubles, enemies survive or a timer accelerates, the first round probably created things without deciding who would clean them up. My preference is to make a round disposable: starting a new one should construct known state, not patch whatever the last one left behind.

A round has a lifecycle: Start → Playing → Won / Lost → Dispose. Restart creates a fresh round after cleanup.
Restart creates a fresh round after cleanup.

Separate the round from the session

I would divide state into three lifetimes. Application state holds account or device preferences. Session state holds progress you want to keep while playing. Round state holds health, enemy instances, collectible IDs and the running clock. A restart replaces the third group.

This distinction matters when AI is adding features in separate passes. A volume setting can end up beside the score because both happened to be numbers in the first prototype. Ask the assistant to classify fields by lifetime before it changes the reset function.

Make transitions own their side effects

A state transition should have an explicit exit path. Leaving playing cancels round timers, releases subscriptions and clears held input. Entering playing creates exactly one set of those resources. Keep that ownership in a small controller rather than scattering starts and stops across interface buttons.

The important contract is attachRoundSystems returning cleanup for everything it attached. That includes event listeners and asynchronous callbacks, not only visible enemies. If an old network or asset callback can still mutate the new round, give each round an identity and ignore results belonging to a disposed one.

// Illustrative ownership pattern; connect to your engine's lifecycle.
let disposeRound = () => {};
function startRound() {
  disposeRound();
  clearHeldInput();
  const round = createInitialRound();
  disposeRound = attachRoundSystems(round);
  showPlaying(round);
}

Test the transitions you hope nobody tries

Run start → pause → restart → pause → resume. Hold a movement key while restarting. Trigger victory at the same moment as the timer reaches zero. Decide which terminal state wins instead of allowing whichever callback happens to run last.

I would make these transition tests deterministic before adding more gameplay. You can keep the simulation's clock under test control and advance it explicitly, rather than sleeping and hoping a timer fires. The exact mechanism depends on the engine, but the ownership rule stays the same.

A restart audit is also a useful request for an assistant reviewing unfamiliar code: list each round-owned resource, its creation point and its cleanup point. Any blank cell is worth investigating. You can usually fix the underlying lifecycle once, which is far better than accumulating a new special-case reset whenever a feature arrives.

Watch & learn

Companion videos from other creators. Interfaces and versions may differ from your project.

Your first 2D game in Godot 4GDQuest · YouTubeA companion beginner project using Godot 4; follow the version used in the video.

Sources & further reading

Made something playable?

Share your game, world or experiment with people exploring what AI can help create.

Add your space to Velven