Coming from Unity, the C# you write in 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), 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) | 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) | World.Add(new Bee()) | A prefab is a constructor plus a sim method that sets it up. |
Object.Destroy (docs) | 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). |
Netcode for GameObjects NetworkVariable (docs) | [Synced] properties | Every synced property replicates from the server, with per-connection visibility. |
| Netcode RPCs (docs) | [Command] methods | The client sends, the authority checks and runs it on the next tick. Predicted = true shows it at once (prediction). |
| Unity Test Framework, Edit Mode and Play Mode (docs) | YAML scenarios and RecursiveAI.OneGame.Testing | Headless, with replays and screenshot checks (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:
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/:
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.
CooldownandTicksLeftare 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 withWorld.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"]replacesRandom.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:
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" }
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
PASS bees (46 ms, tick 1202, hash 5930bc039475d14c) 1 passed, 0 failed
{"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.
- Physics. No rigidbodies, colliders or physics queries. Collision and movement are your own fixed-point sim code (deterministic simulation).
- An asset store. None.
- Floats in game state. Synced state uses
Fixed,FVec2,FVec3,FQuatandTransform3. Afloatsynced 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. To start, follow Getting started.