Place compositions in scenes and play them from behaviors (#417)
A composition can now sit in a scene like a container and play there. Its items become real objects while they run, so behaviors, colliders and events work on them as on any other object. - A composition is placeable with its own symbol, transform and render layer. The scene view shows its picture at frame 0. A click on that picture selects the placement, not a single item. - CompositionSystem spawns each item as an object under the placement when it begins, with the id <placement>~<item>. On start and the other events run. The object is destroyed when the item ends. - The top track lies in front no matter which item started first. World.arrange() orders the entity list and the scene objects. - Clips and driven videos follow the same rules as compositionAt(). Loop, restart, stop and the game pause work. - New nodes: Play composition (with restart), Stop composition and the event On composition finished. They were planned for step 4 but come now, because without autoplay nothing could play otherwise. - The editor run ignores Play composition and says so in the log. It writes into the saved project, and the items would stay behind. - contentId() moved to instance.ts. The animation system uses it for nested ids too. - Compositions get no "Sort with parent" for now. Tests: the gate passed with 301 files and 4643 tests, 18 more than step 1. 13 of them are in tests/core/composition-play.test.ts. The first gate run failed in event-order.test.ts, which counts the event nodes and now finds 19. Five counter-checks each turned exactly their test red. Two order tests stayed green without the fix at first and were rebuilt. e2e 68 is new and passed with 5 own checks. Its counter-check without arrange() failed as expected. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
73e675835e
commit
0334d2508d
31 files changed
+1035
-33
No files matched your search
@@ -96,6 +96,14 @@ function driftedKeys(live: PropertyBag, model: ModelState): readonly string[] {
|
||||
return keys ?? NO_KEYS;
|
||||
}
|
||||
|
||||
/** `order`'s members of `list` in that order, in the slots of `list` they held before. */
|
||||
function reorderIn<T>(list: T[], order: readonly T[]): void {
|
||||
const slots = new Map(list.map((value, index) => [value, index]));
|
||||
const present = order.filter((value) => slots.has(value));
|
||||
const indices = present.map((value) => slots.get(value)!).sort((a, b) => a - b);
|
||||
indices.forEach((slot, i) => (list[slot] = present[i]!));
|
||||
}
|
||||
|
||||
/**
|
||||
* The running game: entities of one scene plus the systems ticking them. Built on the play copy
|
||||
* (discarded on stop) or, for the editor run, on the saved model, where its writes stay.
|
||||
@@ -383,6 +391,45 @@ export class World {
|
||||
return entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds objects a composition plays into the scene (#417), each below the parent it names, and
|
||||
* answers their entities. An object of the same id still in the scene – one taken away this tick –
|
||||
* gives way, so the scene never holds an id twice.
|
||||
*/
|
||||
place(objects: readonly SceneObject[]): Entity[] {
|
||||
const scene = findScene(this.project, this.sceneId);
|
||||
if (!scene) return [];
|
||||
const ids = new Set(objects.map((o) => o.id));
|
||||
for (let i = scene.objects.length - 1; i >= 0; i--)
|
||||
if (ids.has(scene.objects[i]!.id)) scene.objects.splice(i, 1);
|
||||
scene.objects.push(...objects);
|
||||
const placed: Entity[] = [];
|
||||
for (const object of objects) {
|
||||
const asset = findAsset(this.project, object.assetId);
|
||||
if (!asset || !isPlaceableType(asset.type)) continue;
|
||||
const entity = this.materialize(object, asset);
|
||||
entity.parent = this.findByObjectId(object.parentId) ?? null;
|
||||
placed.push(entity);
|
||||
}
|
||||
this.structureChanged = true;
|
||||
return placed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Puts these entities, and their objects in the scene, in this order among the places they already
|
||||
* hold – the order that decides between equal `z` (#417).
|
||||
*/
|
||||
arrange(order: readonly Entity[]): void {
|
||||
reorderIn(this.entities, order);
|
||||
const scene = findScene(this.project, this.sceneId);
|
||||
if (!scene) return;
|
||||
const byId = new Map(scene.objects.map((o) => [o.id, o]));
|
||||
reorderIn(
|
||||
scene.objects,
|
||||
order.map((e) => byId.get(e.objectId)).filter((o): o is SceneObject => o !== undefined),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the entities' state into the scene: positions, property values that differ from
|
||||
* what the model resolves (stored as overrides, so the inspector marks them) and removed
|
||||
|
||||
Reference in new issue
Block a user