This is the multi-page printable view of this section. Click here to print.

Return to the regular view of this page.

Interfaces

The cross-gem contract layer — neutral agreements collected in GS_Core so feature gems depend on contracts instead of on each other’s internals. Covers the three contract kinds (TypeBase, Emit, Exchange).

GS_Core/Interfaces/ is the framework’s cross-gem contract layer. Feature gems no longer depend on each other’s internals — they depend on neutral contracts collected here in GS_Core. A contract lives in GS_Core; the feature gem that produces it owns and operates it. GS_Core holds the agreement, the gem holds the behavior.

This is the layer that lets GS_Interaction emit a “pulse received” signal that GS_Juice can react to without either gem including the other.

 

Contents


Why This Layer Exists

Before this layer, cross-gem features were glued together by bridge components — a component in GS_Complete whose only job was to listen to one gem and drive another (e.g. listen to GS_Unit possession, drive a GS_PhantomCam target). Every such bridge carried a hard dependency on both gems’ internals.

Once both sides speak a Core contract, those bridges collapse into their natural gem and shed the cross-gem-internals dependency. Feature glue now lives in the gem that owns the feature, not in a catch-all integration gem.


The Three Contract Kinds

The layer collects exactly three kinds of contract. The kind tells you how to extend it.

KindPatternOne-lineExtend by
TypeBaseStrategy“Inherit this and you’re plugged in.”Subclass a *Type base, reflect it → it appears in the editor’s type-picker.
EmitObservance“Wait for this signal, then act.”Handle the *EmissionBus (C++ or ScriptCanvas) and override the event you care about.
ExchangeMediator“Ask, a provider answers.”Call the *Exchange query bus. (Rare today — mostly forward-looking.)

Full mechanics, editor/script appearance, and an authoring how-to for each are on the Classifications page.


The Glance Test

The class/bus suffix encodes scope and kind, so you can classify any bus at a glance:

SuffixMeaning
*Requests / *NotificationsIn-gem bus — internal to one gem, not a cross-gem contract.
*Type (a base class)TypeBase contract — a pickable Strategy base.
*Emissions / *EmissionBusEmit contract — a one-way observation bus.
*Exchange / *ExchangeBusExchange contract — a two-way request/fulfill bus.

Status at a Glance

KindShippedNotes
TypeBase9 Strategy bases — built & verifiedThe compiled, reflected contracts.
Emit13 observation buses across 8 gemsAll built, except GS_Cinematics’ three (Typewriter / Cinematic / DialogueSequence) which are code-complete but pending a build — treat their detail as provisional.
Exchange1 (CamCoreExchange)The rest (Play/Stop family, graph parameters, surface query, CamManager queries) are planned and not yet shipped.

The full per-contract breakdown is in the Contract Reference.


Where Contracts Live

Contracts are collected under GS_Core/Interfaces/<Capability>/ by capability (Camera, Pulse, Trigger, Dialogue, Motion, Targeting, UI, Possession, Cinematics, Time) — not by gem. One capability may be produced by one gem and consumed by several.

  • Emit / Exchange buses are header-only in Core (an EBus is runtime, not serialized). The producing gem reflects them to ScriptCanvas.
  • TypeBase classes are compiled (.h + .cpp) and registered in the Core reflection aggregator, so a base is reflected ahead of any gem’s subtypes by module load order.

See the Contract Reference for the full capability-folder map and which gem produces each.


See Also


Get GS_Core

GS_Core — Explore this gem on the product page and add it to your project.

1 - Classifications

The three GS_Core contract kinds in depth — TypeBase (Strategy), Emit (Observance), and Exchange (Mediator) — each with its authoring how-to, plus the naming guard and the hoisting mechanics authors need.

The interfaces layer collects exactly three kinds of cross-gem contract. Each kind has a single, recognisable extension story — the suffix tells you which one you are looking at (see the glance test). This page covers each kind in depth and shows how to author against it.

For the contract concept and why the layer exists, start at the Interfaces overview.

 

Contents


TypeBase — Strategy

“Inherit this and you’re plugged in.”

A pickable polymorphic base class. An author writes a subclass, reflects it, and it auto-appears in the editor’s type-picker for any component that holds a list of that base. There is no registration call and no manifest — the O3DE SerializeContext drives the picker directly from the reflected inheritance.

TypeBase contracts are the only compiled contracts: they carry reflection (.h + .cpp) and are registered in the Core reflection aggregator so the base is reflected ahead of any gem’s subtypes.

“TypeBase” is the category name we use to talk about them — the classes themselves keep their domain name (PulseType, DialogueCondition, UiMotionTrack, …). That saved name string is the on-disk identity and is frozen: renaming it breaks existing assets.

Examples: PulseType / ReactorType (interaction pulses), WorldTriggerType / TriggerSensorType (world triggers), DialogueCondition / DialoguePerformance / DialogueEffect (dialogue graph nodes), UiMotionTrack / FeedbackMotionTrack (UI & FX motion tracks).

How to: add a new type

  1. Derive from the Strategy base — class MyReactor : public GS_Core::ReactorType.
  2. Reflect it to SerializeContext (and EditContext for inspector fields), exactly like any O3DE class.
  3. Done — it appears in the type-picker dropdown of every component that holds a vector of that base. No registration call.

Emit — Observance

“Wait for this signal, then act.”

A one-way observation bus. A producer emits a signal; any number of consumers listen. If the producer is not present at runtime, the signal simply never fires — consumers degrade to silence rather than erroring.

Code shape: an EBus interface named *Emissions, aliased *EmissionBus. Each event has an empty default body, so a handler only implements the events it cares about.

Examples: PossessionEmissions (a controller possessed a unit / a unit was possessed), PulseReactorEmissions (a reactor received a pulse), TimeEmissions (world tick, day/night changed), UIPageEmissions (a page shown/hidden). The full list is in the Contract Reference.

Addressing: per-entity vs global

  • By EntityId — listen to a specific entity (interaction, UI interactable, motion, possession buses). Connect at the address of the entity you care about.
  • Global broadcast — listen for any occurrence of a state change (TimeEmissions, CinematicEmissions, DialogueSequenceEmissions, UIPageEmissions). Connect to the broadcast.

The possession bus fires controller-vantage events on the controller entity and unit-vantage events on the unit entity.

Fire on resolved state, not on request

Emits fire at the moment the observable state actually changed, not when a command was issued. You can trust their timing:

  • “Active camera changed” fires at blend-complete, not when a new camera was requested.
  • “Page shown / hidden” fires after the show/hide animation resolves, not on the call.
  • “Unit released” carries the released entity (captured before the reference clears), so the payload is meaningful rather than already-nulled.

How to: react to an event

  1. Handle the bus — class MyThing : public GS_Core::PulseReactorEmissionBus::Handler (C++), or drop the bus’s event-handler node into a ScriptCanvas graph.
  2. Connect at the right address — BusConnect(entityId) for per-entity buses, BusConnect() for global broadcasts.
  3. Override only the events you need. Disconnect when done.

Exchange — Mediator

“Ask, a provider answers.” — mostly forward-looking

A two-way request/fulfill (query) bus: a consumer asks, a single provider answers. An Exchange is created only when a bidirectional cross-gem need is proven — a pull, not a push. Until then a feature’s command surface stays an in-gem *Requests bus.

Code shape: *Exchange / *ExchangeBus.

Shipped today: only CamCoreExchange (GetCamCore — “which entity is the active camera core?”). It pairs with the CamCore lifecycle Emit and data Emit to form an observable-service trio: learn it exists (Emit) → query it (Exchange) → observe its updates (Emit).

Planned (not yet shipped): Play/Stop a sequence / UI motion / feedback emit / soundtrack / audio event; Get/Set a graph parameter; a terrain surface-info query for footstep sounds; CamManager queries. Each planned Play/Stop Exchange will pair with a completion Emit — command via Exchange, observe via Emit.

How to: query a provider

  1. Call the Exchange bus with a result — GS_PhantomCam::CamCoreExchangeBus::BroadcastResult(out, &GS_PhantomCam::CamCoreExchange::GetCamCore).
  2. A single provider answers. If no provider is present, the result is the bus’s default.

Hoisting Mechanics

The defining idea: a contract lives in GS_Core, but the feature gem owns and operates it. Only the neutral C++ interface moves to Core; everything that needs the producer present stays home.

PieceLives in
The EBus interface (*Emissions / *Exchange) or the Strategy base (*Type)GS_Core
The emit / broadcast call sites (where the signal fires)producing gem
The ScriptCanvas binder + its BehaviorContext reflectionproducing gem
The command/query half (*Requests) until proven cross-gemproducing gem

An observation handler is inert without its emitter, so binding the signal into ScriptCanvas only makes sense where the emitter exists — the producing gem reflects it. GS_Core just publishes the shape.

Transparent hoist vs deliberate redesign

  • Transparent hoist (same events, new home) — ScriptCanvas name, handler UUID, and translation assets are all preserved. No graph breakage. This is the norm (Time, Interaction, UI, Cinematics emits all did this).
  • Deliberate redesign (events split or renamed) — the ScriptCanvas name intentionally changes and old graphs break by design. The one case is Possession: the single “possessed” event (which fired on both possess and depossess) was split into explicit possess + release, across controller and unit vantages. It is now PossessionEmissionBus.

See Also


Get GS_Core

GS_Core — Explore this gem on the product page and add it to your project.

2 - Contract Reference

Every GS_Core contract by capability folder and producing gem — the 9 TypeBase Strategy bases, the 13 Emit observation buses, and the Exchange query buses — with shipped-versus-pending status and a per-gem feature index.

Every contract collected under GS_Core/Interfaces/<Capability>/, the gem that produces it, and its status. For what each kind is and how to author against it, see Classifications.

✦ marks an event or contract created during the interfacing effort (a gap-fill, not a pre-existing notification).

 

Contents


TypeBase — Strategy Bases

Compiled, editor-pickable. Subclass + reflect → the subtype appears in the type-picker. All nine are shipped and build-verified.

ContractCapability folderProducing gem
PulseTypePulseGS_Interaction
ReactorTypePulseGS_Interaction
WorldTriggerTypeTriggerGS_Interaction
TriggerSensorTypeTriggerGS_Interaction
DialogueConditionDialogueGS_Cinematics
DialoguePerformanceDialogueGS_Cinematics
DialogueEffectDialogueGS_Cinematics
UiMotionTrackMotionGS_UI
FeedbackMotionTrackMotionGS_Juice

DialogueEffect is passive — its reversal logic lives in the dialogue node’s teardown rather than a bus handler. An authoring detail, not a contract change.


Emit — Observance Buses

Header-only. Handle the *EmissionBus and override the events you care about.

ContractEventsCapabilityProducerStatus
CamCoreEmissionsUpdateCameraPosition, OnCamCoreRegistered, OnCamCoreUnregisteredCameraGS_PhantomCam✅ Built
TimeEmissionsWorldTick, DayNightChangedTimeGS_Environment✅ Built
PulseReactorEmissionsOnPulseReceivedPulseGS_Interaction✅ Built
TriggerSensorEmissionsOnTriggered, OnResetTriggerGS_Interaction✅ Built
TargetingEmissionsOnUpdateInteractTarget, OnEnterStandby, OnExitStandby, OnInteractTargetingGS_Interaction✅ Built
FeedbackMotionEmissionsOnFeedbackMotionComplete, OnFeedbackMotionLoopedMotionGS_Juice✅ Built
UIInteractableEmissionsOnClickedUIGS_UI✅ Built
UiMotionEmissionsOnUiMotionComplete(entity, name), OnUiMotionLooped(entity, name)MotionGS_UI✅ Built
UIPageEmissionsOnPageShown(page, name), OnPageHidden(page, name)UIGS_UI✅ Built
PossessionEmissionsOnPossessedUnit✦, OnReleasedUnit✦, OnPossessedByController, OnReleasedByControllerPossessionGS_Unit✅ Built
TypewriterEmissionsOnTypeFired, OnTypewriterCompleteDialogueGS_Cinematics⚠️ Pending build
CinematicEmissionsEnterCinematic, ExitCinematicCinematicsGS_Cinematics⚠️ Pending build
DialogueSequenceEmissionsOnDialogueSequenceStarted✦, OnDialogueTextBegin, OnDialogueSequenceCompleteDialogueGS_Cinematics⚠️ Pending build

Addressing. CamCoreEmissions (data event) is global. The Interaction / UI-interactable / motion / possession buses are addressed by EntityId — listen to a specific entity. TimeEmissions, CinematicEmissions, DialogueSequenceEmissions, and UIPageEmissions are global broadcasts — listen for any occurrence. The possession bus fires controller-vantage events on the controller entity and unit-vantage events on the unit entity.


Exchange — Mediator Buses

Two-way query buses. Only CamCoreExchange ships today; the rest are planned.

ContractMethodsCapabilityProducerStatus
CamCoreExchangeGetCamCoreCameraGS_PhantomCam✅ Built
Play/Stop — Sequence · UIMotion · FeedbackEmit · soundtrack · audio event(multi)Cinematics / UI / Juice / Audio⏳ Planned
Get/Set Graph Parameter(graphcanvas graphs)multi⏳ Planned
Terrain surface-info query (footstep sounds)EnvironmentGS_Environment⏳ Planned
CamManager queriesCameraGS_PhantomCam⏳ Planned

Each planned Play/Stop Exchange pairs with a completion Emit — command via Exchange, observe via Emit. GS_Audio’s emits are greenfield and will land with its Play/Stop Exchange, not before.


Per-Gem Feature Index

What each gem contributed to the layer.

GemContribution
GS_CoreThe Interfaces/ tree, the ReflectAllInterfaces aggregator, and a generic motion-loop hook (OnMotionLoop) added to the shared motion runtime to back the UI & Juice motion-loop emits.
GS_PhantomCamCamCore (Exchange + Emit); CamManager rig-lifetime emits; an always-face-camera helper repointed. The internal “set this camera” command stayed an in-gem bus — it is a manager→core pipe, not a cross-gem contract.
GS_EnvironmentTimeManager day/night and world-tick emits; time control stays in-gem.
GS_InteractionPulse-reactor, trigger-sensor, and targeting emits; a new OnInteract✦ emit; a dead handler base removed.
GS_JuiceFeedback-motion complete/loop emits, wired through the emitter.
GS_UIButton “clicked”, UI-motion complete/loop (with a motion name), page shown/hidden.
GS_UnitPossession (controller & unit vantages, possess & release); in-gem consumers repointed.
GS_CinematicsTypewriter, cinematic enter/exit, dialogue-sequence lifecycle (+ a new sequence-started event). Code-complete, pending a build.
GS_CompleteBridge components collapsed: a paper-facing handler moved to GS_Performer; a dialogue-select button, a possession→cam-target utility, and a cinematic controller all repointed to Core contracts and dropped their producing-gem includes.
GS_PerformerReceived the paper-facing handler from GS_Complete.

Deferred / Not Yet in This Layer

  • GS_Audio emits — greenfield; ship with the planned Play/Stop audio Exchange.
  • CamState hoist — unblocks when the neutral EntityId-addressed Camera Exchange replaces its owner-pointer coupling.
  • Possession ScriptCanvas translation assets — regeneration pending.
  • A legacy UI hub bus header — stale; cleanup pending (GS_Page is the live page system).

See Also


Get GS_Core

GS_Core — Explore this gem on the product page and add it to your project.