mirror of
https://github.com/galacean/engine.git
synced 2026-06-20 22:06:02 +08:00
Fix crossFade error caused by transitionOffsetTime and clipStartTime (#2411)
* fix: crossFade error caused by transitionOffsetTime and clipStartTime
This commit is contained in:
@@ -570,9 +570,8 @@ export class Animator extends Component {
|
||||
|
||||
let playCostTime: number;
|
||||
if (transition) {
|
||||
const clipDuration = state.clip.length;
|
||||
const clipEndTime = state.clipEndTime * clipDuration;
|
||||
const exitTime = transition.exitTime * state._getDuration();
|
||||
const clipEndTime = state._getClipActualEndTime();
|
||||
const exitTime = transition.exitTime * state._getDuration() + state._getClipActualStartTime();
|
||||
|
||||
if (isForwards) {
|
||||
if (exitTime < lastClipTime) {
|
||||
@@ -581,7 +580,7 @@ export class Animator extends Component {
|
||||
playCostTime = exitTime - lastClipTime;
|
||||
}
|
||||
} else {
|
||||
const startTime = state.clipStartTime * clipDuration;
|
||||
const startTime = state._getClipActualStartTime();
|
||||
if (lastClipTime < exitTime) {
|
||||
playCostTime = clipEndTime - exitTime + lastClipTime - startTime;
|
||||
} else {
|
||||
@@ -667,15 +666,15 @@ export class Animator extends Component {
|
||||
|
||||
let dstPlayCostTime: number;
|
||||
if (destPlayData.isForwards) {
|
||||
// The time that has been played
|
||||
const playedTime = destPlayData.playedTime;
|
||||
dstPlayCostTime =
|
||||
lastDestClipTime + dstPlayDeltaTime > transitionDuration
|
||||
? transitionDuration - lastDestClipTime
|
||||
: dstPlayDeltaTime;
|
||||
playedTime + dstPlayDeltaTime > transitionDuration ? transitionDuration - playedTime : dstPlayDeltaTime;
|
||||
} else {
|
||||
// The time that has been played
|
||||
const playedTime = destStateDuration - lastDestClipTime;
|
||||
const playedTime = destPlayData.playedTime;
|
||||
dstPlayCostTime =
|
||||
// -actualDestDeltaTime: The time that will be played, negative are meant to make ite be a periods
|
||||
// -dstPlayDeltaTime: The time that will be played, negative are meant to make it be a periods
|
||||
// > transition: The time that will be played is enough to finish the transition
|
||||
playedTime - dstPlayDeltaTime > transitionDuration
|
||||
? // Negative number is used to convert a time period into a reverse deltaTime.
|
||||
@@ -690,7 +689,7 @@ export class Animator extends Component {
|
||||
srcPlayData.update(srcPlayCostTime);
|
||||
destPlayData.update(dstPlayCostTime);
|
||||
|
||||
let crossWeight = Math.abs(destPlayData.frameTime) / transitionDuration;
|
||||
let crossWeight = Math.abs(destPlayData.playedTime) / transitionDuration;
|
||||
(crossWeight >= 1.0 - MathUtil.zeroTolerance || transitionDuration === 0) && (crossWeight = 1.0);
|
||||
|
||||
const crossFadeFinished = crossWeight === 1.0;
|
||||
@@ -794,11 +793,13 @@ export class Animator extends Component {
|
||||
|
||||
let dstPlayCostTime: number;
|
||||
if (destPlayData.isForwards) {
|
||||
// The time that has been played
|
||||
const playedTime = destPlayData.playedTime;
|
||||
dstPlayCostTime =
|
||||
lastDestClipTime + playDeltaTime > transitionDuration ? transitionDuration - lastDestClipTime : playDeltaTime;
|
||||
playedTime + playDeltaTime > transitionDuration ? transitionDuration - playedTime : playDeltaTime;
|
||||
} else {
|
||||
// The time that has been played
|
||||
const playedTime = stateDuration - lastDestClipTime;
|
||||
const playedTime = destPlayData.playedTime;
|
||||
dstPlayCostTime =
|
||||
// -actualDestDeltaTime: The time that will be played, negative are meant to make ite be a periods
|
||||
// > transition: The time that will be played is enough to finish the transition
|
||||
@@ -813,7 +814,7 @@ export class Animator extends Component {
|
||||
|
||||
destPlayData.update(dstPlayCostTime);
|
||||
|
||||
let crossWeight = Math.abs(destPlayData.frameTime) / transitionDuration;
|
||||
let crossWeight = Math.abs(destPlayData.playedTime) / transitionDuration;
|
||||
(crossWeight >= 1.0 - MathUtil.zeroTolerance || transitionDuration === 0) && (crossWeight = 1.0);
|
||||
|
||||
const crossFadeFinished = crossWeight === 1.0;
|
||||
@@ -1086,10 +1087,9 @@ export class Animator extends Component {
|
||||
): AnimatorStateTransition {
|
||||
const { state } = playState;
|
||||
let transitionIndex = playState.currentTransitionIndex;
|
||||
const duration = state._getDuration();
|
||||
for (let n = transitions.length; transitionIndex < n; transitionIndex++) {
|
||||
const transition = transitions[transitionIndex];
|
||||
const exitTime = transition.exitTime * duration;
|
||||
const exitTime = transition.exitTime * state._getDuration() + state._getClipActualStartTime();
|
||||
if (exitTime > curClipTime) {
|
||||
break;
|
||||
}
|
||||
@@ -1120,10 +1120,9 @@ export class Animator extends Component {
|
||||
): AnimatorStateTransition {
|
||||
const { state } = playState;
|
||||
let transitionIndex = playState.currentTransitionIndex;
|
||||
const duration = playState.state._getDuration();
|
||||
for (; transitionIndex >= 0; transitionIndex--) {
|
||||
const transition = transitions[transitionIndex];
|
||||
const exitTime = transition.exitTime * duration;
|
||||
const exitTime = transition.exitTime * state._getDuration() + state._getClipActualStartTime();
|
||||
if (exitTime < curClipTime) {
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -232,4 +232,18 @@ export class AnimatorState {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
_getClipActualStartTime(): number {
|
||||
return this._clipStartTime * this.clip.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
_getClipActualEndTime(): number {
|
||||
return this._clipEndTime * this.clip.length;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,18 +9,20 @@ import { AnimatorStateData } from "./AnimatorStateData";
|
||||
export class AnimatorStatePlayData {
|
||||
state: AnimatorState;
|
||||
stateData: AnimatorStateData;
|
||||
frameTime: number;
|
||||
playedTime: number;
|
||||
playState: AnimatorStatePlayState;
|
||||
clipTime: number;
|
||||
currentEventIndex: number;
|
||||
currentTransitionIndex: number;
|
||||
isForwards = true;
|
||||
offsetFrameTime: number;
|
||||
|
||||
private _changedOrientation = false;
|
||||
|
||||
reset(state: AnimatorState, stateData: AnimatorStateData, offsetFrameTime: number): void {
|
||||
this.state = state;
|
||||
this.frameTime = offsetFrameTime;
|
||||
this.playedTime = 0;
|
||||
this.offsetFrameTime = offsetFrameTime;
|
||||
this.stateData = stateData;
|
||||
this.playState = AnimatorStatePlayState.UnStarted;
|
||||
this.clipTime = state.clipStartTime * state.clip.length;
|
||||
@@ -41,9 +43,9 @@ export class AnimatorStatePlayData {
|
||||
}
|
||||
|
||||
update(deltaTime: number): void {
|
||||
this.frameTime += deltaTime;
|
||||
this.playedTime += deltaTime;
|
||||
const state = this.state;
|
||||
let time = this.frameTime;
|
||||
let time = this.playedTime + this.offsetFrameTime;
|
||||
const duration = state._getDuration();
|
||||
this.playState = AnimatorStatePlayState.Playing;
|
||||
if (state.wrapMode === WrapMode.Loop) {
|
||||
|
||||
@@ -81,24 +81,24 @@ describe("Animator test", function () {
|
||||
const speed = 1;
|
||||
let expectedSpeed = speed * 0.5;
|
||||
animator.speed = expectedSpeed;
|
||||
let lastFrameTime = srcPlayData.frameTime;
|
||||
let lastFrameTime = srcPlayData.playedTime;
|
||||
// @ts-ignore
|
||||
animator.engine.time._frameCount++;
|
||||
animator.update(5);
|
||||
expect(animator.speed).to.eq(expectedSpeed);
|
||||
expect(srcPlayData.frameTime).to.eq(lastFrameTime + 5 * expectedSpeed);
|
||||
expect(srcPlayData.playedTime).to.eq(lastFrameTime + 5 * expectedSpeed);
|
||||
expectedSpeed = speed * 2;
|
||||
animator.speed = expectedSpeed;
|
||||
lastFrameTime = srcPlayData.frameTime;
|
||||
lastFrameTime = srcPlayData.playedTime;
|
||||
animator.update(10);
|
||||
expect(animator.speed).to.eq(expectedSpeed);
|
||||
expect(srcPlayData.frameTime).to.eq(lastFrameTime + 10 * expectedSpeed);
|
||||
expect(srcPlayData.playedTime).to.eq(lastFrameTime + 10 * expectedSpeed);
|
||||
expectedSpeed = speed * 0;
|
||||
animator.speed = expectedSpeed;
|
||||
lastFrameTime = srcPlayData.frameTime;
|
||||
lastFrameTime = srcPlayData.playedTime;
|
||||
animator.update(15);
|
||||
expect(animator.speed).to.eq(expectedSpeed);
|
||||
expect(srcPlayData.frameTime).to.eq(lastFrameTime + 15 * expectedSpeed);
|
||||
expect(srcPlayData.playedTime).to.eq(lastFrameTime + 15 * expectedSpeed);
|
||||
});
|
||||
|
||||
it("play animation", () => {
|
||||
@@ -567,6 +567,47 @@ describe("Animator test", function () {
|
||||
expect(animator.getCurrentAnimatorState(0).name).to.eq("Survey");
|
||||
});
|
||||
|
||||
it("transitionOffset", () => {
|
||||
const walkState = animator.findAnimatorState("Walk");
|
||||
walkState.clearTransitions();
|
||||
const runState = animator.findAnimatorState("Run");
|
||||
runState.clearTransitions();
|
||||
const toRunTransition = walkState.addTransition(runState);
|
||||
toRunTransition.exitTime = 0;
|
||||
toRunTransition.duration = 1;
|
||||
toRunTransition.offset = 0.5;
|
||||
animator.play("Walk");
|
||||
// @ts-ignore
|
||||
animator.engine.time._frameCount++;
|
||||
animator.update(0.01);
|
||||
|
||||
const destPlayData = animator["_animatorLayersData"][0].destPlayData;
|
||||
const destState = destPlayData.state;
|
||||
const transitionDuration = toRunTransition.duration * destState._getDuration();
|
||||
const crossWeight = animator["_animatorLayersData"][0].destPlayData.playedTime / transitionDuration;
|
||||
expect(crossWeight).to.lessThan(0.01);
|
||||
});
|
||||
|
||||
it("clipStartTime crossFade", () => {
|
||||
const walkState = animator.findAnimatorState("Walk");
|
||||
walkState.wrapMode = WrapMode.Once;
|
||||
walkState.clipStartTime = 0.8;
|
||||
walkState.clearTransitions();
|
||||
const runState = animator.findAnimatorState("Run");
|
||||
runState.clearTransitions();
|
||||
const toRunTransition = walkState.addTransition(runState);
|
||||
toRunTransition.exitTime = 0.5;
|
||||
toRunTransition.duration = 1;
|
||||
runState.clipStartTime = 0.5;
|
||||
animator.play("Walk");
|
||||
// @ts-ignore
|
||||
animator.engine.time._frameCount++;
|
||||
animator.update(0.1);
|
||||
|
||||
const destPlayData = animator["_animatorLayersData"][0].destPlayData;
|
||||
expect(destPlayData.state?.name).to.eq("Run");
|
||||
});
|
||||
|
||||
it("change state in one update", () => {
|
||||
const animatorController = new AnimatorController(engine);
|
||||
const layer = new AnimatorControllerLayer("layer");
|
||||
|
||||
Reference in New Issue
Block a user