Animator.re.ts 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310
  1. import * as RE from 'rogue-engine';
  2. import * as THREE from 'three';
  3. export default class Animator extends RE.Component {
  4. @RE.props.list.animation() animations: THREE.AnimationClip[] = [];
  5. private _curAnimations: THREE.AnimationClip[] = [];
  6. @RE.props.data()
  7. data: any[] = [];
  8. private animationsHaveChanged() {
  9. if (this._curAnimations.length !== this.animations.length) return true;
  10. for (let i = 0; i < this._curAnimations.length; i++) {
  11. if (this._curAnimations[i] !== this.animations[i]) return true;
  12. }
  13. return false;
  14. }
  15. private updateConfigs() {
  16. const newConfigs: any[] = [];
  17. for (let i = 0; i < this.animations.length; i++) {
  18. if (this.data[i]) {
  19. newConfigs[i] = this.data[i];
  20. continue;
  21. }
  22. newConfigs[i] = {
  23. actionName: i.toString(),
  24. playOnce: false,
  25. maxWeight: 1,
  26. duration: 1,
  27. speed: 1,
  28. };
  29. }
  30. this._curAnimations = this.animations.slice();
  31. this.data.splice(0);
  32. newConfigs.forEach(config => this.data.push(config));
  33. // this.activeAction && this.activeAction.reset();
  34. this._mixer = this._mixer = new THREE.AnimationMixer(this.object3d);
  35. this.animations.forEach((clip, i) => {
  36. const action = this._mixer.clipAction(clip);
  37. clip.name = this.data[i].actionName;
  38. this.data[i].playOnce && action.setLoop(THREE.LoopOnce, 0);
  39. this.actions[this.data[i].actionName] = {action, config: this.data[i]};
  40. });
  41. }
  42. private _selected = 0;
  43. @RE.props.select()
  44. get selected() {
  45. this.selectedOptions.splice(0, this.selectedOptions.length, ...this.animations.map((_, i) => i.toString() ));
  46. this.isReady && this.animationsHaveChanged() && this.updateConfigs();
  47. return this._selected;
  48. }
  49. editorUpdate: {stop: () => void} | undefined;
  50. set selected(value: number) {
  51. this._selected = value;
  52. this.updateAnimationConfigInputs();
  53. // this.activeAction && this.activeAction.reset();
  54. this.animationsHaveChanged() && this.updateConfigs();
  55. if (this.playLabel === "Stop" && !RE.Runtime.isRunning) {
  56. this.playAction();
  57. }
  58. }
  59. private updateAnimationConfigInputs() {
  60. this.data = this.data;
  61. const activeConfig = this.data[this._selected];
  62. this.actionName = activeConfig.actionName;
  63. this.playOnce = activeConfig.playOnce;
  64. this.duration = activeConfig.duration;
  65. this.speed = activeConfig.speed;
  66. }
  67. selectedOptions: string[] = this.animations.map((elem, i) => i.toString() );
  68. @RE.props.text()
  69. get actionName() {
  70. const activeConfig = this.data[this.selected];
  71. return activeConfig ? activeConfig.actionName : "";
  72. }
  73. set actionName(value: string) {
  74. if (this.selected < 0) return;
  75. const activeConfig = this.data[this.selected];
  76. if (!activeConfig) return;
  77. activeConfig.actionName = value;
  78. }
  79. @RE.props.checkbox()
  80. get playOnce() {
  81. const activeConfig = this.data[this.selected];
  82. return activeConfig ? activeConfig.playOnce : false;
  83. }
  84. set playOnce(value: boolean) {
  85. if (this.selected < 0) return;
  86. const activeConfig = this.data[this.selected];
  87. if (!activeConfig) return;
  88. activeConfig.playOnce = value;
  89. }
  90. @RE.props.num()
  91. get duration() {
  92. const activeConfig = this.data[this.selected];
  93. return activeConfig ? activeConfig.duration : 1;
  94. }
  95. set duration(value: number) {
  96. if (this.selected < 0) return;
  97. const activeConfig = this.data[this.selected];
  98. if (!activeConfig) return;
  99. activeConfig.duration = value;
  100. }
  101. @RE.props.num()
  102. get speed() {
  103. const activeConfig = this.data[this.selected];
  104. return activeConfig ? activeConfig.speed : 1;
  105. }
  106. set speed(value: number) {
  107. if (this.selected < 0) return;
  108. const activeConfig = this.data[this.selected];
  109. if (!activeConfig) return;
  110. activeConfig.speed = Number(value);
  111. }
  112. private stopped = false;
  113. private stopping = false;
  114. get isActive() {
  115. return !this.stopped && !this.stopping;
  116. }
  117. stop() {
  118. this.stopping = true;
  119. }
  120. resume() {
  121. this.stopped = false;
  122. this.stopping = false;
  123. }
  124. @RE.props.button()
  125. play() {
  126. if (RE.Runtime.isRunning) return;
  127. if (this.playLabel === "Play" && !this.editorUpdate) {
  128. this.mixer;
  129. this.animationsHaveChanged() && this.updateConfigs();
  130. this.playAction();
  131. this.editorUpdate = RE.onUpdate(sceneController => {
  132. if (sceneController === RE.Runtime) return;
  133. // this.animationsHaveChanged() && this.stopAction() && this.updateConfigs();
  134. this.mixer.update(sceneController.deltaTime * this.speed);
  135. });
  136. }
  137. else if (this.playLabel === "Stop") {
  138. this.playLabel = "Play";
  139. this.stopAction();
  140. this.editorUpdate?.stop();
  141. this.editorUpdate = undefined;
  142. }
  143. }
  144. private stopAction() {
  145. this.mixer.existingAction(this.animations[this.selected])?.reset();
  146. this.mixer.stopAllAction();
  147. }
  148. private playAction() {
  149. this.stopAction();
  150. const action = this.actions[this.actionName];
  151. if (!this.actions[this.actionName]) return;
  152. this.playLabel = "Stop";
  153. action.action.play();
  154. }
  155. playLabel = "Play";
  156. private _mixer: THREE.AnimationMixer;
  157. actions: {[name: string]: {action: THREE.AnimationAction, config}} = {};
  158. activeAction: THREE.AnimationAction;
  159. get mixer() {
  160. if (!this._mixer) {
  161. this._mixer = new THREE.AnimationMixer(this.object3d);
  162. this.animations.forEach((clip, i) => {
  163. const action = this._mixer.clipAction(clip);
  164. clip.name = this.data[i].actionName;
  165. this.data[i].playOnce && action.setLoop(THREE.LoopOnce, 0);
  166. this.actions[this.data[i].actionName] = {action, config: this.data[i]};
  167. });
  168. }
  169. return this._mixer;
  170. }
  171. awake() {
  172. this.editorUpdate?.stop();
  173. this.editorUpdate = undefined;
  174. }
  175. start() {
  176. this.mixer.existingAction(this.animations[this.selected])?.reset();
  177. this.mixer.stopAllAction();
  178. const configs = this.data;
  179. this.animations.forEach((clip, i) => {
  180. const clipConfig = configs[i];
  181. clipConfig.duration && (clip.duration = clipConfig.duration);
  182. const action = this.mixer.existingAction(clip)
  183. if (!action) return;
  184. action.play() as THREE.AnimationAction;
  185. clipConfig.playOnce && action.setLoop(THREE.LoopOnce, 0);
  186. this.setWeight(action, this.selected === i ? 1 : 0);
  187. });
  188. this.mixer.removeEventListener("finished", this.animationFinished);
  189. this.mixer.addEventListener("finished", this.animationFinished);
  190. this.activeAction = this.defaultAction;
  191. this.mix(Object.keys(this.actions)[0]);
  192. }
  193. update() {
  194. if (this.stopped) return;
  195. this.mixer.update(RE.Runtime.deltaTime * this.actions[this.activeAction.getClip().name].config.speed);
  196. }
  197. getAction(index: number | string) {
  198. return this.actions[index].action;
  199. }
  200. setWeight(action: THREE.AnimationAction, weight: number) {
  201. action.enabled = true;
  202. action.time = 0;
  203. action.setEffectiveTimeScale(1);
  204. action.setEffectiveWeight(weight);
  205. }
  206. getWeight(actionName: string) {
  207. return this.getAction(actionName).getEffectiveWeight();
  208. }
  209. get defaultAction() {
  210. return this.getAction(this.defaultActionName)
  211. }
  212. get defaultActionName() {
  213. return Object.keys(this.actions)[0];
  214. }
  215. private animationFinishedListeners: (() => void)[] = [];
  216. onAnimationFinished(cb: () => void) {
  217. this.animationFinishedListeners.push(cb);
  218. }
  219. private animationFinished = () => {
  220. if (this.stopping) {
  221. this.stopped = true;
  222. this.stopping = false;
  223. }
  224. this.animationFinishedListeners.forEach(listener => listener());
  225. if (this.activeAction.loop === THREE.LoopOnce && !this.activeAction.clampWhenFinished) {
  226. this.mix(this.defaultActionName, 0.001, false);
  227. }
  228. }
  229. mix(actionName: string, transitionTime: number = 0.1, warp = true, weight = 1) {
  230. const action = this.getAction(actionName);
  231. action.reset();
  232. this.setWeight(action, weight);
  233. action.crossFadeFrom(this.activeAction, transitionTime, warp);
  234. this.activeAction = action;
  235. }
  236. }
  237. RE.registerComponent(Animator);