@needle-tools/engine
Version:
Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development with great integrations into editors like Unity or Blender - and can be deployed onto any device! It is flexible, extensible and networking and XR are built-in
261 lines • 11.8 kB
JavaScript
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
import { Mathf } from "../engine/engine_math.js";
import { serializable } from "../engine/engine_serialization_decorator.js";
import { getParam } from "../engine/engine_utils.js";
import { getObjectAnimated } from "./AnimationUtils.js";
import { AnimatorController } from "./AnimatorController.js";
import { Behaviour } from "./Component.js";
const debug = getParam("debuganimator");
/** The Animator component is used to play animations on a GameObject. It is used in combination with an AnimatorController (which is a state machine for animations)
* A new AnimatorController can be created from code via `AnimatorController.createFromClips`
* @category Animation and Sequencing
* @group Components
*/
export class Animator extends Behaviour {
get isAnimationComponent() {
return true;
}
applyRootMotion = false;
hasRootMotion = false;
keepAnimatorControllerStateOnDisable = false;
// set from needle animator extension
set runtimeAnimatorController(val) {
if (this._animatorController && this._animatorController.model === val) {
return;
}
if (val) {
if (!(val instanceof AnimatorController)) {
if (debug)
console.log("Assign animator controller", val, this);
this._animatorController = new AnimatorController(val);
if (this.__didAwake)
this._animatorController.bind(this);
}
else {
if (val.animator && val.animator !== this) {
console.warn("AnimatorController can not be bound to multiple animators", val.model?.name);
if (!val.model) {
console.error("AnimatorController has no model");
}
val = new AnimatorController(val.model);
}
this._animatorController = val;
this._animatorController.bind(this);
}
}
else
this._animatorController = null;
}
get runtimeAnimatorController() {
return this._animatorController;
}
/** The current state info of the animator.
* If you just want to access the currently playing animation action you can use currentAction
* @returns {AnimatorStateInfo} The current state info of the animator or null if no state is playing
*/
getCurrentStateInfo() {
return this.runtimeAnimatorController?.getCurrentStateInfo();
}
/** The current action playing. It can be used to modify the action
* @returns {AnimationAction | null} The current action playing or null if no state is playing
*/
get currentAction() {
return this.runtimeAnimatorController?.currentAction || null;
}
/** @returns {boolean} True if parameters have been changed */
get parametersAreDirty() { return this._parametersAreDirty; }
_parametersAreDirty = false;
/** @returns {boolean} True if the animator has been changed */
get isDirty() { return this._isDirty; }
_isDirty = false;
/**@deprecated use play() */
Play(name, layer = -1, normalizedTime = Number.NEGATIVE_INFINITY, transitionDurationInSec = 0) { this.play(name, layer, normalizedTime, transitionDurationInSec); }
/** Plays an animation on the animator
* @param {string | number} name The name of the animation to play. Can also be the hash of the animation
* @param {number} layer The layer to play the animation on. Default is -1
* @param {number} normalizedTime The normalized time to start the animation at. Default is Number.NEGATIVE_INFINITY
* @param {number} transitionDurationInSec The duration of the transition to the new animation. Default is 0
* @returns {void}
* */
play(name, layer = -1, normalizedTime = Number.NEGATIVE_INFINITY, transitionDurationInSec = 0) {
this.runtimeAnimatorController?.play(name, layer, normalizedTime, transitionDurationInSec);
this._isDirty = true;
}
/**@deprecated use reset */
Reset() { this.reset(); }
/** Resets the animatorcontroller */
reset() {
this._animatorController?.reset();
this._isDirty = true;
}
/**@deprecated use setBool */
SetBool(name, val) { this.setBool(name, val); }
setBool(name, value) {
if (debug)
console.log("setBool", name, value);
if (this.runtimeAnimatorController?.getBool(name) !== value)
this._parametersAreDirty = true;
this.runtimeAnimatorController?.setBool(name, value);
}
/**@deprecated use getBool */
GetBool(name) { return this.getBool(name); }
getBool(name) {
const res = this.runtimeAnimatorController?.getBool(name) ?? false;
if (debug)
console.log("getBool", name, res);
return res;
}
toggleBool(name) {
this.setBool(name, !this.getBool(name));
}
/**@deprecated use setFloat */
SetFloat(name, val) { this.setFloat(name, val); }
setFloat(name, val) {
if (this.runtimeAnimatorController?.getFloat(name) !== val)
this._parametersAreDirty = true;
if (debug)
console.log("setFloat", name, val);
this.runtimeAnimatorController?.setFloat(name, val);
}
/**@deprecated use getFloat */
GetFloat(name) { return this.getFloat(name); }
getFloat(name) {
const res = this.runtimeAnimatorController?.getFloat(name) ?? -1;
if (debug)
console.log("getFloat", name, res);
return res;
}
/**@deprecated use setInteger */
SetInteger(name, val) { this.setInteger(name, val); }
setInteger(name, val) {
if (this.runtimeAnimatorController?.getInteger(name) !== val)
this._parametersAreDirty = true;
if (debug)
console.log("setInteger", name, val);
this.runtimeAnimatorController?.setInteger(name, val);
}
/**@deprecated use getInteger */
GetInteger(name) { return this.getInteger(name); }
getInteger(name) {
const res = this.runtimeAnimatorController?.getInteger(name) ?? -1;
if (debug)
console.log("getInteger", name, res);
return res;
}
/**@deprecated use setTrigger */
SetTrigger(name) { this.setTrigger(name); }
setTrigger(name) {
this._parametersAreDirty = true;
if (debug)
console.log("setTrigger", name);
this.runtimeAnimatorController?.setTrigger(name);
}
/**@deprecated use resetTrigger */
ResetTrigger(name) { this.resetTrigger(name); }
resetTrigger(name) {
this._parametersAreDirty = true;
if (debug)
console.log("resetTrigger", name);
this.runtimeAnimatorController?.resetTrigger(name);
}
/**@deprecated use getTrigger */
GetTrigger(name) { this.getTrigger(name); }
getTrigger(name) {
const res = this.runtimeAnimatorController?.getTrigger(name);
if (debug)
console.log("getTrigger", name, res);
return res;
}
/**@deprecated use isInTransition */
IsInTransition() { return this.isInTransition(); }
/** @returns `true` if the animator is currently in a transition */
isInTransition() {
return this.runtimeAnimatorController?.isInTransition() ?? false;
}
/**@deprecated use setSpeed */
SetSpeed(speed) { return this.setSpeed(speed); }
setSpeed(speed) {
if (speed === this._speed)
return;
if (debug)
console.log("setSpeed", speed);
this._speed = speed;
if (this._animatorController?.animator == this)
this._animatorController.setSpeed(speed);
}
/** Will generate a random speed between the min and max values and set it to the animatorcontroller */
set minMaxSpeed(minMax) {
this._speed = Mathf.lerp(minMax.x, minMax.y, Math.random());
if (this._animatorController?.animator == this)
this._animatorController.setSpeed(this._speed);
}
set minMaxOffsetNormalized(minMax) {
this._normalizedStartOffset = Mathf.lerp(minMax.x, minMax.y, Math.random());
if (this.runtimeAnimatorController?.animator == this)
this.runtimeAnimatorController.normalizedStartOffset = this._normalizedStartOffset;
}
_speed = 1;
_normalizedStartOffset = 0;
_animatorController = null;
awake() {
if (debug)
console.log("ANIMATOR", this.name, this);
if (!this.gameObject)
return;
this.initializeRuntimeAnimatorController();
}
// Why do we jump through hoops like this? It's because of the PlayableDirector and animation tracks
// they NEED to use the same mixer when binding/creating the animation clips
// so when the playable director runs it takes over updating the mixer for blending and then calls the runtimeAnimatorController.update
// so they effectively share the same mixer. There might be cases still where not the same mixer is being used but then the animation track prints an error in dev
_initializeWithRuntimeAnimatorController;
initializeRuntimeAnimatorController(force = false) {
const shouldRun = (force || this.runtimeAnimatorController !== this._initializeWithRuntimeAnimatorController);
if (this.runtimeAnimatorController && shouldRun) {
const clone = this.runtimeAnimatorController.clone();
this._initializeWithRuntimeAnimatorController = clone;
if (clone) {
console.assert(this.runtimeAnimatorController !== clone);
this.runtimeAnimatorController = clone;
console.assert(this.runtimeAnimatorController === clone);
this.runtimeAnimatorController.bind(this);
this.runtimeAnimatorController.setSpeed(this._speed);
this.runtimeAnimatorController.normalizedStartOffset = this._normalizedStartOffset;
}
else
console.warn("Could not clone animator controller", this.runtimeAnimatorController);
}
}
onDisable() {
if (!this.keepAnimatorControllerStateOnDisable)
this._animatorController?.reset();
}
onBeforeRender() {
this._isDirty = false;
this._parametersAreDirty = false;
const isAnimatedExternally = getObjectAnimated(this.gameObject);
if (isAnimatedExternally)
return;
if (this._animatorController) {
this._animatorController.update(1);
}
}
}
__decorate([
serializable()
], Animator.prototype, "applyRootMotion", void 0);
__decorate([
serializable()
], Animator.prototype, "hasRootMotion", void 0);
__decorate([
serializable()
], Animator.prototype, "keepAnimatorControllerStateOnDisable", void 0);
__decorate([
serializable()
], Animator.prototype, "runtimeAnimatorController", null);
//# sourceMappingURL=Animator.js.map