Source: extras/AnimatedSprite.js

extras/AnimatedSprite.js

  1. import * as core from '../core';
  2. /**
  3. * @typedef PIXI.extras.AnimatedSprite~FrameObject
  4. * @type {object}
  5. * @property {PIXI.Texture} texture - The {@link PIXI.Texture} of the frame
  6. * @property {number} time - the duration of the frame in ms
  7. */
  8. /**
  9. * An AnimatedSprite is a simple way to display an animation depicted by a list of textures.
  10. *
  11. * ```js
  12. * let alienImages = ["image_sequence_01.png","image_sequence_02.png","image_sequence_03.png","image_sequence_04.png"];
  13. * let textureArray = [];
  14. *
  15. * for (let i=0; i < 4; i++)
  16. * {
  17. * let texture = PIXI.Texture.fromImage(alienImages[i]);
  18. * textureArray.push(texture);
  19. * };
  20. *
  21. * let animatedSprite = new PIXI.extras.AnimatedSprite(textureArray);
  22. * ```
  23. *
  24. * The more efficient and simpler way to create an animated sprite is using a {@link PIXI.Spritesheet}
  25. * containing the animation definitions:
  26. *
  27. * ```js
  28. * PIXI.loader.add("assets/spritesheet.json").load(setup);
  29. *
  30. * function setup() {
  31. * let sheet = PIXI.loader.resources["assets/spritesheet.json"].spritesheet;
  32. * animatedSprite = new PIXI.extras.AnimatedSprite(sheet.animations["image_sequence"]);
  33. * ...
  34. * }
  35. * ```
  36. *
  37. * @class
  38. * @extends PIXI.Sprite
  39. * @memberof PIXI.extras
  40. */
  41. export default class AnimatedSprite extends core.Sprite
  42. {
  43. /**
  44. * @param {PIXI.Texture[]|PIXI.extras.AnimatedSprite~FrameObject[]} textures - an array of {@link PIXI.Texture} or frame
  45. * objects that make up the animation
  46. * @param {boolean} [autoUpdate=true] - Whether to use PIXI.ticker.shared to auto update animation time.
  47. */
  48. constructor(textures, autoUpdate)
  49. {
  50. super(textures[0] instanceof core.Texture ? textures[0] : textures[0].texture);
  51. /**
  52. * @private
  53. */
  54. this._textures = null;
  55. /**
  56. * @private
  57. */
  58. this._durations = null;
  59. this.textures = textures;
  60. /**
  61. * `true` uses PIXI.ticker.shared to auto update animation time.
  62. * @type {boolean}
  63. * @default true
  64. * @private
  65. */
  66. this._autoUpdate = autoUpdate !== false;
  67. /**
  68. * The speed that the AnimatedSprite will play at. Higher is faster, lower is slower
  69. *
  70. * @member {number}
  71. * @default 1
  72. */
  73. this.animationSpeed = 1;
  74. /**
  75. * Whether or not the animate sprite repeats after playing.
  76. *
  77. * @member {boolean}
  78. * @default true
  79. */
  80. this.loop = true;
  81. /**
  82. * Update anchor to [Texture's defaultAnchor]{@link PIXI.Texture#defaultAnchor} when frame changes.
  83. *
  84. * Useful with [sprite sheet animations]{@link PIXI.Spritesheet#animations} created with tools.
  85. * Changing anchor for each frame allows to pin sprite origin to certain moving feature
  86. * of the frame (e.g. left foot).
  87. *
  88. * Note: Enabling this will override any previously set `anchor` on each frame change.
  89. *
  90. * @member {boolean}
  91. * @default false
  92. */
  93. this.updateAnchor = false;
  94. /**
  95. * Function to call when a AnimatedSprite finishes playing
  96. *
  97. * @member {Function}
  98. */
  99. this.onComplete = null;
  100. /**
  101. * Function to call when a AnimatedSprite changes which texture is being rendered
  102. *
  103. * @member {Function}
  104. */
  105. this.onFrameChange = null;
  106. /**
  107. * Function to call when 'loop' is true, and an AnimatedSprite is played and loops around to start again
  108. *
  109. * @member {Function}
  110. */
  111. this.onLoop = null;
  112. /**
  113. * Elapsed time since animation has been started, used internally to display current texture
  114. *
  115. * @member {number}
  116. * @private
  117. */
  118. this._currentTime = 0;
  119. /**
  120. * Indicates if the AnimatedSprite is currently playing
  121. *
  122. * @member {boolean}
  123. * @readonly
  124. */
  125. this.playing = false;
  126. }
  127. /**
  128. * Stops the AnimatedSprite
  129. *
  130. */
  131. stop()
  132. {
  133. if (!this.playing)
  134. {
  135. return;
  136. }
  137. this.playing = false;
  138. if (this._autoUpdate)
  139. {
  140. core.ticker.shared.remove(this.update, this);
  141. }
  142. }
  143. /**
  144. * Plays the AnimatedSprite
  145. *
  146. */
  147. play()
  148. {
  149. if (this.playing)
  150. {
  151. return;
  152. }
  153. this.playing = true;
  154. if (this._autoUpdate)
  155. {
  156. core.ticker.shared.add(this.update, this, core.UPDATE_PRIORITY.HIGH);
  157. }
  158. }
  159. /**
  160. * Stops the AnimatedSprite and goes to a specific frame
  161. *
  162. * @param {number} frameNumber - frame index to stop at
  163. */
  164. gotoAndStop(frameNumber)
  165. {
  166. this.stop();
  167. const previousFrame = this.currentFrame;
  168. this._currentTime = frameNumber;
  169. if (previousFrame !== this.currentFrame)
  170. {
  171. this.updateTexture();
  172. }
  173. }
  174. /**
  175. * Goes to a specific frame and begins playing the AnimatedSprite
  176. *
  177. * @param {number} frameNumber - frame index to start at
  178. */
  179. gotoAndPlay(frameNumber)
  180. {
  181. const previousFrame = this.currentFrame;
  182. this._currentTime = frameNumber;
  183. if (previousFrame !== this.currentFrame)
  184. {
  185. this.updateTexture();
  186. }
  187. this.play();
  188. }
  189. /**
  190. * Updates the object transform for rendering.
  191. *
  192. * @private
  193. * @param {number} deltaTime - Time since last tick.
  194. */
  195. update(deltaTime)
  196. {
  197. const elapsed = this.animationSpeed * deltaTime;
  198. const previousFrame = this.currentFrame;
  199. if (this._durations !== null)
  200. {
  201. let lag = this._currentTime % 1 * this._durations[this.currentFrame];
  202. lag += elapsed / 60 * 1000;
  203. while (lag < 0)
  204. {
  205. this._currentTime--;
  206. lag += this._durations[this.currentFrame];
  207. }
  208. const sign = Math.sign(this.animationSpeed * deltaTime);
  209. this._currentTime = Math.floor(this._currentTime);
  210. while (lag >= this._durations[this.currentFrame])
  211. {
  212. lag -= this._durations[this.currentFrame] * sign;
  213. this._currentTime += sign;
  214. }
  215. this._currentTime += lag / this._durations[this.currentFrame];
  216. }
  217. else
  218. {
  219. this._currentTime += elapsed;
  220. }
  221. if (this._currentTime < 0 && !this.loop)
  222. {
  223. this.gotoAndStop(0);
  224. if (this.onComplete)
  225. {
  226. this.onComplete();
  227. }
  228. }
  229. else if (this._currentTime >= this._textures.length && !this.loop)
  230. {
  231. this.gotoAndStop(this._textures.length - 1);
  232. if (this.onComplete)
  233. {
  234. this.onComplete();
  235. }
  236. }
  237. else if (previousFrame !== this.currentFrame)
  238. {
  239. if (this.loop && this.onLoop)
  240. {
  241. if (this.animationSpeed > 0 && this.currentFrame < previousFrame)
  242. {
  243. this.onLoop();
  244. }
  245. else if (this.animationSpeed < 0 && this.currentFrame > previousFrame)
  246. {
  247. this.onLoop();
  248. }
  249. }
  250. this.updateTexture();
  251. }
  252. }
  253. /**
  254. * Updates the displayed texture to match the current frame index
  255. *
  256. * @private
  257. */
  258. updateTexture()
  259. {
  260. this._texture = this._textures[this.currentFrame];
  261. this._textureID = -1;
  262. this.cachedTint = 0xFFFFFF;
  263. if (this.updateAnchor)
  264. {
  265. this._anchor.copy(this._texture.defaultAnchor);
  266. }
  267. if (this.onFrameChange)
  268. {
  269. this.onFrameChange(this.currentFrame);
  270. }
  271. }
  272. /**
  273. * Stops the AnimatedSprite and destroys it
  274. *
  275. * @param {object|boolean} [options] - Options parameter. A boolean will act as if all options
  276. * have been set to that value
  277. * @param {boolean} [options.children=false] - if set to true, all the children will have their destroy
  278. * method called as well. 'options' will be passed on to those calls.
  279. * @param {boolean} [options.texture=false] - Should it destroy the current texture of the sprite as well
  280. * @param {boolean} [options.baseTexture=false] - Should it destroy the base texture of the sprite as well
  281. */
  282. destroy(options)
  283. {
  284. this.stop();
  285. super.destroy(options);
  286. }
  287. /**
  288. * A short hand way of creating a movieclip from an array of frame ids
  289. *
  290. * @static
  291. * @param {string[]} frames - The array of frames ids the movieclip will use as its texture frames
  292. * @return {AnimatedSprite} The new animated sprite with the specified frames.
  293. */
  294. static fromFrames(frames)
  295. {
  296. const textures = [];
  297. for (let i = 0; i < frames.length; ++i)
  298. {
  299. textures.push(core.Texture.fromFrame(frames[i]));
  300. }
  301. return new AnimatedSprite(textures);
  302. }
  303. /**
  304. * A short hand way of creating a movieclip from an array of image ids
  305. *
  306. * @static
  307. * @param {string[]} images - the array of image urls the movieclip will use as its texture frames
  308. * @return {AnimatedSprite} The new animate sprite with the specified images as frames.
  309. */
  310. static fromImages(images)
  311. {
  312. const textures = [];
  313. for (let i = 0; i < images.length; ++i)
  314. {
  315. textures.push(core.Texture.fromImage(images[i]));
  316. }
  317. return new AnimatedSprite(textures);
  318. }
  319. /**
  320. * totalFrames is the total number of frames in the AnimatedSprite. This is the same as number of textures
  321. * assigned to the AnimatedSprite.
  322. *
  323. * @readonly
  324. * @member {number}
  325. * @default 0
  326. */
  327. get totalFrames()
  328. {
  329. return this._textures.length;
  330. }
  331. /**
  332. * The array of textures used for this AnimatedSprite
  333. *
  334. * @member {PIXI.Texture[]}
  335. */
  336. get textures()
  337. {
  338. return this._textures;
  339. }
  340. set textures(value) // eslint-disable-line require-jsdoc
  341. {
  342. if (value[0] instanceof core.Texture)
  343. {
  344. this._textures = value;
  345. this._durations = null;
  346. }
  347. else
  348. {
  349. this._textures = [];
  350. this._durations = [];
  351. for (let i = 0; i < value.length; i++)
  352. {
  353. this._textures.push(value[i].texture);
  354. this._durations.push(value[i].time);
  355. }
  356. }
  357. this.gotoAndStop(0);
  358. this.updateTexture();
  359. }
  360. /**
  361. * The AnimatedSprites current frame index
  362. *
  363. * @member {number}
  364. * @readonly
  365. */
  366. get currentFrame()
  367. {
  368. let currentFrame = Math.floor(this._currentTime) % this._textures.length;
  369. if (currentFrame < 0)
  370. {
  371. currentFrame += this._textures.length;
  372. }
  373. return currentFrame;
  374. }
  375. }