Spatial

Emotes

Abstract game-defined emote/action events fanned out to observers as opaque data.

Server
services.AddCrossplayEmotes();
Unity package
com.crossplay.emotes
Depends on
world

Services you call 1

Crossplay implements these. Resolve them from DI and call them from your own systems.

IEmoteService

Broadcasts abstract emote/action events from an in-world entity to whoever can see it. The piece never interprets emote ids or payloads — that is entirely the game's vocabulary.

  • void Play(ISession session, EmoteRequest request)

    Validates and broadcasts request as an EntityEmote to the sender's observers (and the sender itself when EchoToSelf is on). A sender that is not in the world, or an oversized payload (> MaxDataBytes), is silently dropped.

Configuration 1

Every tunable lives in an options object — there are no magic numbers to hunt for.

EmoteOptions

Configurable emote parameters (defaults here; never inlined in logic).

  • bool EchoToSelf { get; set; }

    When true (the default) the performer also receives its own EntityEmote — the client can treat the echo as the authoritative "it happened" confirmation (or play immediately and use the echo purely as an ordering signal; the framework doesn't care which). Turn off only if the game plays the local emote optimistically and never wants the server round-trip echoed back.

  • int MaxDataBytes { get; set; }

    Maximum size of the opaque Data payload, in bytes. Emote parameters are meant to be tiny (a step index, an intensity); anything larger is dropped as malformed/abusive. Default 64. Raise only if your game encodes richer emote parameters; keep it small to bound per-observer fan-out cost.

Wire messages 2

The protocol this piece speaks. Ids are allocated per piece so they can never collide.

1601 EmoteRequest

Client → server: play an emote / action / gesture. EmoteId is an abstract, game-defined id — the framework never knows what it means (a wave, a dance move, a taunt, a card flourish…); the game maps ids to its own animations/VFX/audio on the receiving side. Data is an optional opaque payload for game-specific parameters (e.g. a combo step, an intensity), capped server-side by EmoteOptions.MaxDataBytes.

1602 EntityEmote

Server → observers (and, by default, the performer): an in-world entity performed an emote. Sent reliably — an emote is a discrete event, not a stream. Clients map EmoteId onto their own presentation (animation, VFX, audio, a text line — the framework doesn't care).

Unity services you inject 1

Crossplay binds these in the client context. Inject and call them from your own MonoBehaviours and presenters.

IEmoteClient

The Emotes puzzle piece, client side. Send a game-defined emote id (plus an optional opaque payload); receive EntityEmote events for entities you can see. The piece carries no presentation: the game subscribes to EmotePerformed and maps ids onto its own animations / VFX / audio / UI. Remove this package and emotes simply stop — nothing else breaks.

  • event Action<EntityEmote> EmotePerformed

    An entity in view (possibly yourself — the server echo) performed an emote.

  • void Play(ushort emoteId, byte[] data = null)

    Plays a game-defined emote, optionally with tiny opaque parameters.

UI views you can replace 1

Each ships a working uGUI panel you can drag into a scene. Substitute your own view to keep the logic and change the look — the presenter never knows.

ICrossplayEmotesView

Narrow contract the presenter drives. UI-only: render calls in, user intents out.