Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Animator support transition conditions #2096

Merged
merged 13 commits into from
Jun 21, 2024
260 changes: 224 additions & 36 deletions packages/core/src/animation/Animator.ts

Large diffs are not rendered by default.

14 changes: 14 additions & 0 deletions packages/core/src/animation/AnimatorCondition.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { AnimatorControllerParameterValue } from "./AnimatorControllerParameter";
import { AnimatorConditionMode } from "./enums/AnimatorConditionMode";

/**
* Condition that is used to determine if a transition must be taken.
*/
export class AnimatorCondition {
/** The mode of the condition. */
mode: AnimatorConditionMode;
GuoLei1990 marked this conversation as resolved.
Show resolved Hide resolved
/** The name of the parameter used in the condition. */
parameter: string;
GuoLei1990 marked this conversation as resolved.
Show resolved Hide resolved
/** The AnimatorParameter's threshold value for the condition to be true. */
threshold?: AnimatorControllerParameterValue;
}
59 changes: 57 additions & 2 deletions packages/core/src/animation/AnimatorController.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
import { BoolUpdateFlag } from "../BoolUpdateFlag";
import { AnimatorControllerParameter, AnimatorControllerParameterValue } from "./AnimatorControllerParameter";
import { UpdateFlagManager } from "../UpdateFlagManager";
import { AnimatorControllerLayer } from "./AnimatorControllerLayer";

/**
* Store the data for Animator playback.
*/
export class AnimatorController {
/** @internal */
_parameters: AnimatorControllerParameter[] = [];
/** @internal */
_parametersMap: Record<string, AnimatorControllerParameter> = {};
GuoLei1990 marked this conversation as resolved.
Show resolved Hide resolved
/** @internal */
_layers: AnimatorControllerLayer[] = [];
/** @internal */
_layersMap: Record<string, AnimatorControllerLayer> = {};

private _updateFlagManager: UpdateFlagManager = new UpdateFlagManager();
private _layers: AnimatorControllerLayer[] = [];
private _layersMap: Record<string, AnimatorControllerLayer> = {};

/**
* The layers in the controller.
Expand All @@ -17,6 +25,53 @@ export class AnimatorController {
return this._layers;
}

/**
* The parameters in the controller.
*/
get parameters(): Readonly<AnimatorControllerParameter[]> {
return this._parameters;
}

/**
* Add a parameter to the controller.
* @param name - The name of the parameter
* @param defaultValue - The defaultValue of the parameter
*/
addParameter(name: string, defaultValue?: AnimatorControllerParameterValue): AnimatorControllerParameter;
GuoLei1990 marked this conversation as resolved.
Show resolved Hide resolved

/**
* Add a parameter to the controller.
* @param parameter - The parameter
*/
addParameter(parameter: AnimatorControllerParameter): AnimatorControllerParameter;

addParameter(param: AnimatorControllerParameter | string, defaultValue?: AnimatorControllerParameterValue) {
if (typeof param === "string") {
const parameter = new AnimatorControllerParameter();
parameter.name = param;
parameter.value = defaultValue;
this._parametersMap[param] = parameter;
this._parameters.push(parameter);
return parameter;
} else {
this._parametersMap[param.name] = param;
this._parameters.push(param);
return param;
}
}
GuoLei1990 marked this conversation as resolved.
Show resolved Hide resolved

/**
* Remove a parameter from the controller.
* @param parameter - The parameter
*/
removeParameter(parameter: AnimatorControllerParameter) {
const index = this._parameters.indexOf(parameter);
if (index !== -1) {
this._parameters.splice(index, 1);
delete this._parametersMap[parameter.name];
}
}

/**
* Get the layer by name.
* @param name - The layer's name.
Expand Down
11 changes: 11 additions & 0 deletions packages/core/src/animation/AnimatorControllerParameter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export type AnimatorControllerParameterValue = number | string | boolean;
GuoLei1990 marked this conversation as resolved.
Show resolved Hide resolved

/**
* Used to communicate between scripting and the controller, parameters can be set in scripting and used by the controller.
*/
export class AnimatorControllerParameter {
/** The name of the parameter. */
name: string;
/** The value of the parameter. */
value: AnimatorControllerParameterValue;
}
51 changes: 46 additions & 5 deletions packages/core/src/animation/AnimatorState.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { UpdateFlagManager } from "../UpdateFlagManager";
import { AnimationClip } from "./AnimationClip";
import { AnimatorStateTransition } from "./AnimatorTransition";
import { AnimatorStateTransition } from "./AnimatorStateTransition";
import { WrapMode } from "./enums/WrapMode";
import { StateMachineScript } from "./StateMachineScript";

Expand All @@ -21,6 +21,8 @@ export class AnimatorState {
_onStateExitScripts: StateMachineScript[] = [];
/** @internal */
_updateFlagManager: UpdateFlagManager = new UpdateFlagManager();
/** @internal */
_hasSoloTransition: boolean = false;

private _clipStartTime: number = 0;
private _clipEndTime: number = 1;
Expand Down Expand Up @@ -60,7 +62,7 @@ export class AnimatorState {
}

/**
* The start time of the clip, the range is 0 to 1, default is 0.
* The normalized start time of the clip, the range is 0 to 1, default is 0.
*/
get clipStartTime() {
return this._clipStartTime;
Expand All @@ -71,7 +73,7 @@ export class AnimatorState {
}

/**
* The end time of the clip, the range is 0 to 1, default is 1.
* The normalized end time of the clip, the range is 0 to 1, default is 1.
*/
get clipEndTime() {
return this._clipEndTime;
Expand All @@ -89,10 +91,25 @@ export class AnimatorState {
}

/**
* Add an outgoing transition to the destination state.
* Add an outgoing transition.
* @param transition - The transition
*/
addTransition(transition: AnimatorStateTransition): void {
addTransition(transition: AnimatorStateTransition): AnimatorStateTransition;
/**
* Add an outgoing transition to the destination state.
* @param animatorState - The destination state
*/
addTransition(animatorState: AnimatorState): AnimatorStateTransition;

addTransition(transitionOrAnimatorState: AnimatorStateTransition | AnimatorState): AnimatorStateTransition {
let transition: AnimatorStateTransition | AnimatorState;
GuoLei1990 marked this conversation as resolved.
Show resolved Hide resolved
if (transitionOrAnimatorState instanceof AnimatorState) {
transition = new AnimatorStateTransition();
transition.destinationState = transitionOrAnimatorState;
} else {
transition = transitionOrAnimatorState;
}
transition._srcState = this;
const transitions = this._transitions;
const count = transitions.length;
const time = transition.exitTime;
Expand All @@ -104,6 +121,10 @@ export class AnimatorState {
while (--index >= 0 && time < transitions[index].exitTime);
transitions.splice(index + 1, 0, transition);
}

transition.solo && !this._hasSoloTransition && this._updateSoloTransition(true);

return transition;
}

/**
Expand All @@ -113,6 +134,9 @@ export class AnimatorState {
removeTransition(transition: AnimatorStateTransition): void {
const index = this._transitions.indexOf(transition);
index !== -1 && this._transitions.splice(index, 1);
transition._srcState = null;

this._updateSoloTransition();
GuoLei1990 marked this conversation as resolved.
Show resolved Hide resolved
}

/**
Expand Down Expand Up @@ -179,4 +203,21 @@ export class AnimatorState {
_onClipChanged(): void {
this._updateFlagManager.dispatch();
}

/**
* @internal
*/
_updateSoloTransition(hasSolo?: boolean): void {
if (hasSolo !== undefined) {
this._hasSoloTransition = hasSolo;
} else {
this._hasSoloTransition = false;
for (let i = 0, n = this.transitions.length; i < n; ++i) {
if (this.transitions[i].solo) {
this._hasSoloTransition = true;
return;
}
}
}
}
}
86 changes: 84 additions & 2 deletions packages/core/src/animation/AnimatorStateMachine.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { AnimatorState } from "./AnimatorState";
import { AnimatorStateTransition } from "./AnimatorStateTransition";
export interface AnimatorStateMap {
[key: string]: AnimatorState;
}
Expand All @@ -16,8 +17,23 @@ export class AnimatorStateMachine {
*/
defaultState: AnimatorState;

/** @internal */
_statesMap: AnimatorStateMap = {};
private _entryTransitions: AnimatorStateTransition[] = [];
private _anyStateTransitions: AnimatorStateTransition[] = [];
private _statesMap: AnimatorStateMap = {};

/**
* The list of entry transitions in the state machine.
*/
get entryTransitions(): Readonly<AnimatorStateTransition[]> {
return this._entryTransitions;
}

/**
* The list of AnyState transitions.
*/
get anyStateTransitions(): Readonly<AnimatorStateTransition[]> {
GuoLei1990 marked this conversation as resolved.
Show resolved Hide resolved
return this._anyStateTransitions;
}

/**
* Add a state to the state machine.
Expand Down Expand Up @@ -71,4 +87,70 @@ export class AnimatorStateMachine {
}
return name;
}

/**
* Add an entry transition.
* @param transition - The transition
*/
addEntryStateTransition(transition: AnimatorStateTransition): AnimatorStateTransition;
/**
* Add an entry transition to the destination state.
* @param animatorState - The destination state
*/

addEntryStateTransition(animatorState: AnimatorState): AnimatorStateTransition;

addEntryStateTransition(transitionOrAnimatorState: AnimatorStateTransition | AnimatorState): AnimatorStateTransition {
let transition: AnimatorStateTransition | AnimatorState;
GuoLei1990 marked this conversation as resolved.
Show resolved Hide resolved
if (transitionOrAnimatorState instanceof AnimatorState) {
transition = new AnimatorStateTransition();
transition.destinationState = transitionOrAnimatorState;
} else {
transition = transitionOrAnimatorState;
}
this._entryTransitions.push(transition);
return transition;
}

/**
* Remove an entry transition.
* @param transition - The transition
*/
removeEntryStateTransition(transition: AnimatorStateTransition): void {
const index = this._entryTransitions.indexOf(transition);
index !== -1 && this._entryTransitions.splice(index, 1);
}

/**
* Add an any transition.
* @param transition - The transition
*/
addAnyStateTransition(transition: AnimatorStateTransition): AnimatorStateTransition;
/**
* Add an any transition to the destination state.
* @param animatorState - The destination state
*/

addAnyStateTransition(animatorState: AnimatorState): AnimatorStateTransition;

addAnyStateTransition(transitionOrAnimatorState: AnimatorStateTransition | AnimatorState): AnimatorStateTransition {
let transition: AnimatorStateTransition | AnimatorState;
GuoLei1990 marked this conversation as resolved.
Show resolved Hide resolved
if (transitionOrAnimatorState instanceof AnimatorState) {
transition = new AnimatorStateTransition();
transition.destinationState = transitionOrAnimatorState;
} else {
transition = transitionOrAnimatorState;
}
this._anyStateTransitions.push(transition);
return transition;
}
GuoLei1990 marked this conversation as resolved.
Show resolved Hide resolved

/**
* Remove an any transition.
* @param transition - The transition
*/
removeAnyStateTransition(transition: AnimatorStateTransition): void {
const index = this._anyStateTransitions.indexOf(transition);
index !== -1 && this._anyStateTransitions.splice(index, 1);
}
}
Loading
Loading