Skip to content

GameEntity

Every script you write derives from GameEntity. It is your script and the entity it's attached to — there's no separate "get the entity this script is on" step.

csharp
using JoystickEngine;

public class Player : GameEntity
{
    protected override void OnCreate()
    {
        Log.Info($"{Name} is ready");
    }

    protected override void OnUpdate(float ts)
    {
        Translation += new Vector3(1, 0, 0) * ts;
    }
}

Identity

ID

The entity's unique id, stable for as long as the entity exists. Read-only, and hidden from the inspector — identity is engine state, not something you author.

Name

The entity's name, as shown in the Scene Hierarchy panel. Readable and writable. Reads as an empty string for an entity that no longer exists, which is the cheapest way to check whether a reference you're holding is still alive.

csharp
if (other.Name == "Player")
    TakeDamage();

Enabled

Whether the entity is active. Setting it to false hides the entity and stops its scripts, and it takes the whole child subtree with it — the usual way to hold a pause menu or a boss room in the scene, switched off, until something turns it on.

csharp
GameEntity? menu = FindEntityByName("PauseMenu");
if (menu != null)
    menu.Enabled = true;

Position

Translation

The entity's position as a Vector3. Read and write.

csharp
Translation = new Vector3(0, 5, 0);
Translation += new Vector3(move.X, move.Y, 0) * m_Speed * ts;

Moving a physics body by hand

Setting Translation on an entity with a dynamic Rigidbody 2D teleports it — it skips collision detection along the way, so it can end up inside a wall. For anything simulated, apply an impulse or set velocity instead. See Rigidbody2DComponent.

There is also a TransformComponent façade whose Translation forwards to exactly this property, for when you want the component shape for consistency:

csharp
GetComponent<TransformComponent>().Translation = Vector3.Zero;

Hierarchy

Parenting keeps an entity's world position, so attaching something to a parent never makes it visually jump.

Parent

The entity's parent, or null if it's a root entity. Read and write — assigning re-parents the entity.

ChildCount

How many direct children this entity has.

GetChild(int index)

The child at index (0 to ChildCount - 1), or null if out of range.

FindChild(string name)

The first direct child with that name, or null. Direct children only — it doesn't search grandchildren.

csharp
GameEntity? fill = FindChild("Fill");

FindEntityByName(string name)

Finds an entity anywhere in the scene by name, or null. Cache the result in OnCreate rather than calling it every frame.

csharp
private GameEntity? m_Player;

protected override void OnCreate()
{
    m_Player = FindEntityByName("Player");
}

Components

HasComponent<T>()

Whether this entity has the component.

GetComponent<T>()

The component, or null if the entity doesn't have it. Returns a fresh façade object each call — the façade holds no state of its own, it reads and writes through to the engine — so cache it in a field rather than calling this every frame.

csharp
private Rigidbody2DComponent? m_Body;

protected override void OnCreate()
{
    m_Body = GetComponent<Rigidbody2DComponent>();
}

See Components for everything you can ask for.

As<T>()

Casts this entity to another script class, so you can reach that class's own fields and methods. Returns null if the entity isn't running that script.

csharp
protected override void OnTriggerEnter(GameEntity? other)
{
    Player? player = other?.As<Player>();
    if (player != null)
        player.TakeDamage(1);
}

Surviving a scene change

Persist()

Moves this entity, and its whole child subtree, into a hidden always-loaded scene that survives every future Scenes.Load. This is how a music player, a save manager or a singleton controller outlives a level change. Calling it again once already persisted does nothing.

csharp
public class AudioManager : GameEntity
{
    protected override void OnCreate() => Persist();
}

A persisted entity becomes a root — cross-scene parenting isn't representable, so it loses its parent if it had one.

The duplicate guard

If a persistent entity with the same Name already exists, the new one is destroyed and the original keeps running untouched. That's the fix for the classic "reloading the level created a second AudioManager" bug, and it's why persistent entities want stable, unique names.

For plain data crossing a scene boundary — a score, a checkpoint, a flag — prefer Session. A persistent entity carries a transform, components, physics and a script instance across a boundary where none of that is usually meaningful, and each one is a way to leak.

Coroutines

RunCoroutine(IEnumerator routine)

Starts a coroutine and returns a handle. See Coroutines for what you can yield return.

StopCoroutine(CoroutineHandle handle)

Stops that coroutine, including one paused mid-wait.

StopAllCoroutines()

Stops every coroutine this entity started.

Lifecycle hooks

Override the ones you need. All are protected override.

Core

HookWhen it runs
OnCreate()Once, when the entity comes to life
OnUpdate(float ts)Once per rendered frame. ts is the scaled time since the last frame
OnDestroy()Once, when the entity is destroyed

Overriding OnUpdate on a script that uses coroutines

The base OnUpdate is what advances this entity's coroutines. If you override it and call RunCoroutine, call base.OnUpdate(ts) somewhere in your override — otherwise your override replaces the base entirely (ordinary C# virtual dispatch) and the coroutines quietly stop advancing. A script that never overrides OnUpdate needs no such call.

Physics

HookWhen it runs
OnFixedUpdate(float fixedTs)Once per fixed physics substep — zero, one or several times per rendered frame
OnCollisionEnter(Collision2D collision)A solid contact begins
OnCollisionExit(Collision2D collision)A solid contact ends
OnTriggerEnter(GameEntity? other)A sensor overlap begins
OnTriggerExit(GameEntity? other)A sensor overlap ends

Forces and impulses belong in OnFixedUpdate. Applying one in OnUpdate makes its strength depend on frame rate — the same bug as a variable-timestep jump height, wearing a different hat.

Collision callbacks fire for a solid contact (neither collider is a sensor); trigger callbacks fire when at least one side is a sensor. Both entities in a touch get the callback, once each, with no ordering guarantee between the two sides — don't write gameplay that assumes one runs first.

other can be null

collision.Entity and other are null when the far side was destroyed earlier in the same dispatch batch — two bullets hitting one enemy in a single physics step, for instance. It's never a dangling reference, but you do have to null-check.

See Physics2D for queries and 2D Physics for the concepts.

Pooling

HookWhen it runs
OnSpawn()Every time the entity is handed out of a pool
OnDespawn()Every time it's returned to its pool

A pooled entity is built once and reused forever, so OnCreate and OnDestroy fire once each for the whole life of the instance. OnSpawn and OnDespawn are what fire on every reuse — that's where "start of life" and "clean up" belong for anything pooled. Neither is called for an entity that isn't pooled.

OnDespawn is not optional

Your state is not reset for you. Velocity, animation frame, timers and every field on your script survive into the next life. A recycled bullet that arrives already moving is an empty OnDespawn, not an engine bug — the engine can't know what "clean" means for your game.

It fires before the entity is disabled, so physics and audio are still live: zeroing a velocity there works.

See Pools.

Animation

HookWhen it runs
OnAnimatorStateEntered(string stateName)The animator enters a state
OnAnimatorStateExited(string stateName)The animator leaves a state
OnAnimatorPose()After the animator has written this frame's pose

Entered/Exited fire once per transition, in Exited-then-Entered order. OnAnimatorPose is specifically for layering procedural motion — recoil, look-at, breathing — on top of a clip without the next frame's sample overwriting it. See Animator.

UI

HookWhen it runs
OnClick()A completed click on a UI Button on this same entity

The script has to be on the button's own entity — see UI.

Networking

NetIdentity

This entity's network identity — net id, owner, and authority. Always safe to read; IsSpawned is false for any entity that isn't on the network, which is every entity in a single-player game. See Networking.

See also