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

Return to the regular view of this page.

Cam Manager

Camera system lifecycle controller — channel registry, per-channel priority arbitration, group target registry, influence routing, dispatch overrides, and active main-view selection.

The Cam Manager is the singleton controller for the GS_PhantomCam camera system. It extends GS_ManagerComponent and owns the full lifecycle of virtual cameras: per-channel registration of phantom cameras and Cam Cores, per-channel priority arbitration, channel-aware target binding, influence routing, the named Group Target registry, cross-channel cinematic dispatch, and engine main-view selection.

When a phantom camera activates, it walks transform ancestors looking for a ChannelStampComponent, identifies its channel, and self-registers with the Cam Manager. When priorities change — through direct calls, enable/disable toggling, or influence fields — the Cam Manager re-evaluates the affected channel(s) and notifies the channel’s Cam Core to begin blending toward the new winner.

For usage guides and setup examples, see The Basics: GS_PhantomCam.

Cam Manager component in the O3DE Inspector

 

Contents


How It Works

Camera Registration

Every Phantom Camera registers with the Cam Manager on activation and unregisters on deactivation. The registration path depends on the cam’s CamChannelScope and whether it lives under a ChannelStampComponent ancestor — see Channels. For legacy / single-player flows (no stamp ancestor), the cam routes through channel 0 via the legacy RegisterPhantomCam method.

Priority Evaluation

When any camera’s priority changes, the Cam Manager calls EvaluatePriority(), which iterates every active channel. Each channel computes effective = base + sum(influences) per cam and picks the highest. A change in a channel’s winner triggers SettingNewCamOnChannel(channelId, targetCam) (when channel instancing is enabled) or the legacy SettingNewCam(targetCam) (when it is not). The channel’s Cam Core listens for this to begin its blend transition.

Camera Influences

Camera influences are named priority modifiers that shift a camera’s effective priority. They are added and removed through AddCameraInfluence(sourceEntity, targetEntity, camName, influence) and RemoveCameraInfluence(sourceEntity, targetEntity). The sourceEntity is the storage key (so multiple overlapping fields don’t collide); the targetEntity is the routing key — the Cam Manager looks up the channel via GetChannelForTarget(targetEntity). Influences whose target isn’t bound to any channel are silently dropped. See Camera Influence Fields.

Target Assignment

Each channel holds its own target binding. SetChannelTarget(channelId, target) sets the target for one channel; the legacy SetTarget(target) forwards to channel 0. Setting a valid target on a channel propagates it to every cam in the channel via SetCameraTarget and pushes a synchronous SnapCameraNow so the cam’s evaluated pose lands before downstream consumers query its transform. Clearing the target (invalid EntityId) does not push snap — this preserves the depossess-guard pose-hold behavior.


Authoring Tiers

The Cam Manager exposes a tiered author surface via progressive disclosure. Three top-level inspector fields control which tier is active:

TierConfigurationBehavior
Tier 1 — Standard single-playerm_primaryRigPrefab set; m_enableInstancedChannels = falseCam Manager spawns one rig from the primary prefab on startup; everything routes through channel 0. Most projects use this tier.
Tier 2 — Legacy level-placedNo m_primaryRigPrefab; m_enableInstancedChannels = falseThe CamCore is hand-placed in the level instead of spawned from a rig prefab. Legacy author flow — supported but not preferred.
Tier 3 — Multi-channelm_enableInstancedChannels = true; m_channelConfigs populatedPer-channel rig spawning, per-channel arbitration, channel-aware notifications, cross-channel cinematic dispatch. Split-screen and co-op projects use this tier.
Inspector fieldVisibilityPurpose
m_primaryRigPrefabAlwaysDefault rig prefab. Universal fallback for any channel with no override.
m_enableInstancedChannelsAlwaysMaster gate. When OFF, channel-aware fields are hidden and legacy bus signatures are routed. When ON, the full channel surface is live.
m_channelConfigsWhen master gate is ONArray of CameraChannelConfig rows.
m_activeChannelCountWhen master gate is ONLobby-driven cap. Channels configured beyond this count stay dormant at startup.

CameraChannelConfig per-row fields:

FieldInspector labelPurpose
m_channelNameChannel NameDisplay label (“P1”, “P2”). Not a lookup key.
m_policyChannel PolicyPerChannelInstance (default — Cam Manager spawns this channel’s rig) or LegacyLevelPlaced (channel uses level-placed cams, channel 0 only).
m_rigPrefabRig Override PrefabPer-channel rig. Leave empty to inherit m_primaryRigPrefab.
m_enabledByDefaultEnabled By DefaultWhether this channel spawns on startup.

Channels

A channel is one player viewpoint slot. Each channel owns its own rig, target binding, priority table, active influence set, and Cam Core. Channel 0 (DefaultChannelId) is the implicit default for legacy / single-player flows.

Channel addressing covers most of the Cam Manager’s runtime surface. The bus exposes paired methods — a legacy single-arg form that forwards to channel 0, and a channel-aware multi-arg form for Tier 3:

ConcernLegacyChannel-aware
Set the targetSetTarget(entity)SetChannelTarget(channelId, entity)
Get the targetGetTarget()GetChannelTarget(channelId)
Register a camRegisterPhantomCam(cam)RegisterPhantomCamForChannel(cam, stampEntity, channelId, token)
Register a CamCoreRegisterCamCore(camCore)RegisterCamCoreToChannel(camCore, stampEntity, channelId, token)
Dominance notificationSettingNewCam(cam)SettingNewCamOnChannel(channelId, cam)

Direct registration variants (RegisterPhantomCamDirect, RegisterPhantomCamShared) cover author-explicit CamChannelScope::TrueUnique cams that bypass the stamp-walk path. See Channels & Instancing for the full scope enum and stamp-walk semantics.

MethodUse
GetChannelForTarget(target)Reverse lookup — returns the channel an entity is bound to (or InvalidChannelId). Used by influence routing.
SetActiveChannelCount(count)Pre-startup lobby cap. Configured-but-extra channels stay dormant.
EnableChannel(channelId)Spawn a configured channel mid-session. Fires ChannelSpawned.
DisableChannel(channelId)Despawn a spawned channel. Fires ChannelDespawned.
GetChannelCamCore(channelId)Query the bound CamCore entity for a channel.

Group Target Registry

The Cam Manager is the single global authority for named Group Targets. A GroupTargetComponent self-registers on activate; stages with CamTargetMode::GroupTarget resolve the group’s entity by name.

MethodUse
RegisterGroupTarget(name, groupEntity)Called by GroupTargetComponent::Activate.
UnregisterGroupTarget(groupEntity)Called by GroupTargetComponent::Deactivate.
FindGroupTargetByName(name)Stages query this to resolve a group entity.
GetRegisteredGroupTargetNames()Returns the full list. Used by editor dropdowns and debug.

Dispatch Overrides

For cinematic / cross-channel scenarios — e.g. player 3’s wide cam should temporarily render on player 1’s Cam Core — the Cam Manager exposes a dispatch override. Channel arbitration still runs internally during dispatch (SettingNewCamOnChannel still fires with the arbitrated winner); only the physical Cam Core route is overridden.

MethodUse
FindCamForTarget(logicalName, targetEntity)Resolves a cam by logical (entity) name with multi-tier fallback: channel-of-target match → shared-cam scan → reserved AllChannels duplicate path.
DispatchCamToCamCore(cam, camCoreEntity)Force the Cam Core to render the specified cam. Persists until release. Fires OnCamCoreDispatched.
ReleaseCamCoreDispatch(camCoreEntity)Clear the override. Re-runs arbitration so the Cam Core routes back to its arbitrated winner. Fires OnCamCoreDispatchReleased.

Active Main View

Wraps O3DE’s AzFramework::Camera::CameraRequests::MakeActiveView so the channel system can orchestrate which Cam Core is the engine’s main view. Orthogonal to dispatch (what cam) and render-target binding (where pixels go).

MethodUse
SetActiveChannel(channelId)Resolves the channel’s Cam Core, delegates to SetActiveCamCore.
SetActiveCamCore(camCoreEntity)Calls MakeActiveView on the Cam Core. Idempotent.
GetActiveChannel()Reverse query — returns the channel that owns the engine’s active camera, or InvalidChannelId if the active cam isn’t channel-bound.
GetActiveCamCore()Direct passthrough to the engine.

Listeners receive OnActiveCameraChanged(channelId, camCoreEntity). External MakeActiveView calls made outside this API are not observed — subscribe to AzFramework::Camera::CameraNotificationBus directly if you need to detect engine-level changes.


Spawn Lifecycle

The Cam Manager’s OnStartupComplete hook (driven by the Game Manager) spawns all configured rigs before broadcasting HandleStartup so spawned cams settle into the same startup wave. Stage transitions (BeginLoadStage / LoadStageComplete) despawn and respawn rigs cleanly.

OnStartupComplete:
  if m_enableInstancedChannels == false:
    Tier 1: if m_primaryRigPrefab valid → spawn one rig for channel 0
    Tier 2: no spawn (assumes level-placed CamCore)
  else:
    Tier 3: iterate m_channelConfigs[0 .. min(size, m_activeChannelCount)]
      for each PerChannelInstance config with valid prefab and m_enabledByDefault:
        SpawnChannelRig(channelId, GetEffectiveRigPrefab(channelId))

SpawnChannelRig uses an EntitySpawnTicket with two callbacks:

  1. Pre-insertion — Cam Manager attaches a ChannelStampComponent to the spawn root and calls StampChannel(channelId). This bumps the channel’s stamp token and fires OnStamped to any subscribers.
  2. Completion — Entities activate. Cam Cores and PhantomCams walk ancestors, find the stamp, and self-register to the channel via token-aware methods. Cam Manager then walks activated entities into channel.m_rigSpawnedEntities, marks the channel active, folds shared TrueUnique cams into the channel’s priority table, validates a Cam Core registered, broadcasts ChannelSpawned(channelId, camCoreEntity), and re-runs EvaluatePriority so the arbitration result reaches the Cam Core.

Mid-session EnableChannel / DisableChannel reuse the same spawn / despawn paths and fire the same notifications.


Setup

  1. Add GS_CamManagerComponent to a dedicated entity (commonly inside a Cam Manager prefab).
  2. Register the manager prefab in the Game Manager Startup Managers list.
  3. Choose your tier:
    • Tier 1 — Assign a rig prefab (containing a Cam Core entity and your phantom cameras) to m_primaryRigPrefab. Leave m_enableInstancedChannels off.
    • Tier 2 — Place the Cam Core entity as a child of the Cam Manager entity directly. Leave m_primaryRigPrefab empty and m_enableInstancedChannels off.
    • Tier 3 — Toggle m_enableInstancedChannels on and populate m_channelConfigs with one row per supported player slot. Use m_primaryRigPrefab as the universal default and per-row Rig Override Prefab for asymmetric setups.
  4. Place or author phantom cameras with priorities, target modes, and stage compositions.

For a full walkthrough, see the PhantomCam Set Up Guide.


API Reference

Request Bus: CamManagerRequestBus

Commands sent to the Cam Manager. Global bus — single address, single handler. Extends GS_Core::ManagerBaseRequests.

Registration

MethodParametersReturnsDescription
RegisterPhantomCamAZ::EntityId camvoidLegacy registration. Routes the cam through channel 0. Used by cams without a ChannelStampComponent ancestor.
UnRegisterPhantomCamAZ::EntityId camvoidLegacy unregister.
RegisterPhantomCamForChannelAZ::EntityId cam, AZ::EntityId stampEntity, ChannelId claimedChannelId, AZ::u32 claimedTokenvoidStamp-aware registration. Cam Manager verifies the (channelId, token) against the live stamp on stampEntity before mutating channel tables — mismatched tokens are rejected.
UnRegisterPhantomCamFromChannelAZ::EntityId cam, AZ::EntityId stampEntity, ChannelId, AZ::u32 tokenvoidStamp-aware unregister.
RegisterPhantomCamDirectAZ::EntityId cam, ChannelId channelIdvoidAuthor-explicit binding via CamChannelScope::TrueUnique + m_boundChannelId. Bypasses stamp-walk.
UnRegisterPhantomCamDirectAZ::EntityId cam, ChannelId channelIdvoidDirect unregister.
RegisterPhantomCamSharedAZ::EntityId camvoidAdds the cam to every active channel’s priority table. Used by CamChannelScope::TrueUnique + m_allChannelsShare.
UnRegisterPhantomCamSharedAZ::EntityId camvoidShared unregister.
RegisterCamCoreAZ::EntityId camCorevoidLegacy Cam Core registration (channel 0).
UnregisterCamCoreAZ::EntityId camCorevoidLegacy unregister.
RegisterCamCoreToChannelAZ::EntityId camCore, AZ::EntityId stampEntity, ChannelId, AZ::u32 tokenvoidStamp-aware Cam Core registration.
UnregisterCamCoreFromChannelAZ::EntityId camCore, AZ::EntityId stampEntity, ChannelId, AZ::u32 tokenvoidStamp-aware unregister.

Priority and influence

MethodParametersReturnsDescription
ChangeCameraPriorityAZ::EntityId cam, AZ::u32 priorityvoidSets base priority. Cam Manager walks every channel where the cam is registered and re-evaluates.
AddCameraInfluenceAZ::EntityId sourceEntity, AZ::EntityId targetEntity, AZStd::string camName, AZ::u32 influencevoidChannel-routed. sourceEntity is the per-channel storage key; targetEntity is the routing key (channel resolved via GetChannelForTarget). Silently dropped if the target isn’t channel-bound.
RemoveCameraInfluenceAZ::EntityId sourceEntity, AZ::EntityId targetEntityvoidChannel-routed removal by (source, target) key.

Targets

MethodParametersReturnsDescription
SetTargetAZ::EntityId targetvoidLegacy. Forwards to SetChannelTarget(DefaultChannelId, target).
GetTargetAZ::EntityIdLegacy. Returns channel 0’s target.
SetChannelTargetChannelId, AZ::EntityId targetvoidChannel-aware. Propagates the new target to every cam in the channel via SetCameraTarget, and pushes SnapCameraNow when binding to a valid target so evaluated pose lands before downstream queries. Clearing the target does not push snap.
GetChannelTargetChannelIdAZ::EntityIdReturns the channel’s current target binding.
GetChannelForTargetAZ::EntityId targetChannelIdReverse lookup. Returns InvalidChannelId if the entity isn’t bound.
GetCamManagerAZ::EntityIdReturns the Cam Manager’s own entity id.
GetPhantomCamAZStd::string camNameAZ::EntityIdResolves a cam by entity name.

Runtime channel management

MethodParametersReturnsDescription
SetActiveChannelCountAZ::u32 countvoidPre-startup only. Lobby-driven cap. Mid-session calls warn and are ignored — use EnableChannel / DisableChannel instead.
GetActiveChannelCountAZ::u32Query.
EnableChannelChannelIdvoidSpawn a configured channel mid-session. Fires ChannelSpawned.
DisableChannelChannelIdvoidDespawn. Fires ChannelDespawned. No-op if not spawned.
GetChannelCamCoreChannelIdAZ::EntityIdReturns the bound Cam Core entity, invalid if none.

Dispatch and active view

MethodParametersReturnsDescription
FindCamForTargetAZStd::string logicalName, AZ::EntityId targetEntityAZ::EntityIdMulti-tier resolution. Channel-of-target first, then shared-cam scan.
DispatchCamToCamCoreAZ::EntityId cam, AZ::EntityId camCoreEntityvoidForce a Cam Core to render the specified cam. Persists until release.
ReleaseCamCoreDispatchAZ::EntityId camCoreEntityvoidClear override; restore arbitration.
SetActiveChannelChannelIdvoidResolves the channel’s Cam Core and delegates to SetActiveCamCore.
SetActiveCamCoreAZ::EntityId camCoreEntityvoidCalls MakeActiveView. Idempotent.
GetActiveChannelChannelIdReturns the channel owning the engine’s active camera.
GetActiveCamCoreAZ::EntityIdDirect passthrough to the engine.

Group target registry

MethodParametersReturnsDescription
RegisterGroupTargetAZStd::string name, AZ::EntityId groupEntityvoidCalled by GroupTargetComponent::Activate.
UnregisterGroupTargetAZ::EntityId groupEntityvoidCalled by GroupTargetComponent::Deactivate.
FindGroupTargetByNameAZStd::string nameAZ::EntityIdResolves a group entity for stages set to CamTargetMode::GroupTarget.
GetRegisteredGroupTargetNamesAZStd::vector<AZStd::string>Returns the full list.

Notification Bus: CamManagerNotificationBus

Events broadcast by the Cam Manager. Multiple handler bus — any number of components can subscribe. Extends GS_Core::ManagerBaseNotifications.

Legacy (instancing OFF)

EventDescription
EnableCameraSystemFired when the camera system is fully initialized.
DisableCameraSystemFired when the camera system is shutting down.
SettingNewCam(targetCam)A new phantom camera became dominant in channel 0. The Cam Core listens for this to begin blending.

Channel-aware (instancing ON)

EventDescription
SettingNewCamOnChannel(channelId, targetCam)Per-channel arbitration produced a new winner. Replaces the legacy SettingNewCam when instancing is on.
ChannelSpawned(channelId, camCoreEntity)A configured channel finished spawning AND its Cam Core self-registered. Listeners can treat this as a ready-to-render signal.
ChannelDespawned(channelId)Fires before entity teardown when a channel rig is despawned.
OnAllChannelsActivatedSharedCam(sharedCam)Edge-triggered: every active channel simultaneously selected the same shared TrueUnique cam. Typical cause: a shared cinematic cam paired with a Group Target at convergence radius.
OnCamCoreDispatched(camCoreEntity, dispatchedCam)A cross-channel dispatch override was applied.
OnCamCoreDispatchReleased(camCoreEntity)Dispatch override cleared.
OnActiveCameraChanged(channelId, camCoreEntity)The engine’s main-view active camera changed via this API. channelId == InvalidChannelId when the active cam isn’t channel-bound. External MakeActiveView calls outside this API are not observed.

Virtual Methods

Override these when extending the Cam Manager. Always call the base implementation.

MethodParametersReturnsDescription
EvaluatePriority()voidTop-level arbitration entry point. Iterates every active channel. Override to add system-wide rules (gameplay-state gates, distance weighting, lock rules).
EvaluatePriorityForChannel(channelId)ChannelIdAZ::EntityIdPer-channel arbitration. Computes effective = base + sum(influences) and returns the winner. Override for per-channel custom logic in Tier 3 projects.

Extending the Cam Manager

Extend the Cam Manager to add custom priority logic, additional registration behavior, or project-specific camera selection rules. Extension is done in C++.

Header (.h)

#pragma once
#include <GS_PhantomCam/GS_CamManagerBus.h>
#include <Source/GS_CamManagerComponent.h>

namespace MyProject
{
    class MyCamManager : public GS_PhantomCam::GS_CamManagerComponent
    {
    public:
        AZ_COMPONENT_DECL(MyCamManager);

        static void Reflect(AZ::ReflectContext* context);

    protected:
        void EvaluatePriority() override;
        AZ::EntityId EvaluatePriorityForChannel(GS_PhantomCam::ChannelId channelId) override;
    };
}

Implementation (.cpp)

#include "MyCamManager.h"
#include <AzCore/Serialization/SerializeContext.h>

namespace MyProject
{
    AZ_COMPONENT_IMPL(MyCamManager, "MyCamManager", "{YOUR-UUID-HERE}");

    void MyCamManager::Reflect(AZ::ReflectContext* context)
    {
        if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
        {
            serializeContext->Class<MyCamManager, GS_PhantomCam::GS_CamManagerComponent>()
                ->Version(0);

            if (AZ::EditContext* editContext = serializeContext->GetEditContext())
            {
                editContext->Class<MyCamManager>("My Cam Manager", "Custom camera manager")
                    ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
                        ->Attribute(AZ::Edit::Attributes::Category, "MyProject")
                        ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC_CE("Game"));
            }
        }
    }

    void MyCamManager::EvaluatePriority()
    {
        // Call base to run standard per-channel arbitration.
        GS_CamManagerComponent::EvaluatePriority();

        // Custom system-wide logic here.
    }

    AZ::EntityId MyCamManager::EvaluatePriorityForChannel(GS_PhantomCam::ChannelId channelId)
    {
        // Apply per-channel rules before falling back to base arbitration.
        // Example: lock to a specific cam during a scripted sequence.
        // if (m_lockedCam.IsValid()) return m_lockedCam;
        return GS_CamManagerComponent::EvaluatePriorityForChannel(channelId);
    }
}

Script Canvas Examples

Enabling and disabling the camera system:

Setting the global camera target:

Reacting to a new dominant camera:


See Also

For related PhantomCam components:

For foundational systems:

For conceptual overviews and usage guides:


Get GS_PhantomCam

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

1 - Channels & Instancing

PhantomCam channel system — per-player viewpoint slots, scope enum, ChannelStampComponent, rig spawn lifecycle, cross-channel dispatch, active main-view selection.

A channel is one player viewpoint slot. Each channel owns its own rig (spawned from a prefab), its own Cam Core, zero or more Phantom Cameras registered to it, a bound target, and its own priority table. Channel 0 (DefaultChannelId) is the implicit default for all legacy / single-player flows.

The channel system progressively discloses across three authoring tiers — Tier 1 single-cam, Tier 2 single-player rig, Tier 3 multi-channel. The per-cam authoring surface is identical across tiers; only the Cam Manager configuration changes.

Pending — multi-view rendering (I.9b). The channel system architecturally supports per-channel render targets via AttachmentImage assets, but the Atom RPI binding has not landed yet. Until it does, the engine renders one channel at a time even when multiple are arbitrating internally. The active main-view selector decides which channel reaches the engine framebuffer; see Active Main View.

 

Contents


Three Authoring Tiers

The Cam Manager exposes three top-level inspector fields that control the system tier. See Cam Manager — Authoring Tiers for the full author surface.

TierMaster GateChannel CountUse case
Tier 1 — Single playerm_enableInstancedChannels = false, m_primaryRigPrefab set1 (channel 0)Most projects. Spawn one rig from the primary prefab.
Tier 2 — Single player, level-placed CamCorem_enableInstancedChannels = false, no primary prefab1 (channel 0)Legacy. Author hand-places Cam Core in the level.
Tier 3 — Multi-channelm_enableInstancedChannels = true, m_channelConfigs populatedN (lobby-driven)Split-screen, per-player rigs, cross-channel cinematic dispatch.

The Master Gate is m_enableInstancedChannels. When OFF, channel-aware fields are hidden in the inspector and only the legacy bus signatures (SetTarget, SettingNewCam) route through. When ON, the full channel surface is live and channel-aware notifications (SettingNewCamOnChannel) replace the legacy ones.


ChannelId

using ChannelId = AZ::u32;
static constexpr ChannelId InvalidChannelId = static_cast<ChannelId>(-1);
static constexpr ChannelId DefaultChannelId = 0;

Channel ids are integers (0 … max − 1). The default channel id is 0. Legacy single-arg bus calls (SetTarget(target), RegisterPhantomCam(cam)) forward to channel 0.

Logical cam names — the entity names of phantom cameras inside a rig prefab — are scoped within their channel. The same name can appear in all four channels’ rigs without collision. Authors never type _i1 / _i2 suffixes.


Cam Channel Scope

Each Phantom Camera authors a CamChannelScope that decides how it participates in the channel system:

enum class CamChannelScope : AZ::u8 {
    Local       = 0,  // Default. Lives in its owning channel's scope.
    AllChannels = 1,  // In-rig natural duplicate — every rig instance carries one.
    TrueUnique  = 2,  // Exactly one instance total; bound or shared.
};
ScopeStamp-walk behaviorUse for
Local (default)Walks ancestors for a ChannelStampComponent. Found → registers to that channel via RegisterPhantomCamForChannel. No stamp → legacy RegisterPhantomCam (channel 0).Cams inside a rig prefab. Each spawned rig instance hosts its own copy.
AllChannelsSame stamp-walk as Local for the in-rig case. Out-of-rig (no stamp ancestor) warns and falls back to channel 0 (full runtime cloning is deferred).Per-player tailored broadcast cam inside a rig prefab.
TrueUniqueBypasses the stamp-walk. Sub-modes via the fields below.Hero-perspective or shared cinematic cams that need explicit binding.

TrueUnique sub-fields (visible only when scope is TrueUnique):

FieldVisibilityPurpose
m_boundChannelIdAlways when TrueUniqueExplicit channel binding for direct mode.
m_allChannelsShareWhen m_showAdvanced is trueShared mode — cam appears in every active channel’s priority table.
m_showAdvancedWhen TrueUniqueReveals advanced fields.

Author recipes:

  • Cam inside a rig prefab — leave as Local.
  • Per-player tailored broadcast cam in the levelAllChannels (in-rig); deferred for out-of-rig.
  • Hero-perspective cam for a specific playerTrueUnique + m_boundChannelId = N.
  • Shared cinematic collapse camTrueUnique + advanced + m_allChannelsShare = true. Pair with a Group Target to trigger OnAllChannelsActivatedSharedCam.

ChannelStampComponent

Pure runtime plumbing. Marks an entity (typically a rig root) with a (ChannelId, stampToken) pair. Phantom Camera and Cam Core components walk transform ancestors looking for this stamp at activate time to determine which channel they belong to.

Authored visibility: none. The stamp is reflected for serialization only — no EditContext block. The author never sees or sets it; the Cam Manager auto-attaches one during the spawn pre-insertion callback and calls StampChannel(channelId) to bump the token.

Why the token

m_stampToken bumps on every StampChannel(id) call (including idempotent re-stamps). The Cam Manager validates the supplied (channelId, token) against the live stamp on every registration call — mismatched tokens are rejected with a warning. This prevents stale messages from a previous stamp generation overwriting fresh bindings.

Buses

BusTypePurpose
ChannelStampRequestBusPer-entity, read-sideGetStampedChannelId, GetStampToken, IsStamped. Presence-tested via HasHandlers.
ChannelStampNotificationBusPer-entity, multi-handlerOnStamped(channelId, token) — every PhantomCam / CamCore beneath the stamp subscribes.

Static helper

static AZ::EntityId ChannelStampComponent::FindStampAncestor(AZ::EntityId start);

Walks transform ancestors via the request bus and returns the closest stamped entity (or invalid id if no stamp ancestor).


Rig Prefab Resolution

Authored rig prefab in the O3DE Editor

Three inspector fields on the Cam Manager control where each channel’s rig comes from:

FieldTierPurpose
m_primaryRigPrefabAlways visibleThe default rig. Tier 1 / 2 single-rig source. In Tier 3 it is the universal fallback for any channel whose own override is empty.
m_channelConfigs[i].m_rigPrefabTier 3 (master gate ON)Per-channel override. Inspector label: “Rig Override Prefab”. Leave empty to inherit the primary.
m_enableInstancedChannelsAlways visibleMaster gate.

The Cam Manager’s GetEffectiveRigPrefab(channelId) returns:

  1. m_channelConfigs[channelId].m_rigPrefab if set.
  2. Else m_primaryRigPrefab.
  3. Else invalid (the channel emits a warning at spawn time and stays inert).

Symmetric multi-channel co-op (all players use the same rig) sets the primary once; every channel inherits. Asymmetric setups (e.g. P1 third-person, P2 top-down) set per-channel overrides.


Spawn Pipeline

The Cam Manager spawns configured rigs on startup (driven by OnStartupComplete) and re-spawns them on stage transitions.

OnStartupComplete:
  if m_enableInstancedChannels == false:
    Tier 1: m_primaryRigPrefab valid → SpawnChannelRig(DefaultChannelId, m_primaryRigPrefab)
    Tier 2: no spawn (assumes level-placed CamCore)
  else:
    Tier 3: iterate m_channelConfigs[0 .. min(size, m_activeChannelCount)]
      for each PerChannelInstance config with m_enabledByDefault and valid prefab:
        SpawnChannelRig(channelId, GetEffectiveRigPrefab(channelId))

SpawnChannelRig uses an EntitySpawnTicket with two callbacks:

  1. Pre-insertion callback (entities created, not yet activated):
    • Cam Manager identifies the spawn root (first entity in the spawn container).
    • Attaches a ChannelStampComponent to it.
    • Calls StampChannel(channelId) — bumps the token, fires OnStamped to any subscribers.
  2. Completion callback (entities activated):
    • Cam Manager walks activated entities into channel.m_rigSpawnedEntities.
    • Marks the channel m_active = true.
    • Folds any TrueUnique-shared cams into the new channel’s priority table.
    • Validates that a Cam Core registered (warns if not).
    • Broadcasts ChannelSpawned(channelId, camCoreEntity).
    • Re-runs EvaluatePriority(channelId) so the channel’s arbitration result reaches its Cam Core.

Stage transitions

BeginLoadStage    → DespawnAllChannelRigs (lock arbitration first)
LoadStageComplete → SpawnAllConfiguredRigs BEFORE broadcasting HandleStartup
                    so fresh-stage cams come up in the startup wave.

Stamp-Walk Registration

When a Phantom Camera (or Cam Core) activates, it walks ancestors for a ChannelStampComponent and branches:

RegisterWithCamManager:
   branch on m_channelScope (PhantomCam only — CamCore is always Local-equivalent):

   ┌── Local:
   │      stamp = FindStampAncestor(myEntity)
   │      if stamp valid:
   │         RegisterPhantomCamForChannel(myEntity, stamp, channelId, token)
   │         registrationMode = StampAware
   │      else:
   │         RegisterPhantomCam(myEntity)             // legacy channel 0
   │         registrationMode = LegacyChan0
   ├── AllChannels:
   │      stamp = FindStampAncestor(myEntity)
   │      if stamp valid:
   │         RegisterPhantomCamForChannel(myEntity, stamp, channelId, token)
   │         registrationMode = StampAware
   │      else:
   │         warn (out-of-rig cloning deferred)
   │         fall back to legacy channel 0
   │         registrationMode = LegacyChan0
   └── TrueUnique:
          if m_boundChannelId valid AND !m_allChannelsShare:
             RegisterPhantomCamDirect(myEntity, m_boundChannelId)
             registrationMode = Direct
          elif !m_boundChannelId AND m_allChannelsShare:
             RegisterPhantomCamShared(myEntity)
             registrationMode = (Shared, stored as Direct for unregister routing)
          else:
             warn (fragile manual-control mode)
             stay unregistered
             registrationMode = None

registrationMode is cached on the component so UnregisterFromCamManager can route symmetrically.

OnStamped — mid-session re-stamp handling

If a rig is re-stamped mid-session (rare; typically only during stage transitions or hot-reload), the cam handles it:

OnStamped(newChannelId, newToken):
   Only acts in StampAware or None modes.
   In Direct or LegacyChan0 modes, the signal is ignored  author intent
   takes precedence over stamps.

   UnregisterFromCamManager (old binding)
   m_resolvedChannelId  = newChannelId
   m_resolvedStampToken = newToken
   RegisterWithCamManager (new binding)

The Cam Core mirrors the same pattern.


Shared Cams and Collapse Detection

RegisterPhantomCamShared(cam) adds the cam to every active channel’s priority table. The Cam Manager tracks the list in m_sharedCams. A shared cam:

  • Wins arbitration in every channel where it has the highest priority.
  • Gains focus from any channel selecting it.
  • Never DROPS focus via channel notifications — a shared cam losing in channel N doesn’t preclude winning in channel M.

Collapse detection

EvaluatePriority collects per-channel winners. If every active channel picked the same cam AND that cam is in m_sharedCams, the Cam Manager edge-triggers OnAllChannelsActivatedSharedCam(sharedCam) via m_lastSharedCollapsedCam — fires once on entry into the condition, and once on cam-A → cam-B shared-cam transitions.

Typical use case: a shared cinematic cam paired with a Group Target tracking all players. When players converge such that the shared cam wins in every channel, UI receives the collapse signal and can switch from split-screen layout to single-view layout.

No counterpart “uncollapsed” event exists — listeners dedupe locally if they care about exit.


Cross-Channel Dispatch

For cinematic / cross-channel scenarios — e.g. player 3’s wide cam should temporarily render on player 1’s Cam Core — the Cam Manager exposes a dispatch override.

MethodUse
FindCamForTarget(logicalName, targetEntity)Multi-tier resolution. (1) if targetEntity is channel-bound, look up logicalName in that channel’s m_logicalToEntity. (2) Walk m_sharedCams matching by entity name. (3) Reserved for AllChannels-duplicate fallback. Returns invalid EntityId on no match.
DispatchCamToCamCore(cam, camCoreEntity)Force the Cam Core to render the specified cam. Persists until release. Fires OnCamCoreDispatched.
ReleaseCamCoreDispatch(camCoreEntity)Clear the override; re-run arbitration so the Cam Core routes back to its arbitrated winner. Fires OnCamCoreDispatchReleased.

Channel arbitration still runs internally during dispatch. SettingNewCamOnChannel continues to fire with the arbitrated winner — only the physical Cam Core route is overridden. This means downstream listeners still see what “would have” arbitrated, useful for HUD or AI behavior that shouldn’t follow the cinematic override.


Active Main View

Wraps O3DE’s AzFramework::Camera::CameraRequests::MakeActiveView so the channel system can orchestrate “this player’s view is on main right now” without callers reaching into camera bus plumbing. Orthogonal to dispatch (what cam) and render-target binding (where pixels go).

MethodUse
SetActiveChannel(channelId)Resolves the channel’s Cam Core, delegates to SetActiveCamCore.
SetActiveCamCore(camCoreEntity)Calls MakeActiveView. Idempotent — silently no-ops if already active.
GetActiveChannel()Reverse query: returns the channel owning the engine’s active camera, or InvalidChannelId if the active cam isn’t channel-bound.
GetActiveCamCore()Direct passthrough to the engine.

Listeners receive OnActiveCameraChanged(channelId, camCoreEntity). channelId == InvalidChannelId when the active cam isn’t channel-bound (e.g. a direct SetActiveCamCore to an external entity). External MakeActiveView calls made outside this API are not observed — subscribe to AzFramework::Camera::CameraNotificationBus directly if you need to detect engine-level changes.


Runtime Channel API

MethodUse
SetActiveChannelCount(count)Lobby-driven cap. Pre-startup only. Mid-session calls warn and are ignored — use EnableChannel / DisableChannel instead.
GetActiveChannelCount()Query.
EnableChannel(channelId)Spawn a configured channel mid-session. Validates id in bounds, not already spawned, policy is PerChannelInstance, rig prefab assigned. Fires ChannelSpawned.
DisableChannel(channelId)Despawn. Fires ChannelDespawned. No-op if not spawned.
GetChannelCamCore(channelId)Returns the bound Cam Core EntityId, invalid if no Cam Core.

Intended lobby flow: author sets m_channelConfigs to N (primed max), pre-startup the lobby calls SetActiveChannelCount(actualPlayerCount), then OnStartupComplete spawns just actualPlayerCount rigs. Mid-session join / drop uses EnableChannel / DisableChannel.


Notifications

Channel-related events on CamManagerNotificationBus. See Cam Manager — Notifications for the full notification surface.

EventWhen
SettingNewCamOnChannel(channelId, targetCam)Per-channel arbitration produced a new winner. Replaces legacy SettingNewCam when instancing is ON.
ChannelSpawned(channelId, camCoreEntity)A configured channel finished spawning AND its Cam Core self-registered. Ready-to-render signal.
ChannelDespawned(channelId)Fires before entity teardown when a channel rig is despawned.
OnAllChannelsActivatedSharedCam(sharedCam)Edge-triggered. Every active channel simultaneously selected the same shared TrueUnique cam.
OnCamCoreDispatched(camCoreEntity, dispatchedCam)A cross-channel dispatch override was applied.
OnCamCoreDispatchReleased(camCoreEntity)Dispatch override cleared.
OnActiveCameraChanged(channelId, camCoreEntity)Engine main-view active camera changed via this API.

Tier 3 Author Workflow

  1. On the Cam Manager: toggle m_enableInstancedChannels = true.
  2. Set m_primaryRigPrefab to the project’s default rig (used as universal fallback).
  3. Populate m_channelConfigs with one entry per supported player slot:
    • Set m_channelName for inspector readability (“P1”, “P2”, …).
    • Optionally set m_rigPrefab (the “Rig Override Prefab”) — leave empty to inherit the primary.
    • Set m_enabledByDefault = true for slots that should spawn on startup.
  4. Optionally set m_activeChannelCount for the lobby cap, or call SetActiveChannelCount at runtime before startup.
  5. Per-player cams placed inside the rig prefab will auto-stamp on spawn — no per-cam authoring required for normal Local-scope cams.
  6. For shared cinematic cams (one cam to rule them all), set the cam’s m_channelScope = TrueUnique + m_showAdvanced = true + m_allChannelsShare = true.
  7. For author-explicit per-channel cams (rare), use m_channelScope = TrueUnique + m_boundChannelId = N + m_allChannelsShare = false.

For the basics-side walkthrough with screenshots and step-by-step recipes, see The Basics: Channels & Instancing.


Naming Collision Callout

Tug-field m_channels ≠ instancing ChannelId. Tug-field channels are arbitrary string tags (“Cinematic”, “Combat”) that match tug volumes to listeners — see Tug Fields. Instancing channels documented on this page are integer ChannelIds for per-player viewpoint routing. The two systems are unrelated despite the shared word. The system uses the same identifier name in the codebase for both; the docs make the distinction prominent here and in the tug-fields page.


See Also

For related PhantomCam pages:

For the basics-side walkthrough:


Get GS_PhantomCam

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