Play animation can blend into the new animation over a number of frames (#106)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
beeea355d9
commit
04dc254b3d
4 files changed
+177
-14
No files matched your search
@@ -4,6 +4,10 @@ import {
|
||||
animatableDefsOf,
|
||||
animatedKind,
|
||||
animationPlayheadAt,
|
||||
blendShare,
|
||||
blends,
|
||||
blendValue,
|
||||
fpsOf,
|
||||
isPositionRow,
|
||||
playbackOf,
|
||||
trackWriteAt,
|
||||
@@ -34,8 +38,8 @@ interface Binding {
|
||||
|
||||
/**
|
||||
* Plays what `Play animation` started. Players run in start order, so of two setting one property
|
||||
* the later start wins. Runs before the BehaviorSystem: a behavior writing the same property writes
|
||||
* last, and hears this frame's `finished`.
|
||||
* the later start wins – or, while it blends in, mixes over what was there. Runs before the
|
||||
* BehaviorSystem: a behavior writing the same property writes last, and hears this frame's `finished`.
|
||||
*/
|
||||
export class AnimationSystem implements System {
|
||||
finished: AnimationFinish[] = [];
|
||||
@@ -46,6 +50,8 @@ export class AnimationSystem implements System {
|
||||
for (const entity of world.entities)
|
||||
for (const player of entity.animations ?? []) running.push({ entity, player });
|
||||
running.sort((a, b) => a.player.order - b.player.order);
|
||||
// What the players before this one wrote this tick – a blend mixes from that value, live.
|
||||
const written = new Set<string>();
|
||||
for (const { entity, player } of running) {
|
||||
const asset = findAssetOf(world.project, player.animationId, 'animation');
|
||||
if (!asset) {
|
||||
@@ -53,16 +59,32 @@ export class AnimationSystem implements System {
|
||||
continue;
|
||||
}
|
||||
const bindings = bind(world, asset, entity);
|
||||
const blend = player.blend;
|
||||
if (blend && !blend.from) {
|
||||
blend.from = {};
|
||||
for (const b of bindings) blend.from[b.base] = b.read();
|
||||
}
|
||||
if (!player.bases) {
|
||||
// The first tick only takes the starting values and shows frame 0; the clock runs from the next.
|
||||
player.bases = {};
|
||||
for (const b of bindings) player.bases[b.base] = b.read();
|
||||
} else if (player.playing && isHandleEnabled(entity)) player.time += dt;
|
||||
const head = animationPlayheadAt(asset, player.time);
|
||||
for (const b of bindings) {
|
||||
const value = trackWriteAt(b.track, b.kind, head.frame, player.bases[b.base], head.reverse);
|
||||
if (value !== undefined) b.write(value);
|
||||
} else if (player.playing && isHandleEnabled(entity)) {
|
||||
player.time += dt;
|
||||
if (blend) blend.elapsed += dt;
|
||||
}
|
||||
const head = animationPlayheadAt(asset, player.time);
|
||||
const share = blend ? blendShare(blend, fpsOf(asset)) : 1;
|
||||
for (const b of bindings) {
|
||||
let value = trackWriteAt(b.track, b.kind, head.frame, player.bases[b.base], head.reverse);
|
||||
if (value === undefined) continue;
|
||||
if (share < 1 && blends(b.kind)) {
|
||||
const from = written.has(b.base) ? b.read() : blend?.from?.[b.base];
|
||||
if (from !== undefined) value = blendValue(b.kind, from, value, share);
|
||||
}
|
||||
b.write(value);
|
||||
written.add(b.base);
|
||||
}
|
||||
if (blend && share >= 1) delete player.blend;
|
||||
if (!head.finished) continue;
|
||||
if (playbackOf(asset) === 'once-reset')
|
||||
for (const b of bindings) {
|
||||
|
||||
Reference in new issue
Block a user