diff --git a/packages/core/src/animation/AnimatorController.ts b/packages/core/src/animation/AnimatorController.ts index 60bc43921..bc799feff 100644 --- a/packages/core/src/animation/AnimatorController.ts +++ b/packages/core/src/animation/AnimatorController.ts @@ -19,6 +19,9 @@ export class AnimatorController extends ReferResource { _layersMap: Record = {}; private _updateFlagManager: UpdateFlagManager = new UpdateFlagManager(); + private _onStateMachineChanged = (): void => { + this._updateFlagManager.dispatch(); + }; /** * The layers in the controller. @@ -112,6 +115,7 @@ export class AnimatorController extends ReferResource { addLayer(layer: AnimatorControllerLayer): void { this._layers.push(layer); this._layersMap[layer.name] = layer; + layer.stateMachine._setChangeCallback(this._onStateMachineChanged); layer._setEngine(this._engine); this._updateFlagManager.dispatch(); } @@ -123,6 +127,7 @@ export class AnimatorController extends ReferResource { removeLayer(layerIndex: number): void { const theLayer = this.layers[layerIndex]; this._layers.splice(layerIndex, 1); + theLayer.stateMachine._setChangeCallback(null); delete this._layersMap[theLayer.name]; this._updateFlagManager.dispatch(); } @@ -131,6 +136,10 @@ export class AnimatorController extends ReferResource { * Clear layers. */ clearLayers(): void { + const { _layers: layers } = this; + for (let i = 0, n = layers.length; i < n; i++) { + layers[i].stateMachine._setChangeCallback(null); + } this._layers.length = 0; for (let name in this._layersMap) { delete this._layersMap[name]; @@ -151,6 +160,7 @@ export class AnimatorController extends ReferResource { _setEngine(engine: Engine): void { const { _layers: layers } = this; for (let i = 0, n = layers.length; i < n; i++) { + layers[i].stateMachine._setChangeCallback(this._onStateMachineChanged); layers[i]._setEngine(engine); } } diff --git a/packages/core/src/animation/AnimatorStateMachine.ts b/packages/core/src/animation/AnimatorStateMachine.ts index 3200aab5d..9c9990dd1 100644 --- a/packages/core/src/animation/AnimatorStateMachine.ts +++ b/packages/core/src/animation/AnimatorStateMachine.ts @@ -14,6 +14,7 @@ export class AnimatorStateMachine { readonly states: AnimatorState[] = []; private _engine: Engine; + private _onChanged: (() => void) | null = null; /** * The state will be played automatically. @@ -72,6 +73,7 @@ export class AnimatorStateMachine { if (this.defaultState === state) { this.defaultState = null; } + this._onChanged?.(); } } @@ -170,4 +172,11 @@ export class AnimatorStateMachine { states[i]._setEngine(engine); } } + + /** + * @internal + */ + _setChangeCallback(onChanged: (() => void) | null): void { + this._onChanged = onChanged; + } } diff --git a/tests/src/core/Animator.test.ts b/tests/src/core/Animator.test.ts index 4dc0333dd..cb2534e2b 100644 --- a/tests/src/core/Animator.test.ts +++ b/tests/src/core/Animator.test.ts @@ -1487,6 +1487,59 @@ describe("Animator test", function () { } }); + it("removing a state invalidates cached layer data before the state name is reused", () => { + const entity = new Entity(engine); + const localAnimator = entity.addComponent(Animator); + const controller = new AnimatorController(engine); + const layer = new AnimatorControllerLayer("layer"); + controller.addLayer(layer); + + const oldState = layer.stateMachine.addState("Temp"); + const oldClip = new AnimationClip("old-temp-clip"); + const oldCurve = new AnimationFloatCurve(); + const oldStart = new Keyframe(); + const oldEnd = new Keyframe(); + oldStart.time = 0; + oldStart.value = 0; + oldEnd.time = 1; + oldEnd.value = 1; + oldCurve.addKey(oldStart); + oldCurve.addKey(oldEnd); + oldClip.addCurveBinding("", Transform, "position.x", oldCurve); + oldState.clip = oldClip; + localAnimator.animatorController = controller; + + try { + localAnimator.play("Temp"); + let layerData = localAnimator["_animatorLayersData"][0]; + expect(layerData.animatorStateDataMap.has(oldState)).to.eq(true); + + layer.stateMachine.removeState(oldState); + const newState = layer.stateMachine.addState("Temp"); + const newClip = new AnimationClip("new-temp-clip"); + const newCurve = new AnimationFloatCurve(); + const newStart = new Keyframe(); + const newEnd = new Keyframe(); + newStart.time = 0; + newStart.value = 0; + newEnd.time = 1; + newEnd.value = 2; + newCurve.addKey(newStart); + newCurve.addKey(newEnd); + newClip.addCurveBinding("", Transform, "position.x", newCurve); + newState.clip = newClip; + + localAnimator.play("Temp"); + layerData = localAnimator["_animatorLayersData"][0]; + + expect(localAnimator.getCurrentAnimatorState(0)._state).to.eq(newState); + expect(layerData.animatorStateDataMap.has(oldState)).to.eq(false); + expect(layerData.animatorStateDataMap.has(newState)).to.eq(true); + } finally { + entity.destroy(); + } + }); + it("Clone", () => { expect(animator.entity.clone().getComponent(Animator).animatorController).to.eq(animator.animatorController); });