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
@@ -436,6 +436,40 @@ export function materializeInstance(
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* The id the object at `path` below `ownerId` has – one level deeper when the owner is content
|
||||
* itself, so the content of `q~a` is `q~a/b`, the id a template path through nested containers gives.
|
||||
*/
|
||||
export function contentId(ownerId: string, path: string): string {
|
||||
const split = splitInstanceId(ownerId);
|
||||
return split
|
||||
? instanceChildId(split.instanceId, `${split.path}${PATH_SEP}${path}`)
|
||||
: instanceChildId(ownerId, path);
|
||||
}
|
||||
|
||||
/**
|
||||
* `object` as `id` below `parentId`, followed by its container content with the ids below that one –
|
||||
* how a composition puts its objects into a scene (#417). Copies, free to be stored and written to.
|
||||
*/
|
||||
export function placedWithContent(
|
||||
project: Project,
|
||||
object: SceneObject,
|
||||
id: string,
|
||||
parentId: string,
|
||||
): SceneObject[] {
|
||||
const rename = (at: string) => (at === object.id ? id : contentId(id, at.slice(object.id.length + 1)));
|
||||
const { groupId: _, ...top } = object;
|
||||
return resolveObjects(project, { objects: [{ ...top, parentId: '' }] }).map((o) => ({
|
||||
...o,
|
||||
id: rename(o.id),
|
||||
parentId: o.id === object.id ? parentId : rename(o.parentId),
|
||||
position: { ...o.position },
|
||||
editorOffset: { ...o.editorOffset },
|
||||
overrides: { ...o.overrides },
|
||||
instanceEdits: {},
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Spells out every live instance of the holder. The runtime needs it: entities are built from
|
||||
* stored objects, and a behavior may destroy or move one of them – which a resolved copy could
|
||||
|
||||
Reference in new issue
Block a user