Skip to content

Entities

GameEntity is the entity base class, in sage-core. NodeEntity extends it in sage with a Babylon TransformNode. Both are abstract; a game subclasses one and declares a behavior tuple. The concepts behind them are on the Entities page.

typescript
import { GameEntity, NodeEntity } from '@skewedaspect/sage';

GameEntity

Instance members

MemberTypeMeaning
idstringThe entity's ID
namestringThe class-table key it was spawned or restored under
stateobjectThe state bag. Narrow with hasBehavior, or re-declare on the subclass.
opsobjectThe operations. Same.
eventSubscriptionsreadonly EventSubscription[]Every subscription its behaviors declared
hasBehavior(Class)this is NarrowedTo<Class>True when an attached behavior is an instance of Class, counting subclasses. Narrows state and ops.
getEntity(id)GameEntity | undefinedResolves another entity through the simulation context
emit(type, payload, targetID?)voidQueues an event with this entity as sender
update(dt)voidRuns each behavior's onUpdate in attachment order. Overridable.
destroy()voidUnsubscribes from the bus

Two members are protected, reachable from the entity class's own code and nowhere else:

MemberMeaning
contextThe GameSimulationContext the entity was created with: bus and getEntity
attachedBehaviors()A copy of the behavior instances, in attachment order

attachedBehaviors is how an update() override runs a hook every behavior declares, which unique operation names keep out of ops.

Statics

StaticMeaning
behaviorsThe behavior tuple, static override readonly behaviors = [ ... ] as const
create(id, context, state?, name?)Builds an entity and runs onCreate
restore(id, context, state, name?)Builds an entity from snapshot state, without onCreate

The constructor is protected. simulation.spawn calls create; fromSnapshot calls restore. Both check the composition at construction and throw for a behavior that declares an operation with no method behind it, or two behaviors that expose the same operation name.

Overriding update

An entity class may override update() and order its work explicitly. super.update(dt) runs the default walk:

typescript
export class VehicleEntity extends NodeEntity
{
    static override readonly behaviors = behaviors;

    declare readonly state : StateFor<typeof behaviors>;
    declare readonly ops : OpsFor<typeof behaviors>;

    override update(dt : number) : void
    {
        super.update(dt);
        this.ops.applyVelocity(dt);
    }
}

Here applyVelocity is an operation one of the vehicle's behaviors exposes, run after every behavior's onUpdate. An override that needs to run a hook every behavior declares, which unique operation names keep out of ops, walks this.attachedBehaviors() instead.

The simulation loop calls update() on every entity in insertion order and knows nothing else about what happens inside. An entity never calls another entity's update or destroy.

NodeEntity

Instance members

MemberTypeMeaning
nodeTransformNodeThe entity's node in the level's scene, never null
meshAbstractMesh | undefinedThe loaded mesh, undefined until it arrives and always for an adopted node
audioAudioManager | undefinedThe game's audio manager, when it opted into one

update() on a NodeEntity runs the behavior walk, then writes state.position and state.rotation onto the node. An adopted node is left alone.

destroy() runs every behavior's onDestroy, unsubscribes from the bus, then returns the node and mesh to the pool for a poolable class, disposes the node otherwise, or leaves an adopted node in place.

Statics

StaticDefaultMeaning
poolablefalseWhether despawn returns the node and mesh set to the pool
poolSizeundefinedThe cap for this class's pool, in place of poolSizeLimit
meshConfigundefinedA MeshConfig for the standard mesh source. See Authoring.
actionsundefinedThe input actions this class's entities carry, as Action declarations

NodeEntityState

Every NodeEntity carries these state fields whatever its behaviors declare:

typescript
interface NodeEntityState extends GameEntityState
{
    tags : string[];
    position : Vector3State;
    rotation : QuaternionState;
    parentID ?: string;
    nodeName ?: string;
}
FieldMeaning
tagsThe entity's tags, an ordered list of unique strings
position{ x, y, z }, written onto the node each fixed step. Defaults to the origin.
rotation{ x, y, z, w }, written onto the node each fixed step. Defaults to no rotation, { x: 0, y: 0, z: 0, w: 1 }.
parentIDThe parent entity's ID. The node is parented under the parent's node.
nodeNameThe authored node this entity adopts. Written by a level, never by a game.

A behavior that moves the entity declares position in its own slice and writes it there.

The hierarchy

A child names its parent in parentID at spawn:

typescript
const ship = simulation.spawn('ShipEntity');
const turret = simulation.spawn('TurretEntity', undefined, { parentID: ship.id });

Node parenting follows the field, so Babylon composes world transforms. Restore parents the node from the same field. There is no update cascade.

Node behavior hooks

Behaviors on a NodeEntity may implement onMeshLoaded() and onDestroy(). NodeEntity recognizes each by name on whichever of its behaviors implements it. See Behaviors.

Composition types

All of these come from @skewedaspect/sage-core and are re-exported by @skewedaspect/sage except where noted.

TypeMeaning
GameEntityTypeWhat a class table holds: a constructor with a behaviors tuple and a GameEntity prototype
StateFor<Behaviors>The composed state of a behavior tuple, tags included
OpsFor<Behaviors>The composed operations of a behavior tuple
ComposedOf<Type>An instance of an entity class with state and ops typed over its tuple. What create and restore return.
NarrowedTo<Class>A GameEntity narrowed to one behavior: deep read-only state, that behavior's operations. What hasBehavior narrows to.
GameEntityState{ tags : string[] }
StateBag, StateValueThe JSON-compatible state shapes
OpsOf<Class>One behavior's operations. Used as a Requires argument. sage-core only.
StateSliceOf<Class>One behavior's state slice. sage-core only.
DeepReadonly<Value>sage-core only
ValidTuple<Behaviors>The tuple when the composition checks, or an error type naming the problem. sage-core only.

Vector3State and QuaternionState

typescript
interface Vector3State { x : number; y : number; z : number }
interface QuaternionState { x : number; y : number; z : number; w : number }

toVec3Object, toVector3, toQuatObject, and toQuaternion convert between these and Babylon's own types.

Released under the MIT License.