---
title: "onegame for Unity developers"
url: https://recursiveai.org/onegame/articles/onegame-for-unity-developers/
description: "A concept map from Unity to onegame: MonoBehaviour, Update, Instantiate, Netcode and the Test Framework, what has no equivalent, and a spawner ported to C#."
date: 2026-10-27
author: onegame team
---

# onegame for Unity developers

A concept map from Unity to onegame: MonoBehaviour, Update, Instantiate, Netcode and the Test Framework, what has no equivalent, and a spawner ported to C#.

October 27, 2026 · onegame team

Coming from Unity, the C# you write in [onegame](https://recursiveai.org/onegame/) will look familiar, but there is no editor around it. A MonoBehaviour becomes an `[Entity]` class, `Update` becomes `OnTick` at a fixed 60 ticks per second, `Instantiate` and `Destroy` become `World.Add` and `World.Remove`, Netcode’s network variables and RPCs become `[Synced]` properties and `[Command]` methods, and Play Mode tests become headless scenario tests. Scenes, prefabs and the Inspector have no counterpart: the world is built in code and inspected from the terminal. This page maps the concepts, ports a small spawner, and lists what has no equivalent. It is a concept map, not a line-by-line port. Every onegame sample was run against onegame 1.2.0 on .NET 10.

## The concept map

| Unity | onegame | What changes |
|---|---|---|
| `MonoBehaviour` on a `GameObject` | An `[Entity]` partial class | One class holds the state, commands, simulation and view code. It isn’t an ECS, and there are no components to attach. |
| `Update`, “called every frame” ([docs](https://docs.unity3d.com/ScriptReference/MonoBehaviour.Update.html)), and `FixedUpdate` | `OnTick()` | One fixed step, 60 ticks per second of game time, on every machine. |
| `Time.deltaTime`, “the interval in seconds from the last frame” ([docs](https://docs.unity3d.com/ScriptReference/Time-deltaTime.html)) | None needed | A tick is the unit. Durations are tick counts; `World.Tick` is the clock. |
| Serialized fields | `[Synced]` partial properties | Generators add change tracking, snapshots and network deltas. Setters are private to the entity. |
| `Object.Instantiate` ([docs](https://docs.unity3d.com/ScriptReference/Object.Instantiate.html)) | `World.Add(new Bee())` | A prefab is a constructor plus a sim method that sets it up. |
| `Object.Destroy` ([docs](https://docs.unity3d.com/ScriptReference/Object.Destroy.html)) | `World.Remove(Id)` | Delayed destruction is a tick countdown in synced state. |
| Coroutines, `Invoke` | Tick counters, or `Timed<T>` | Waiting is state, so it survives snapshots and replays. |
| `Random.Range` | `World.Rng["name"]` | Named streams seeded from the world’s seed. |
| Scenes | `Setup`, fixtures and screens | The starting world is code. Fixtures are named worlds that tests and live control load. |
| Input System actions and bindings | `DefineActions`, read from `game.Actions` | Contexts, default bindings, composites such as WASD and sticks, and rebinding. The view reads actions and sends a `[Command]`; the simulation never reads a device. |
| `SpriteRenderer` | `Draw(Draw2D)` on an entity that implements `IView2D` | The entity draws its sprites, sprite-sheet clips, shapes and text each frame, from state it only reads. |
| uGUI or UI Toolkit | Screens with CSS-model styles and a component kit | Bindings re-read state every frame. Controller navigation is built in ([UI guide](https://recursiveai.org/onegame/docs/ui-guide/)). |
| Netcode for GameObjects `NetworkVariable` ([docs](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@2.7/manual/basics/networkvariable.html)) | `[Synced]` properties | Every synced property replicates from the server, with per-connection visibility. |
| Netcode RPCs ([docs](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@2.7/manual/advanced-topics/message-system/rpc.html)) | `[Command]` methods | The client sends, the authority checks and runs it on the next tick. `Predicted = true` shows it at once ([prediction](https://recursiveai.org/onegame/articles/client-side-prediction-csharp/)). |
| Unity Test Framework, Edit Mode and Play Mode ([docs](https://docs.unity3d.com/Packages/com.unity.test-framework@1.4/manual/index.html)) | YAML scenarios and `RecursiveAI.OneGame.Testing` | Headless, with replays and screenshot checks ([automated game testing](https://recursiveai.org/onegame/articles/automated-game-testing/)). |
| Play Mode and the Inspector | `onegame run`, live control over MCP | Query state and the UI tree, click by id, step time and capture frames from a terminal or script. |
| Build Settings | `onegame export` | NativeAOT builds for Linux, Windows and macOS. |

## Port a spawner

The Unity version: a hive MonoBehaviour accumulates `Time.deltaTime` in `Update`, and every 1.5 seconds, while fewer than five bees exist, it calls `Instantiate` on a bee prefab. Each bee calls `Destroy(gameObject, lifetime)` with a random lifetime from `Random.Range` and adds honey to the hive when it goes. A Harvest button empties the honey. In onegame:

*Terminal · onegame 1.2.0*

```
dotnet tool install -g RecursiveAI.OneGame.Cli --version 1.2.0
onegame new hive
cd hive
```

Replace the scaffold’s `Game.cs` with the hive, and delete its ship picture, `art/`:

*Game.cs · onegame 1.2.0 · .NET 10*

```
using System.Linq;
using OneGame.Core;
using OneGame.Host;
using OneGame.UI;
 
namespace Hive;
 
/// <summary>A hive. Like a spawner MonoBehaviour: every SpawnEvery ticks it adds a bee, up to a cap.</summary>
[Entity]
public partial class Hive : Entity
{
    public const int SpawnEvery = 90;   // 1.5 s at 60 ticks per second
    public const int MaxBees = 5;
 
    [Synced] public partial int Cooldown { get; private set; }
    [Synced] public partial int Honey { get; private set; }
 
    protected override void OnTick()
    {
        if (++Cooldown < SpawnEvery)
        {
            return;
        }
 
        Cooldown = 0;
        if (World!.Entities.OfType<Bee>().Count() < MaxBees)
        {
            // Instantiate: a new entity in the world. Its lifetime is a random 4 to 8 seconds from a named stream.
            World.Add(new Bee()).Hatch(World.Rng["bees"].NextInt(240, 481));
        }
    }
 
    [Command]
    public bool Harvest()
    {
        if (Honey == 0)
        {
            return false;
        }
 
        Honey = 0;
        return true;
    }
 
    // Called by a bee on its last tick.
    public void Deliver() => Honey++;
 
    [View]
    public string Label() => $"{World!.Entities.OfType<Bee>().Count()} bees out, {Honey} honey";
}
 
/// <summary>A bee. Like Destroy(gameObject, lifetime): it removes itself when its time is up.</summary>
[Entity]
public partial class Bee : Entity
{
    [Synced] public partial int TicksLeft { get; private set; }
 
    public void Hatch(int ticks) => TicksLeft = ticks;
 
    protected override void OnTick()
    {
        if (--TicksLeft > 0)
        {
            return;
        }
 
        World!.Entities.OfType<Hive>().First().Deliver();
        World.Remove(Id);
    }
}
 
public sealed class HiveGame : GameDefinition
{
    public override string Name => "hive";
 
    public override void RegisterEntities(EntityRegistry registry) => OneGame.Generated.Hive.EntityTypes.Register(registry);
 
    public override void Setup(World world) => world.Add(new Hive());
 
    public override void DefineScreens(ScreenSet screens) =>
        screens.Add("main", game => Ui.Column(
            Ui.Label(() => HiveOf(game).Label()).Named("hive"),
            Ui.Button("Harvest", () => HiveOf(game).Send.Harvest()).Named("harvest").Padding(Length.Px(8)).Background(Color.Rgb(0x8A6A1E)))
            .Padding(Length.Px(24)).Gap(Length.Px(12)).Background(Color.Rgb(0x1F1A12)).TextColor(Color.White));
 
    private static Hive HiveOf(GameInstance game) => (Hive)game.World.Entities[0];
}
```

- **Timers are state.** `Cooldown` and `TicksLeft` are synced properties, so a snapshot taken mid-flight resumes with the same timers, and a client’s replica shows them as the server has them.
- **Instantiate and Destroy.** `World.Add(new Bee()).Hatch(...)` creates and configures a bee in one tick. A bee removes itself with `World.Remove(Id)`. Both happen inside sim code, so they are recorded and replayed like everything else.
- **Finding other objects.** `World.Entities.OfType<Hive>()` replaces looking objects up by type in the scene. The entity list is ordered, so iterating it is deterministic.
- **Randomness.** `World.Rng["bees"]` replaces `Random.Range`. With the same seed, the same bees live the same lifetimes.

### Test it

Remove the scaffold’s ship scenarios (everything in `scenarios/`) and add a scenario that lets twenty seconds pass, harvests, and checks that a second harvest is refused:

*scenarios/bees.yaml · onegame 1.2.0*

```
name: bees
description: Twenty seconds of bees coming and going, then a harvest; a second harvest finds nothing.
seed: 3
steps:
  - step: 1200
  - expect:
      ui: { id: hive, text: "4 bees out, 9 honey" }
  - click: harvest
  - step: 1
  - command: { entity: Hive, name: Harvest }   # nothing left: refused
  - step: 1
expect:
  - state: { entity: Hive, property: Honey, equals: 0 }
  - ui: { id: hive, text: "4 bees out, 0 honey" }
```

*Terminal · onegame 1.2.0*

```
onegame test
onegame run --headless --frames 1200 --seed 3 --json
onegame run --headless --frames 1200 --seed 3 --json
onegame run --headless --frames 1200 --seed 4 --json
```

*Output · onegame test*

```
PASS bees (46 ms, tick 1202, hash 5930bc039475d14c)
1 passed, 0 failed
```

*Output · onegame run*

```
{"ok":true,"game":"hive","headless":true,"frames":1200,"tick":1200,"hash":"0fc1cbdccd4614ad","width":1280,"height":720}
 
{"ok":true,"game":"hive","headless":true,"frames":1200,"tick":1200,"hash":"0fc1cbdccd4614ad","width":1280,"height":720}
 
{"ok":true,"game":"hive","headless":true,"frames":1200,"tick":1200,"hash":"6e947a2ff299e09e","width":1280,"height":720}
```

The same seed gives the same world hash after 1,200 ticks, and seed 4 gives different bees. That is what makes the exact counts in the scenario (“4 bees out, 9 honey”) safe to assert. A version timed with `Time.deltaTime`, which varies from frame to frame, can’t pin counts that exactly.

## What has no equivalent

- **The editor.** No scene view, Inspector, prefab editor or animation window, by design. Inspection is through live control and captures; see [Driving a game](https://recursiveai.org/onegame/docs/driving-a-game/).
- **Physics.** No rigidbodies, colliders or physics queries. Collision and movement are your own fixed-point sim code ([deterministic simulation](https://recursiveai.org/onegame/articles/deterministic-game-simulation-csharp/)).
- **An asset store.** None.
- **Floats in game state.** Synced state uses `Fixed`, `FVec2`, `FVec3`, `FQuat` and `Transform3`. A `float` synced property is a compile error. Views may use floats.
- **Platforms.** From the NuGet packages: Linux (including the Steam Deck), Windows and macOS, plus dedicated servers for Linux. **Web, Android and iOS exports need an engine checkout at 1.2.0.** Consoles aren’t covered in onegame’s docs.

For the bigger decision, including Unity’s current pricing and the other engines that keep C#, see [Unity alternatives for C# programmers](https://recursiveai.org/onegame/articles/unity-alternatives-csharp/). To start, follow [Getting started](https://recursiveai.org/onegame/docs/getting-started/).
