playcanvas
Version:
PlayCanvas WebGL game engine
313 lines (310 loc) • 11 kB
JavaScript
import { Debug } from '../../../core/debug.js';
import { math } from '../../../core/math/math.js';
import { AnimTrack } from '../../anim/evaluator/anim-track.js';
import { AnimTransition } from '../../anim/controller/anim-transition.js';
import { ANIM_LAYER_OVERWRITE } from '../../anim/controller/constants.js';
/**
* @import { AnimComponent } from './component.js'
* @import { AnimController } from '../../anim/controller/anim-controller.js'
*/ /**
* The Anim Component Layer allows managers a single layer of the animation state graph.
*
* @category Animation
*/ class AnimComponentLayer {
/**
* Create a new AnimComponentLayer instance.
*
* @param {string} name - The name of the layer.
* @param {AnimController} controller - The controller to manage this layers animations.
* @param {AnimComponent} component - The component that this layer is a member of.
* @param {number} [weight] - The weight of this layer. Defaults to 1.
* @param {string} [blendType] - The blend type of this layer. Defaults to {@link ANIM_LAYER_OVERWRITE}.
* @ignore
*/ constructor(name, controller, component, weight = 1, blendType = ANIM_LAYER_OVERWRITE){
/** @private */ this._mask = null;
/** @private */ this._blendTime = 0;
/** @private */ this._blendTimeElapsed = 0;
/** @private */ this._startingWeight = 0;
/** @private */ this._targetWeight = 0;
this._name = name;
this._controller = controller;
this._component = component;
this._weight = weight;
this._blendType = blendType;
}
/**
* Returns the name of the layer.
*
* @type {string}
*/ get name() {
return this._name;
}
/**
* Sets whether this layer is currently playing.
*
* @type {boolean}
*/ set playing(value) {
this._controller.playing = value;
}
/**
* Gets whether this layer is currently playing.
*
* @type {boolean}
*/ get playing() {
return this._controller.playing;
}
/**
* Returns true if a state graph has been loaded and all states in the graph have been assigned
* animation tracks.
*
* @type {boolean}
*/ get playable() {
return this._controller.playable;
}
/**
* Gets the currently active state name.
*
* @type {string}
*/ get activeState() {
return this._controller.activeStateName;
}
/**
* Gets the previously active state name.
*
* @type {string|null}
*/ get previousState() {
return this._controller.previousStateName;
}
/**
* Gets the currently active state's progress as a value normalized by the state's animation
* duration. Looped animations will return values greater than 1.
*
* @type {number}
*/ get activeStateProgress() {
return this._controller.activeStateProgress;
}
/**
* Gets the currently active states duration.
*
* @type {number}
*/ get activeStateDuration() {
return this._controller.activeStateDuration;
}
/**
* Sets the active state's time in seconds.
*
* @type {number}
*/ set activeStateCurrentTime(time) {
const controller = this._controller;
const layerPlaying = controller.playing;
controller.playing = true;
controller.activeStateCurrentTime = time;
if (!layerPlaying) {
controller.update(0);
}
controller.playing = layerPlaying;
}
/**
* Gets the active state's time in seconds.
*
* @type {number}
*/ get activeStateCurrentTime() {
return this._controller.activeStateCurrentTime;
}
/**
* Gets whether the anim component layer is currently transitioning between states.
*
* @type {boolean}
*/ get transitioning() {
return this._controller.transitioning;
}
/**
* Gets the progress, if the anim component layer is currently transitioning between states.
* Otherwise returns null.
*
* @type {number|null}
*/ get transitionProgress() {
if (this.transitioning) {
return this._controller.transitionProgress;
}
return null;
}
/**
* Gets all available states in this layers state graph.
*
* @type {string[]}
*/ get states() {
return this._controller.states;
}
/**
* Sets the blending weight of this layer. Used when calculating the value of properties that
* are animated by more than one layer.
*
* @type {number}
*/ set weight(value) {
this._weight = value;
this._component.dirtifyTargets();
}
/**
* Sets the blending weight of this layer.
*
* @type {number}
*/ get weight() {
return this._weight;
}
set blendType(value) {
if (value !== this._blendType) {
this._blendType = value;
if (this._controller.normalizeWeights) {
this._component.rebind();
}
}
}
get blendType() {
return this._blendType;
}
/**
* Sets the mask of bones which should be animated or ignored by this layer.
*
* @type {object}
* @example
* entity.anim.baseLayer.mask = {
* // include the spine of the current model and all of its children
* "path/to/spine": {
* children: true
* },
* // include the hip of the current model but not all of its children
* "path/to/hip": true
* };
*/ set mask(value) {
if (this._controller.assignMask(value)) {
this._component.rebind();
}
this._mask = value;
}
/**
* Gets the mask of bones which should be animated or ignored by this layer.
*
* @type {object}
*/ get mask() {
return this._mask;
}
/**
* Start playing the animation in the current state.
*
* @param {string} [name] - If provided, will begin playing from the start of the state with
* this name.
*/ play(name) {
this._controller.play(name);
}
/**
* Pause the animation in the current state.
*/ pause() {
this._controller.pause();
}
/**
* Reset the animation component to its initial state, including all parameters. The system
* will be paused.
*/ reset() {
this._controller.reset();
}
/**
* Rebind any animations in the layer to the currently present components and model of the anim
* components entity.
*/ rebind() {
this._controller.rebind();
}
update(dt) {
if (this._blendTime) {
if (this._blendTimeElapsed < this._blendTime) {
this.weight = math.lerp(this._startingWeight, this._targetWeight, this._blendTimeElapsed / this._blendTime);
this._blendTimeElapsed += dt;
} else {
this.weight = this._targetWeight;
this._blendTime = 0;
this._blendTimeElapsed = 0;
this._startingWeight = 0;
this._targetWeight = 0;
}
}
this._controller.update(dt);
}
/**
* Blend from the current weight value to the provided weight value over a given amount of time.
*
* @param {number} weight - The new weight value to blend to.
* @param {number} time - The duration of the blend in seconds.
*/ blendToWeight(weight, time) {
this._startingWeight = this.weight;
this._targetWeight = weight;
this._blendTime = Math.max(0, time);
this._blendTimeElapsed = 0;
}
/**
* Assigns an animation track to a state or blend tree node in the current graph. If a state
* for the given nodePath doesn't exist, it will be created. If all states nodes are linked and
* the {@link AnimComponent#activate} value was set to true then the component will begin
* playing.
*
* @param {string} nodePath - Either the state name or the path to a blend tree node that this
* animation should be associated with. Each section of a blend tree path is split using a
* period (`.`) therefore state names should not include this character (e.g "MyStateName" or
* "MyStateName.BlendTreeNode").
* @param {AnimTrack} animTrack - The animation track that will be assigned to this state and
* played whenever this state is active.
* @param {number} [speed] - Update the speed of the state you are assigning an animation to.
* Defaults to 1.
* @param {boolean} [loop] - Update the loop property of the state you are assigning an
* animation to. Defaults to true.
*/ assignAnimation(nodePath, animTrack, speed, loop) {
if (!(animTrack instanceof AnimTrack)) {
Debug.error('assignAnimation: animTrack supplied to function was not of type AnimTrack');
return;
}
this._controller.assignAnimation(nodePath, animTrack, speed, loop);
if (this._controller._transitions.length === 0) {
this._controller._transitions.push(new AnimTransition({
from: 'START',
to: nodePath
}));
}
if (this._component.activate && this._component.playable) {
this._component.playing = true;
}
}
/**
* Removes animations from a node in the loaded state graph.
*
* @param {string} nodeName - The name of the node that should have its animation tracks removed.
*/ removeNodeAnimations(nodeName) {
if (this._controller.removeNodeAnimations(nodeName)) {
this._component.playing = false;
}
}
/**
* Returns an object holding the animation asset id that is associated with the given state.
*
* @param {string} stateName - The name of the state to get the asset for.
* @returns {{ asset: number }} An object containing the animation asset id associated with the given state.
*/ getAnimationAsset(stateName) {
return this._component.animationAssets[`${this.name}:${stateName}`];
}
/**
* Transition to any state in the current layers graph. Transitions can be instant or take an
* optional blend time.
*
* @param {string} to - The state that this transition will transition to.
* @param {number} [time] - The duration of the transition in seconds. Defaults to 0.
* @param {number} [transitionOffset] - If provided, the destination state will begin playing
* its animation at this time. Given in normalized time, based on the states duration & must be
* between 0 and 1. Defaults to null.
*/ transition(to, time = 0, transitionOffset = null) {
this._controller.updateStateFromTransition(new AnimTransition({
from: this._controller.activeStateName,
to,
time,
transitionOffset
}));
}
}
export { AnimComponentLayer };