Vulkan Schnee 0.0.1 (22840dc8)
High-performance rendering engine
Loading...
Searching...
No Matches
Actor Attachments

Actor attachments reparent scene graph nodes while preserving either world-space or local-space transforms.

Use attachments for grouped gameplay objects, actor-owned UI, weapons, hands, sockets, and temporary parent-child relationships.

APIs Used

Task API
Transform preservation rule Engine::Entities::AttachmentTransformRule
Reparent a scene node Engine::Entities::SceneNode::reparentTo(parent, rule)
Attach actor to actor Engine::Entities::Actor::attachTo(parentActor, rule)
Attach actor to node Engine::Entities::Actor::attachTo(parentNode, rule)
Detach actor to scene root Engine::Entities::Actor::detach(rule)
Spawn actor under actor Engine::Entities::Scene::spawnActorAttached<T>(localTransform, parentActor, args...)
Spawn actor under node Engine::Entities::Scene::spawnActorAttached<T>(localTransform, parentNode, args...)

Transform Rules

AttachmentTransformRule controls which transform stays unchanged during attachment or detachment.

Rule Behavior
KeepWorld Keeps the actor visually in place. The local matrix changes to compensate for the new parent.
KeepLocal Keeps the current local matrix. The actor moves with the new parent relationship.

KeepWorld is the default for reparentTo, attachTo, and detach.

SceneNode::reparentTo(parent, rule)

reparentTo safely moves a node from its current parent to a new parent.

childNode->reparentTo(parentNode, Engine::Entities::AttachmentTransformRule::KeepWorld);
@ KeepWorld
Preserve the node's world transform while recomputing its local transform.
Definition SceneGraph.h:19

The new parent must be non-null. The node cannot be reparented to itself or to one of its descendants.

KeepWorld caches the current world matrix before graph mutation, reparents the node, then restores that world matrix. KeepLocal keeps the existing local matrix and marks the node hierarchy dirty.

Do not use SceneNode::addChild() for gameplay reparenting. addChild() is a low-level graph operation and does not remove the node from its old parent.

Actor::attachTo(parent, rule)

attachTo(Actor&) reparents this actor's scene node under another actor's scene node.

Use KeepWorld when the actor should stay where it is while ownership changes.

Use KeepLocal when the actor already has the desired local offset from the new parent.

Engine::Ecs::Transform localGripTransform;
localGripTransform.setPosition({ 0.25f, -0.1f, -0.4f });
auto* weapon = spawnActor<WeaponActor>(localGripTransform);
@ KeepLocal
Preserve the node's local transform relative to the new parent.
Definition SceneGraph.h:20
Local transform matrix and helpers for translation, rotation, and scale.
Definition EcsData.h:476
void setPosition(const glm::vec3 &newPosition)
Sets local position and preserves rotation and scale.

attachTo(SceneNode) attaches to non-actor nodes.

labelActor->attachTo(customUiPivotNode, Engine::Entities::AttachmentTransformRule::KeepLocal);

Actor::detach(rule)

detach() reparents the actor to the active scene root.

detach(KeepWorld) keeps the actor visually in place after removing the parent.

detach(KeepLocal) keeps the actor's local matrix and makes it local to the scene root.

Scene::spawnActorAttached<T>()

spawnActorAttached creates an actor node directly under the supplied parent node.

The transform parameter is a local transform relative to the parent.

Engine::Ecs::Transform localTransform;
localTransform.setPosition({ 0.0f, 1.0f, -0.5f });
auto* card = spawnActorAttached<UpgradeChoiceActor>(
localTransform,
*selectionActor,
cardData,
choiceIndex
);

Use spawnActorAttached instead of spawning at world origin and attaching with KeepLocal.

The function follows the same actor lifecycle as spawnActor<T>(): it creates a scene node, constructs the actor, stores the actor in the scene, then calls beginPlay().

Ownership And Destruction

Attachments only change scene graph hierarchy. They do not change actor ownership.

Scene owns all actors it spawns. Destroy child actors explicitly when a parent actor owns gameplay state for them.

void UpgradeSelectionActor::endPlay()
{
for (UpgradeChoiceActor* choice : choices_) {
if (choice != nullptr) {
choice->destroyActorImmediate();
}
}
choices_.clear();
Actor::endPlay();
}

Use destroyActor() from gameplay callbacks during tick. Use destroyActorImmediate() for teardown paths where no actor is currently executing logic.

Common Patterns

Spawn A Grouped UI

Engine::Ecs::Transform panelTransform;
panelTransform.setPosition({ 0.0f, 1.0f, -2.5f });
auto* panel = spawnActor<UpgradeSelectionActor>(panelTransform);
for (size_t i = 0; i < cards.size(); ++i) {
Engine::Ecs::Transform cardTransform;
cardTransform.setPosition({ (static_cast<float>(i) - 1.0f) * 1.75f, 0.0f, 0.0f });
auto* choice = spawnActorAttached<UpgradeChoiceActor>(
cardTransform,
*panel,
cards[i],
static_cast<uint32_t>(i)
);
}

The choices move when the panel moves because their transforms are local to the panel actor.

Pick Up An Existing Actor

The item stays at the same world transform when the parent changes.

Attach With A Known Offset

item->getSceneNode()->setLocalModelMatrix(gripOffsetMatrix);

The item uses gripOffsetMatrix relative to the hand.

Failure Cases

SceneNode::reparentTo rejects invalid graph mutations.

Case Result
newParent == nullptr Throws std::invalid_argument
Reparent to self Throws std::invalid_argument
Reparent to descendant Throws std::invalid_argument

Actor attachment wrappers forward to SceneNode::reparentTo, so the same rules apply.

Related Documentation