Skip to content

Commit

Permalink
feat/add AnimatorState script for handle animatorState's lifecycle (#552
Browse files Browse the repository at this point in the history
)

* feat: add `StateMachineScript`
  • Loading branch information
luzhuang committed Oct 29, 2021
1 parent 4edd2ee commit a6d1f11
Show file tree
Hide file tree
Showing 26 changed files with 319 additions and 117 deletions.
2 changes: 1 addition & 1 deletion lerna.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"npmClient": "npm",
"version": "0.5.6",
"version": "0.5.7",
"bootstrap": {
"hoist": true
},
Expand Down
4 changes: 2 additions & 2 deletions packages/controls/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@oasis-engine/controls",
"version": "0.5.6",
"version": "0.5.7",
"license": "MIT",
"scripts": {
"b:types": "tsc",
Expand All @@ -15,6 +15,6 @@
"types/**/*"
],
"dependencies": {
"oasis-engine": "0.5.6"
"oasis-engine": "0.5.7"
}
}
6 changes: 3 additions & 3 deletions packages/core/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@oasis-engine/core",
"version": "0.5.6",
"version": "0.5.7",
"license": "MIT",
"main": "dist/main.js",
"module": "dist/module.js",
Expand All @@ -14,9 +14,9 @@
"types/**/*"
],
"dependencies": {
"@oasis-engine/math": "0.5.6"
"@oasis-engine/math": "0.5.7"
},
"devDependencies": {
"@oasis-engine/design": "0.5.6"
"@oasis-engine/design": "0.5.7"
}
}
122 changes: 102 additions & 20 deletions packages/core/src/animation/Animator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { AnimatorStateTransition } from "./AnimatorTransition";
import { AnimatorUtils } from "./AnimatorUtils";
import { AnimationProperty } from "./enums/AnimationProperty";
import { AnimatorLayerBlendingMode } from "./enums/AnimatorLayerBlendingMode";
import { AnimatorStatePlayState } from "./enums/AnimatorStatePlayState";
import { LayerState } from "./enums/LayerState";
import { AnimationCurveOwner } from "./internal/AnimationCurveOwner";
import { AnimationEventHandler } from "./internal/AnimationEventHandler";
Expand Down Expand Up @@ -406,35 +407,52 @@ export class Animator extends Component {
this._checkTransition(srcPlayData, crossFadeTransitionInfo, layerIndex);
switch (animLayerData.layerState) {
case LayerState.Playing:
this._updatePlayingState(srcPlayData, animLayerData, layerWeight, deltaTime, layerAdditive);
this._updatePlayingState(srcPlayData, animLayerData, layerIndex, layerWeight, deltaTime, layerAdditive);
break;
case LayerState.FixedCrossFading:
this._updateCrossFadeFromPose(destPlayData, animLayerData, layerWeight, deltaTime, layerAdditive);
this._updateCrossFadeFromPose(destPlayData, animLayerData, layerIndex, layerWeight, deltaTime, layerAdditive);
break;
case LayerState.CrossFading:
this._updateCrossFade(srcPlayData, destPlayData, animLayerData, layerWeight, deltaTime, layerAdditive);
this._updateCrossFade(
srcPlayData,
destPlayData,
animLayerData,
layerIndex,
layerWeight,
deltaTime,
layerAdditive
);
break;
}
}

private _updatePlayingState(
playData: AnimatorStatePlayData,
layerData: AnimatorLayerData,
layerIndex: number,
weight: number,
delta: number,
additive: boolean
): void {
const { curveOwners, eventHandlers } = playData.stateData;
const { state } = playData;
const { state, playState: lastPlayState, clipTime: lastClipTime } = playData;
const { _curveBindings: curves } = state.clip;
const lastClipTime = playData.clipTime;

playData.update();

const clipTime = playData.clipTime;
const { clipTime, playState } = playData;

eventHandlers.length && this._fireAnimationEvents(playData, eventHandlers, lastClipTime, clipTime);

if (lastPlayState === AnimatorStatePlayState.UnStarted) {
this._callAnimatorScriptOnEnter(state, layerIndex);
}
if (playState === AnimatorStatePlayState.Finished) {
this._callAnimatorScriptOnExit(state, layerIndex);
} else {
this._callAnimatorScriptOnUpdate(state, layerIndex);
}

for (let i = curves.length - 1; i >= 0; i--) {
const owner = curveOwners[i];
const value = this._evaluateCurve(owner.property, curves[i].curve, clipTime, additive);
Expand All @@ -446,7 +464,7 @@ export class Animator extends Component {
}
playData.frameTime += state.speed * delta;

if (playData.finished) {
if (playState === AnimatorStatePlayState.Finished) {
layerData.layerState = LayerState.Standby;
}
}
Expand All @@ -455,22 +473,51 @@ export class Animator extends Component {
srcPlayData: AnimatorStatePlayData,
destPlayData: AnimatorStatePlayData,
layerData: AnimatorLayerData,
layerIndex,
weight: number,
delta: number,
additive: boolean
) {
const crossCurveDataCollection = this._crossCurveDataCollection;
const srcCurves = srcPlayData.state.clip._curveBindings;
const { state: destState } = destPlayData;
const destCurves = destState.clip._curveBindings;
const { _crossCurveDataCollection: crossCurveDataCollection } = this;
const { _curveBindings: srcCurves } = srcPlayData.state.clip;
const { state: srcState, stateData: srcStateData, playState: lastSrcPlayState } = srcPlayData;
const { eventHandlers: srcEventHandler } = srcStateData;
const { state: destState, stateData: destStateData, playState: lastDstPlayState } = destPlayData;
const { eventHandlers: destEventHandler } = destStateData;
const { _curveBindings: destCurves } = destState.clip;
const { clipTime: lastSrcClipTime } = srcPlayData;
const { clipTime: lastDestClipTime } = destPlayData;

let crossWeight = destPlayData.frameTime / (destState._getDuration() * layerData.crossFadeTransition.duration);
crossWeight >= 1.0 && (crossWeight = 1.0);
srcPlayData.update();
destPlayData.update();

const srcClipTime = srcPlayData.clipTime;
const destClipTime = destPlayData.clipTime;
const { clipTime: srcClipTime } = srcPlayData;
const { clipTime: destClipTime } = destPlayData;

srcEventHandler.length && this._fireAnimationEvents(srcPlayData, srcEventHandler, lastSrcClipTime, srcClipTime);
destEventHandler.length &&
this._fireAnimationEvents(destPlayData, destEventHandler, lastDestClipTime, destClipTime);

if (lastSrcPlayState === AnimatorStatePlayState.UnStarted) {
this._callAnimatorScriptOnEnter(srcState, layerIndex);
}
if (crossWeight === 1 || srcPlayData.playState === AnimatorStatePlayState.Finished) {
this._callAnimatorScriptOnExit(srcState, layerIndex);
} else {
this._callAnimatorScriptOnUpdate(srcState, layerIndex);
}

if (lastDstPlayState === AnimatorStatePlayState.UnStarted) {
this._callAnimatorScriptOnEnter(destState, layerIndex);
}
if (destPlayData.playState === AnimatorStatePlayState.Finished) {
this._callAnimatorScriptOnExit(destState, layerIndex);
} else {
this._callAnimatorScriptOnUpdate(destState, layerIndex);
}

for (let i = crossCurveDataCollection.length - 1; i >= 0; i--) {
const { curveOwner, srcCurveIndex, destCurveIndex } = crossCurveDataCollection[i];
const { property, defaultValue } = curveOwner;
Expand All @@ -486,26 +533,40 @@ export class Animator extends Component {

this._applyCrossClipValue(curveOwner, srcValue, destValue, crossWeight, weight, additive);
}

this._updateCrossFadeData(layerData, crossWeight, delta, false);
}

private _updateCrossFadeFromPose(
destPlayData: AnimatorStatePlayData,
layerData: AnimatorLayerData,
layerIndex: number,
weight: number,
delta: number,
additive: boolean
) {
const crossCurveDataCollection = this._crossCurveDataCollection;
const { state: destState } = destPlayData;
const curves = destState.clip._curveBindings;
const { state, stateData, playState: lastPlayState } = destPlayData;
const { eventHandlers } = stateData;
const { _curveBindings: curves } = state.clip;
const { clipTime: lastDestClipTime } = destPlayData;

let crossWeight = destPlayData.frameTime / (destState._getDuration() * layerData.crossFadeTransition.duration);
let crossWeight = destPlayData.frameTime / (state._getDuration() * layerData.crossFadeTransition.duration);
crossWeight >= 1.0 && (crossWeight = 1.0);
destPlayData.update();

const destClipTime = destPlayData.clipTime;
const { clipTime: destClipTime } = destPlayData;

eventHandlers.length && this._fireAnimationEvents(destPlayData, eventHandlers, lastDestClipTime, destClipTime);

if (lastPlayState === AnimatorStatePlayState.UnStarted) {
this._callAnimatorScriptOnEnter(state, layerIndex);
}
if (destPlayData.playState === AnimatorStatePlayState.Finished) {
this._callAnimatorScriptOnExit(state, layerIndex);
} else {
this._callAnimatorScriptOnUpdate(state, layerIndex);
}

for (let i = crossCurveDataCollection.length - 1; i >= 0; i--) {
const { curveOwner, destCurveIndex } = crossCurveDataCollection[i];
const destValue =
Expand All @@ -523,7 +584,7 @@ export class Animator extends Component {
const { destPlayData } = layerData;
destPlayData.frameTime += destPlayData.state.speed * delta;
if (crossWeight === 1.0) {
if (destPlayData.finished) {
if (destPlayData.playState === AnimatorStatePlayState.Finished) {
layerData.layerState = LayerState.Standby;
} else {
layerData.layerState = LayerState.Playing;
Expand Down Expand Up @@ -766,7 +827,28 @@ export class Animator extends Component {
}
}

private _clearPlayData() {
private _callAnimatorScriptOnEnter(state: AnimatorState, layerIndex: number): void {
const scripts = state._onStateEnterScripts;
for (let i = 0, n = scripts.length; i < n; i++) {
scripts[i].onStateEnter(this, state, layerIndex);
}
}

private _callAnimatorScriptOnUpdate(state: AnimatorState, layerIndex: number): void {
const scripts = state._onStateUpdateScripts;
for (let i = 0, n = scripts.length; i < n; i++) {
scripts[i].onStateUpdate(this, state, layerIndex);
}
}

private _callAnimatorScriptOnExit(state: AnimatorState, layerIndex: number): void {
const scripts = state._onStateExitScripts;
for (let i = 0, n = scripts.length; i < n; i++) {
scripts[i].onStateExit(this, state, layerIndex);
}
}

private _clearPlayData(): void {
this._animatorLayersData.length = 0;
this._crossCurveDataCollection.length = 0;
this._animationCurveOwners.length = 0;
Expand Down
51 changes: 50 additions & 1 deletion packages/core/src/animation/AnimatorState.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { AnimationClip } from "./AnimationClip";
import { AnimatorStateTransition } from "./AnimatorTransition";
import { WrapMode } from "./enums/WrapMode";
import { StateMachineScript } from "./StateMachineScript";

/**
* States are the basic building blocks of a state machine. Each state contains a AnimationClip which will play while the character is in that state.
Expand All @@ -11,6 +12,13 @@ export class AnimatorState {
/** The wrap mode used in the state. */
wrapMode: WrapMode = WrapMode.Loop;

/** @internal */
_onStateEnterScripts: StateMachineScript[] = [];
/** @internal */
_onStateUpdateScripts: StateMachineScript[] = [];
/** @internal */
_onStateExitScripts: StateMachineScript[] = [];

private _clipStartTime: number = 0;
private _clipEndTime: number = Infinity;
private _clip: AnimationClip;
Expand All @@ -24,7 +32,7 @@ export class AnimatorState {
}

/**
* ƒThe clip that is being played by this animator state.
* The clip that is being played by this animator state.
*/
get clip(): AnimationClip {
return this._clip;
Expand Down Expand Up @@ -82,6 +90,28 @@ export class AnimatorState {
index !== -1 && this._transitions.splice(index, 1);
}

/**
* Adds a state machine script class of type T to the AnimatorState.
* @param scriptType - The state machine script class of type T
*/
addStateMachineScript<T extends StateMachineScript>(scriptType: new () => T): T {
const script = new scriptType();
script._state = this;

const { prototype } = StateMachineScript;
if (script.onStateEnter !== prototype.onStateEnter) {
this._onStateEnterScripts.push(script);
}
if (script.onStateUpdate !== prototype.onStateUpdate) {
this._onStateUpdateScripts.push(script);
}
if (script.onStateExit !== prototype.onStateExit) {
this._onStateExitScripts.push(script);
}

return script;
}

/**
* Clears all transitions from the state.
*/
Expand All @@ -95,4 +125,23 @@ export class AnimatorState {
_getDuration(): number {
return this._clipEndTime - this._clipStartTime;
}

/**
* @internal
*/
_removeStateMachineScript(script: StateMachineScript): void {
const { prototype } = StateMachineScript;
if (script.onStateEnter !== prototype.onStateEnter) {
const index = this._onStateEnterScripts.indexOf(script);
index !== -1 && this._onStateEnterScripts.splice(index, 1);
}
if (script.onStateUpdate !== prototype.onStateUpdate) {
const index = this._onStateUpdateScripts.indexOf(script);
index !== -1 && this._onStateUpdateScripts.splice(index, 1);
}
if (script.onStateExit !== prototype.onStateExit) {
const index = this._onStateExitScripts.indexOf(script);
index !== -1 && this._onStateExitScripts.splice(index, 1);
}
}
}
47 changes: 47 additions & 0 deletions packages/core/src/animation/StateMachineScript.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { Animator } from "../animation/Animator";
import { AnimatorState } from "../animation/AnimatorState";

/**
* StateMachineScript is a component that can be added to a animator state. It's the base class every script on a state derives from.
*/
export class StateMachineScript {
/** @internal */
_destroyed: boolean = false;
/** @internal */
_state: AnimatorState;
/**
* onStateEnter is called when a transition starts and the state machine starts to evaluate this state.
* @param animator - The animator
* @param animatorState - The state be evaluated
* @param layerIndex - The index of the layer where the state is located
*/
onStateEnter(animator: Animator, animatorState: AnimatorState, layerIndex: number): void {}

/**
* onStateUpdate is called on each Update frame between onStateEnter and onStateExit callbacks.
* @param animator - The animator
* @param animatorState - The state be evaluated
* @param layerIndex - The index of the layer where the state is located
*/
onStateUpdate(animator: Animator, animatorState: AnimatorState, layerIndex: number): void {}

/**
* onStateExit is called when a transition ends and the state machine finishes evaluating this state.
* @param animator - The animator
* @param animatorState - The state be evaluated
* @param layerIndex - The index of the layer where the state is located
*/
onStateExit(animator: Animator, animatorState: AnimatorState, layerIndex: number): void {}

/**
* Destroy this instance.
*/
destroy(): void {
if (this._destroyed) {
return;
}

this._state._removeStateMachineScript(this);
this._destroyed = true;
}
}
Loading

0 comments on commit a6d1f11

Please sign in to comment.