UNPKG

@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
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