Skip to content

Commit

Permalink
feat: update controller loader to enhance stateMachine and fix crossf…
Browse files Browse the repository at this point in the history
…ade bug when dest state has no clip (#2228)

* feat: update controller loader to enhance stateMachine
  • Loading branch information
luzhuang authored Jul 19, 2024
1 parent e2d137a commit 4171a97
Show file tree
Hide file tree
Showing 3 changed files with 118 additions and 31 deletions.
32 changes: 23 additions & 9 deletions packages/core/src/animation/Animator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1078,8 +1078,11 @@ export class Animator extends Component {
if (exitTime >= lastClipTime) {
playState.currentTransitionIndex = Math.min(transitionIndex + 1, n - 1);
if (this._checkConditions(state, transition)) {
this._applyTransition(layerIndex, layerData, stateMachine, transition);
return transition;
if (this._applyTransition(layerIndex, layerData, stateMachine, transition)) {
return transition;
} else {
return null;
}
}
}
}
Expand Down Expand Up @@ -1108,8 +1111,11 @@ export class Animator extends Component {
if (exitTime <= lastClipTime) {
playState.currentTransitionIndex = Math.max(transitionIndex - 1, 0);
if (this._checkConditions(state, transition)) {
this._applyTransition(layerIndex, layerData, stateMachine, transition);
return transition;
if (this._applyTransition(layerIndex, layerData, stateMachine, transition)) {
return transition;
} else {
return null;
}
}
}
}
Expand All @@ -1126,8 +1132,11 @@ export class Animator extends Component {
for (let i = 0, n = transitions.length; i < n; i++) {
const transition = transitions[i];
if (this._checkConditions(state, transition)) {
this._applyTransition(layerIndex, layerData, stateMachine, transition);
return transition;
if (this._applyTransition(layerIndex, layerData, stateMachine, transition)) {
return transition;
} else {
return null;
}
}
}
}
Expand Down Expand Up @@ -1155,13 +1164,14 @@ export class Animator extends Component {
layerData: AnimatorLayerData,
stateMachine: AnimatorStateMachine,
transition: AnimatorStateTransition
): void {
): boolean {
// Need prepare first, it should crossFade when to exit
this._prepareCrossFadeByTransition(transition, layerIndex);
const success = this._prepareCrossFadeByTransition(transition, layerIndex);
if (transition.isExit) {
this._checkAnyAndEntryState(layerIndex, layerData, stateMachine);
return;
return true;
}
return success;
}

private _checkConditions(state: AnimatorState, transition: AnimatorStateTransition): boolean {
Expand All @@ -1176,6 +1186,10 @@ export class Animator extends Component {
let pass = false;
const { mode, parameterName: name, threshold } = conditions[i];
const parameter = this.getParameter(name);
if (!parameter) {
return false;
}

switch (mode) {
case AnimatorConditionMode.Equals:
if (parameter.value === threshold) {
Expand Down
3 changes: 3 additions & 0 deletions packages/core/src/animation/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,6 @@ export { WrapMode } from "./enums/WrapMode";
export * from "./Keyframe";
export * from "./AnimatorLayerMask";
export { StateMachineScript } from "./StateMachineScript";
export { AnimatorCondition } from "./AnimatorCondition";
export * from "./AnimatorControllerParameter";
export { LayerPathMask } from "./LayerPathMask";
114 changes: 92 additions & 22 deletions packages/loader/src/AnimatorControllerLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,12 @@ import {
ResourceManager,
AnimatorController,
AnimatorControllerLayer,
AnimatorStateMachine,
AnimatorStateTransition
AnimatorStateTransition,
AnimatorState,
AnimatorConditionMode,
AnimatorControllerParameterValueType,
WrapMode,
AnimatorControllerParameter
} from "@galacean/engine-core";

@resourceLoader(AssetType.AnimatorController, ["json"], false)
Expand All @@ -21,37 +25,38 @@ class AnimatorControllerLoader extends Loader<AnimatorController> {
})
.then((data) => {
const animatorController = new AnimatorController();
const { layers } = data;
const { layers, parameters } = data;
const promises = [];
layers.forEach((layerData, layerIndex: number) => {
const { name, blendingMode, weight, stateMachine: stateMachineData } = layerData;
const layer = new AnimatorControllerLayer(name);
layer.blendingMode = blendingMode;
layer.weight = weight;
if (stateMachineData) {
const { states } = stateMachineData;
const stateMachine = (layer.stateMachine = new AnimatorStateMachine());
states.forEach((stateData, stateIndex: number) => {
const { states, transitions, entryTransitions, anyTransitions } = stateMachineData;
const stateMachine = layer.stateMachine;
const statesMap: Record<string, AnimatorState> = {};
const transitionsMap: Record<string, AnimatorStateTransition> = {};
states.forEach((stateData: IStateData, stateIndex: number) => {
const {
id,
name,
speed,
wrapMode,
clipStartNormalizedTime,
clipEndNormalizedTime,
isDefaultState,
clip: clipData,
scripts
} = stateData;
const state = stateMachine.addState(name);
isDefaultState && (stateMachine.defaultState = state);
state.speed = speed;
state.wrapMode = wrapMode;
state.clipStartTime = clipStartNormalizedTime;
state.clipEndTime = clipEndNormalizedTime;
const scriptsObject = JSON.parse(scripts);
scriptsObject?.forEach((script) => {
scripts.forEach((script) => {
state.addStateMachineScript(Loader.getClass(script));
});
statesMap[id] = state;
if (clipData) {
promises.push(
new Promise((resolve) => {
Expand All @@ -67,23 +72,39 @@ class AnimatorControllerLoader extends Loader<AnimatorController> {
);
}
});
states.forEach((stateData) => {
const { name, transitions } = stateData;
transitions.forEach((transitionData) => {
const { targetStateName, duration, offset, exitTime } = transitionData;
const sourceState = stateMachine.findStateByName(name);
const destState = stateMachine.findStateByName(targetStateName);
const transition = new AnimatorStateTransition();
transition.destinationState = destState;
transition.duration = duration;
transition.exitTime = exitTime;
transition.offset = offset;
sourceState.addTransition(transition);
transitions.forEach((transitionData: ITransitionData) => {
const transition = this._createTransition(transitionData, statesMap[transitionData.destinationStateId]);
transitionsMap[transitionData.id] = transition;
});

states.forEach((stateData: IStateData) => {
const { id, transitions } = stateData;
transitions.forEach((transitionId) => {
const transition = transitionsMap[transitionId];
transition && statesMap[id].addTransition(transition);
});
});

entryTransitions.forEach((entryTransitionData: ITransitionData) => {
stateMachine.addEntryStateTransition(
this._createTransition(entryTransitionData, statesMap[entryTransitionData.destinationStateId])
);
});

anyTransitions.forEach((anyTransitionData: ITransitionData) => {
stateMachine.addAnyStateTransition(
this._createTransition(anyTransitionData, statesMap[anyTransitionData.destinationStateId])
);
});
}
animatorController.addLayer(layer);
});
parameters.forEach((parameterData) => {
const parameter = new AnimatorControllerParameter();
parameter.name = parameterData.name;
parameter.value = parameterData.value;
animatorController.addParameter(parameter);
});
Promise.all(promises).then((clipData) => {
clipData.forEach((data) => {
const { layerIndex, stateIndex, clip } = data;
Expand All @@ -95,4 +116,53 @@ class AnimatorControllerLoader extends Loader<AnimatorController> {
.catch(reject);
});
}

private _createTransition(transitionData: ITransitionData, destinationState: AnimatorState): AnimatorStateTransition {
const transition = new AnimatorStateTransition();
transition.duration = transitionData.duration;
transition.offset = transitionData.offset;
transition.exitTime = transitionData.exitTime;
transition.solo = transitionData.solo;
transition.mute = transitionData.mute;
// @ts-ignore
transition._isExit = transitionData.isExit;
transition.destinationState = destinationState;
transitionData.conditions.forEach((conditionData) => {
transition.addCondition(conditionData.mode, conditionData.parameterName, conditionData.threshold);
});
return transition;
}
}

interface IStateData {
id?: string;
name: string;
speed: number;
wrapMode: WrapMode;
clipStartNormalizedTime: number;
clipEndNormalizedTime: number;
clip: any;
transitions: string[];
scripts: string[];
isEntryState: boolean;
isExitState: boolean;
isAnyState: boolean;
}

interface ITransitionData {
id?: string;
duration: number;
offset: number;
exitTime: number;
destinationStateId: string;
solo: boolean;
mute: boolean;
isExit: boolean;
conditions: IConditionData[];
}

interface IConditionData {
mode: AnimatorConditionMode;
parameterName: string;
threshold?: AnimatorControllerParameterValueType;
}

0 comments on commit 4171a97

Please sign in to comment.