Skip to content

Commit

Permalink
Fix animator play backwards error and onStateExit not triggered when …
Browse files Browse the repository at this point in the history
…crossFade finished and actualDeltaTime error (#2325)

* fix: animator play backwards error

* fix: onStateExit not called when crossFade finished but state not finished

* fix: actualDelta time calculate error when speed is 0
  • Loading branch information
luzhuang authored Aug 13, 2024
1 parent 475302c commit 588dc53
Show file tree
Hide file tree
Showing 5 changed files with 151 additions and 42 deletions.
38 changes: 38 additions & 0 deletions e2e/case/animator-play-backwards.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/**
* @title Animation Play Backwards
* @category Animation
*/
import { Animator, Camera, DirectLight, GLTFResource, Logger, Vector3, WebGLEngine } from "@galacean/engine";
import { OrbitControl } from "@galacean/engine-toolkit";
import { initScreenshot, updateForE2E } from "./.mockForE2E";

Logger.enable();
WebGLEngine.create({ canvas: "canvas" }).then((engine) => {
engine.canvas.resizeByClientSize(2);
const scene = engine.sceneManager.activeScene;
const rootEntity = scene.createRootEntity();

// camera
const cameraEntity = rootEntity.createChild("camera_node");
cameraEntity.transform.position = new Vector3(0, 1, 5);
const camera = cameraEntity.addComponent(Camera);
cameraEntity.addComponent(OrbitControl).target = new Vector3(0, 1, 0);

const lightNode = rootEntity.createChild("light_node");
lightNode.addComponent(DirectLight).intensity = 0.6;
lightNode.transform.lookAt(new Vector3(0, 0, 1));
lightNode.transform.rotate(new Vector3(0, 90, 0));

engine.resourceManager
.load<GLTFResource>("https://gw.alipayobjects.com/os/bmw-prod/5e3c1e4e-496e-45f8-8e05-f89f2bd5e4a4.glb")
.then((gltfResource) => {
const { defaultSceneRoot } = gltfResource;
rootEntity.addChild(defaultSceneRoot);
const animator = defaultSceneRoot.getComponent(Animator);
animator.findAnimatorState("walk").speed = -1;
animator.play("walk");
updateForE2E(engine);

initScreenshot(engine, camera);
});
});
5 changes: 5 additions & 0 deletions e2e/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ export const E2E_CONFIG = {
caseFileName: "animator-play",
threshold: 0.1
},
playBackWards: {
category: "Animator",
caseFileName: "animator-play-backwards",
threshold: 0.1
},
playBeforeActive: {
category: "Animator",
caseFileName: "animator-play-beforeActive",
Expand Down
3 changes: 3 additions & 0 deletions e2e/fixtures/originImage/Animator_animator-play-backwards.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
80 changes: 38 additions & 42 deletions packages/core/src/animation/Animator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -548,27 +548,25 @@ export class Animator extends Component {
srcPlayData.update(playDeltaTime);

const { clipTime, isForwards } = srcPlayData;
const { transitions } = state;
const { anyStateTransitions } = layer.stateMachine;

const transition =
this._applyTransitionsByCondition(
layerIndex,
layerData,
layer,
state,
layer.stateMachine.anyStateTransitions,
aniUpdate
) ||
this._applyStateTransitions(
layerIndex,
layerData,
layer,
isForwards,
srcPlayData,
state.transitions,
lastClipTime,
clipTime,
playDeltaTime,
aniUpdate
);
(anyStateTransitions.length &&
this._applyTransitionsByCondition(layerIndex, layerData, layer, state, anyStateTransitions, aniUpdate)) ||
(transitions.length &&
this._applyStateTransitions(
layerIndex,
layerData,
layer,
isForwards,
srcPlayData,
transitions,
lastClipTime,
clipTime,
playDeltaTime,
aniUpdate
));

let playCostTime: number;
if (transition) {
Expand Down Expand Up @@ -686,7 +684,7 @@ export class Animator extends Component {
: dstPlayDeltaTime;
}

const actualCostTime = dstPlaySpeed === 0 ? 0 : dstPlayCostTime / dstPlaySpeed;
const actualCostTime = dstPlaySpeed === 0 ? deltaTime : dstPlayCostTime / dstPlaySpeed;
const srcPlayCostTime = actualCostTime * srcPlaySpeed;

srcPlayData.update(srcPlayCostTime);
Expand All @@ -698,6 +696,7 @@ export class Animator extends Component {
const crossFadeFinished = crossWeight === 1.0;

if (crossFadeFinished) {
srcPlayData.playState = AnimatorStatePlayState.Finished;
this._preparePlayOwner(layerData, destState);
this._evaluatePlayingState(destPlayData, weight, additive, aniUpdate);
} else {
Expand Down Expand Up @@ -810,7 +809,7 @@ export class Animator extends Component {
: playDeltaTime;
}

const actualCostTime = playSpeed === 0 ? 0 : dstPlayCostTime / playSpeed;
const actualCostTime = playSpeed === 0 ? deltaTime : dstPlayCostTime / playSpeed;

destPlayData.update(dstPlayCostTime);

Expand Down Expand Up @@ -899,28 +898,25 @@ export class Animator extends Component {
playData.updateOrientation(actualDeltaTime);

const { clipTime, isForwards } = playData;
const { transitions } = state;
const { anyStateTransitions } = layer.stateMachine;

const transition =
this._applyTransitionsByCondition(
layerIndex,
layerData,
layer,
state,
stateMachine.anyStateTransitions,
aniUpdate
) ||
this._applyStateTransitions(
layerIndex,
layerData,
layer,
isForwards,
playData,
state.transitions,
clipTime,
clipTime,
actualDeltaTime,
aniUpdate
);
(anyStateTransitions.length &&
this._applyTransitionsByCondition(layerIndex, layerData, layer, state, anyStateTransitions, aniUpdate)) ||
(transitions.length &&
this._applyStateTransitions(
layerIndex,
layerData,
layer,
isForwards,
playData,
transitions,
clipTime,
clipTime,
actualDeltaTime,
aniUpdate
));

if (transition) {
this._updateState(layerIndex, layerData, layer, deltaTime, aniUpdate);
Expand Down
67 changes: 67 additions & 0 deletions tests/src/core/Animator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -642,4 +642,71 @@ describe("Animator test", function () {
const newParam2 = animator.animatorController.addParameter("oldName", 2);
expect(newParam2).to.eq(null);
});

it("stateMachineScript", () => {
const animatorController = new AnimatorController(engine);
const layer = new AnimatorControllerLayer("layer");
animatorController.addLayer(layer);
const state1 = layer.stateMachine.addState("state1");
const state2 = layer.stateMachine.addState("state2");
state1.wrapMode = WrapMode.Once;
state2.wrapMode = WrapMode.Once;
const clip1 = new AnimationClip("clip1");
const rotationCurve = new AnimationFloatCurve();
const key1 = new Keyframe<number>();
const key2 = new Keyframe<number>();
key1.time = 0;
key1.value = 0;
key2.time = 1;
key2.value = 90;
rotationCurve.addKey(key1);
rotationCurve.addKey(key2);
clip1.addCurveBinding("", Transform, "rotation.x", rotationCurve);

const clip2 = new AnimationClip("clip2");
const positionCurve = new AnimationFloatCurve();
const key3 = new Keyframe<number>();
const key4 = new Keyframe<number>();
key3.time = 0;
key3.value = 0;
key4.time = 1;
key4.value = 5;
positionCurve.addKey(key3);
positionCurve.addKey(key4);
clip2.addCurveBinding("", Transform, "position.x", positionCurve);
state1.clip = clip1;
state2.clip = clip2;

const transition = new AnimatorStateTransition();
transition.destinationState = state2;
transition.exitTime = 1;
transition.duration = 1;
state1.addTransition(transition);

animator.animatorController = animatorController;

class TestScript extends StateMachineScript {
onStateEnter(animator) {}
onStateExit(animator) {}
}

const testScript = state1.addStateMachineScript(TestScript);
const testScript2 = state2.addStateMachineScript(TestScript);

const onStateEnterSpy = chai.spy.on(testScript, "onStateEnter");
const onStateExitSpy = chai.spy.on(testScript, "onStateExit");
const onStateEnter2Spy = chai.spy.on(testScript2, "onStateEnter");
const onStateExit2Spy = chai.spy.on(testScript2, "onStateExit");

animator.play("state1");

// @ts-ignore
animator.engine.time._frameCount++;
animator.update(3);

expect(onStateEnterSpy).to.have.been.called.exactly(1);
expect(onStateExitSpy).to.have.been.called.exactly(1);
expect(onStateEnter2Spy).to.have.been.called.exactly(1);
expect(onStateExit2Spy).to.have.been.called.exactly(1);
});
});

0 comments on commit 588dc53

Please sign in to comment.