Open-source project
chickensoft-games/GameDemo avatar
chickensoft-games/GameDemo

Chickensoft GameDemo: a third-person Godot game used as a C# architecture testbed

The Chickensoft Game Demo — a fully tested, third-person 3D game built with Godot and C#. Now with saving and loading!

634 stars59 forksC#MIT

At a glance

What is it?
The Chickensoft GameDemo is a third-person 3D game in Godot with C#, published not as a game but as a working example of an opinionated engine architecture: dependency injection through the scene tree, two phase node initialisation, hierarchical state machines for logic, and unit tests that run in CI. The gameplay is a small collect and bounce level, and the architecture is the point.
Who is it for?
The GameDemo suits a Godot developer with C# who wants to see a fully unit tested scene tree in practice, and a studio considering Chickensoft's packages who wants to judge the boilerplate cost against the testability before adopting the architecture wholesale. It does not suit someone looking for a game, since the level is a demo, and it does not suit a project that does not want an opinionated architecture, since the packages pull in a consistent set of decisions.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 2 days ago.
What is it written in?
Mainly C#, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The game is small on purpose, the architecture is not

The Chickensoft GameDemo is described as a fully tested, third-person 3D game built with Godot and C#, and the current headline is that it now has saving and loading with full game state preservation.

The gameplay is a small collect and bounce level. You move around with WASD, you bounce with the spacebar, holding the spacebar while falling makes you bounce as soon as you hit the ground, you jump on the mushrooms, you collect all the coins, and you are not to fall off the world. That is the whole game, and it is a deliberate choice.

The readme explains the framing: the project is a result of two and a half years of learning, the other dozen or so open source Chickensoft packages, and a lot of help and support from the Godot community. The Chickensoft packages are the subject, and the game is the specimen.

The reason is stated in the architecture section. Following a highly opinionated architecture has a few advantages: the code is easier to learn, fully testable, more consistent, and other people can learn the architecture rules and get up to speed fairly quickly. The author is honest about the cost in the same paragraph, noting that it results in a little bit of boilerplate, but that this is true whenever you make code modular enough to be fully unit tested, and that the benefits outweigh the extra boilerplate especially for studios with more than one person contributing.

That last point is the real argument. A single author can hold an architecture in their head. A team cannot, and a test suite is what lets a team change a game without fear. The demo exists to show that the framework makes the testable version the easy version.

AutoInject resolves dependencies by walking up the scene tree

The mechanism that makes the scene tree testable is AutoInject, and it is the part of the architecture with the most interesting design.

Node dependencies are resolved in a tree-based manner by looking through a node's ancestors until it finds one that provides what the dependent node is looking for. Supplying dependencies in a tree-based manner mirrors Godot's own node-based approach, and it solves the age-old problem of needing something higher up in the scene tree before the parent node has had a chance to initialize it. Under the hood, AutoInject temporarily subscribes to the parent and calls the child back once the dependency is available.

That subscription trick is what makes it work. The alternative in Godot is that the child asks the parent for a dependency in its own initialisation, which fails when the parent has not run its own initialisation yet. AutoInject turns that ordering problem into a subscription, so the child waits rather than guessing.

The testing payoff is stated directly. AutoInject provides mechanisms for faking dependencies easily in unit tests, and GodotNodeInterfaces provides generated interfaces and adapters for every object in GodotSharp that extends GodotObject, which lets those objects be accessed in a way that is easy to mock. GodotNodeInterfaces also offers alternative methods for manipulating a node's children that accept interfaces rather than concrete node types, so a test can use a fake scene tree for a node that has to be attached to the real one during play.

The one requirement is that GodotNodeInterfaces has to be in the project even if you never call it directly, because AutoInject depends on it. The readme notes that most games will never need to use it, and that this game just happens to be fully unit tested.

Two phase initialisation and the IsTesting flag

The second piece is the lifecycle, and it is a smaller idea with a large effect on testability.

Two-phase initialisation, accomplished with AutoInject's Enhanced Lifecycles, splits a node's initialisation into two steps. The first phase allows the node to create the values it wants to use, and the second phase allows it to consume those values for initial setup.

The split exists because of the ordering problem again, but at a different level. A node that both constructs its collaborators and immediately uses them cannot be tested with fakes, because the construction happens inside the same method as the use. Split it, and the test can supply a fake at the boundary.

The mechanism for that is an ordinary property. AutoInject adds a property to each node script it is used on, named IsTesting, which allows the node to discern whether it is running in-game or in a test environment, and which skips the first phase during testing so that mock or fake objects can be used instead.

This is a pattern worth stealing even if you never use the packages. A boolean that tells a constructor whether it is under test, combined with an initialisation split into create and consume phases, is a small amount of discipline that makes a scene tree testable at all. Most Godot codebases do not have it, which is why most Godot codebases do not have unit tests.

LogicBlocks separate state from presentation

For anything with state, the project uses a hierarchical state machine called a logic block, and it is the part of the architecture that most directly changes how you write gameplay code.

The state is separated from the presentation by creating a hierarchical state machine for the node and consuming it through a simple binding system, ensuring the view is always synchronised with its state. LogicBlocks also generates pictures of the state diagrams, which the readme calls reason enough on its own, and the state diagrams are in the project.

The argument for the approach is consistency. Using LogicBlocks for all logic everywhere sometimes results in views with very simple states, but the additional effort makes it very consistent: if a node does anything more than simple visualisation, you know the details are in its logic block. Separating visualisation from state also makes it easy to test the logic block in isolation, which is the same payoff as the two-phase initialisation, applied to behaviour rather than to construction.

The domain-driven design layer is what connects the logic blocks together. Following domain-driven design means the logic blocks can share and use domain repository objects and react to changes in the domain model, keeping all the active logic blocks synchronised to changes occurring across the entire game, in a way similar to an event bus model.

Underneath that sits a lightweight reactive layer. The Collections package provides simplified rx-style observable objects to accomplish reactive and easy to maintain game domain models. The API is similar to an observable with a few tweaks for ergonomics and a simplified implementation to keep it lightweight using C# vanilla events, which is a deliberate decision to avoid pulling in a reactive extensions dependency.

The tooling around the tests is the most telling part

The project demonstrates the architecture with a test suite, and the tools around that suite are where the engineering effort is visible.

GoDotTest is the test runner integration, and it is designed for use with CI/CD as well as local testing, with compatibility with VSCode's debug launch profiles so that tests can be hooked into and debugged during development. That compatibility point is the one that decides whether a test suite gets used, because a test you cannot step through is a test you skip when it fails.

The repository layout backs this up. There is a coverage/ directory at the top level and both coverage.ps1 and coverage.sh scripts, which run on PowerShell and on a Unix shell respectively. There is a CodeAnalyzers.ruleset, an .editorconfig, a .idea/ directory and a .vscode/ directory, a cspell.json for spell checking, a renovate.json for dependency updates, a nuget.config, a global.json, a project.godot file, a GameDemo.csproj and GameDemo.sln, and separate src/ and test/ directories.

The node mixins are the other piece worth naming. Introspection allows additional code to be added to node scripts at build time using C# source generation, which makes up for the lack of mixin support in C#. The readme describes mixins as a data-driven technique, and says that combining data-driven techniques with object-oriented programming lets you get the best of both worlds, making the code exceptionally clean and easy to maintain.

A consistent, automatically enforced coding style is delivered through the Chickensoft EditorConfig tooling, and the readme describes that as consistent and automatically enforced rather than merely recommended.

Two practical notes close the setup out. The readme insists you run the git lfs pull command to resolve the binary files, which is the kind of instruction that saves a confusing first build. And you need the Godot C# development environment set up, the current .NET LTS release and a stable Godot version should work, and you have to open the project in Godot once before you attempt to launch it from your code editor.

Editorial conclusion

The GameDemo suits a Godot developer with C# who wants to see a fully unit tested scene tree in practice, and a studio considering Chickensoft's packages who wants to judge the boilerplate cost against the testability before adopting the architecture wholesale. It does not suit someone looking for a game, since the level is a demo, and it does not suit a project that does not want an opinionated architecture, since the packages pull in a consistent set of decisions. Before cloning it, run the git lfs pull command the readme insists on to resolve the binary files, open the project in Godot once before launching it from your editor, and read the coverage scripts, which are the clearest statement of what the project considers its deliverable.

Frequently asked questions

What is the Chickensoft GameDemo?

It is a fully tested, third-person 3D game built with Godot and C#, published as a demonstration of Chickensoft's opinionated game architecture, and it now includes saving and loading with full game state preservation.

How does AutoInject resolve dependencies in the GameDemo?

In a tree-based manner, by looking through a node's ancestors until it finds one that provides what the dependent node needs. Under the hood AutoInject temporarily subscribes to the parent and calls the child back once the dependency is available, which solves the problem of needing something higher up in the tree before the parent has initialised.

What is the point of two-phase initialisation in the GameDemo?

It splits a node's initialisation into creating the values it will use and consuming those values, which makes node scripts testable. AutoInject adds an IsTesting property to each node script it is used on, letting the node tell whether it is running in-game or in a test, and skipping the first phase during testing so mocks can be used.

How do I run the Chickensoft GameDemo?

You need the Godot C# development environment set up, with the current .NET LTS release and a stable Godot version. Run git lfs pull to resolve the binary files, open the project in Godot once before launching it from your code editor, then move with WASD, bounce with the spacebar, jump on the mushrooms and collect the coins.

Official sources

  1. chickensoft-games/GameDemo on GitHub
  2. License: MIT
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/chickensoft-games-gamedemo.svg)](https://hysenlabs.com/projects/chickensoft-games-gamedemo)

Was this useful?