UNPKG

pixi.js

Version:

<p align="center"> <a href="https://pixijs.com" target="_blank" rel="noopener noreferrer"> <img height="150" src="https://files.pixijs.download/branding/pixijs-logo-transparent-dark.svg?v=1" alt="PixiJS logo"> </a> </p> <br/> <p align="center">

526 lines (522 loc) 16.5 kB
'use strict'; var Texture = require('../../rendering/renderers/shared/texture/Texture.js'); var _const = require('../../ticker/const.js'); var Ticker = require('../../ticker/Ticker.js'); var Sprite = require('../sprite/Sprite.js'); "use strict"; class AnimatedSprite extends Sprite.Sprite { /** @ignore */ constructor(...args) { let options = args[0]; if (Array.isArray(args[0])) { options = { textures: args[0], autoUpdate: args[1] }; } const { animationSpeed = 1, autoPlay = false, autoUpdate = true, loop = true, onComplete = null, onFrameChange = null, onLoop = null, textures, updateAnchor = false, ...rest } = options; const [firstFrame] = textures; super({ ...rest, texture: firstFrame instanceof Texture.Texture ? firstFrame : firstFrame.texture }); this._textures = null; this._durations = null; this._autoUpdate = autoUpdate; this._isConnectedToTicker = false; this.animationSpeed = animationSpeed; this.loop = loop; this.updateAnchor = updateAnchor; this.onComplete = onComplete; this.onFrameChange = onFrameChange; this.onLoop = onLoop; this._currentTime = 0; this._playing = false; this._previousFrame = null; this.textures = textures; if (autoPlay) { this.play(); } } /** * Stops the animation playback and freezes the current frame. * Does not reset the current frame or animation progress. * @example * ```ts * // Create an animated sprite * const sprite = new AnimatedSprite({ * textures: [ * Texture.from('walk1.png'), * Texture.from('walk2.png'), * Texture.from('walk3.png') * ], * autoPlay: true * }); * * // Stop at current frame * sprite.stop(); * * // Stop at specific frame * sprite.gotoAndStop(1); // Stops at second frame * * // Stop and reset * sprite.stop(); * sprite.currentFrame = 0; * * // Stop with completion check * if (sprite.playing) { * sprite.stop(); * sprite.onComplete?.(); * } * ``` * @see {@link AnimatedSprite#play} For starting playback * @see {@link AnimatedSprite#gotoAndStop} For stopping at a specific frame * @see {@link AnimatedSprite#playing} For checking play state */ stop() { if (!this._playing) { return; } this._playing = false; if (this._autoUpdate && this._isConnectedToTicker) { Ticker.Ticker.shared.remove(this.update, this); this._isConnectedToTicker = false; } } /** * Starts or resumes the animation playback. * If the animation was previously stopped, it will continue from where it left off. * @example * ```ts * // Basic playback * const sprite = new AnimatedSprite({ * textures: [ * Texture.from('walk1.png'), * Texture.from('walk2.png'), * ], * autoPlay: false * }); * sprite.play(); * * // Play after stopping * sprite.stop(); * sprite.currentFrame = 0; // Reset to start * sprite.play(); // Play from beginning * * // Play with auto-update disabled * sprite.autoUpdate = false; * sprite.play(); * app.ticker.add(() => { * sprite.update(app.ticker); // Manual updates * }); * ``` * @see {@link AnimatedSprite#stop} For stopping playback * @see {@link AnimatedSprite#gotoAndPlay} For playing from a specific frame * @see {@link AnimatedSprite#playing} For checking play state */ play() { if (this._playing) { return; } this._playing = true; if (this._autoUpdate && !this._isConnectedToTicker) { Ticker.Ticker.shared.add(this.update, this, _const.UPDATE_PRIORITY.HIGH); this._isConnectedToTicker = true; } } /** * Stops the AnimatedSprite and sets it to a specific frame. * @example * ```ts * // Create an animated sprite * const sprite = new AnimatedSprite({ * textures: [ * Texture.from('walk1.png'), * Texture.from('walk2.png'), * Texture.from('walk3.png'), * ] * }); * * // Go to specific frames * sprite.gotoAndStop(0); // First frame * sprite.gotoAndStop(2); // Third frame * * // Jump to last frame * sprite.gotoAndStop(sprite.totalFrames - 1); * ``` * @param frameNumber - Frame index to stop at (0-based) * @throws {Error} If frameNumber is out of bounds * @see {@link AnimatedSprite#gotoAndPlay} For going to a frame and playing * @see {@link AnimatedSprite#currentFrame} For getting/setting current frame * @see {@link AnimatedSprite#totalFrames} For total number of frames */ gotoAndStop(frameNumber) { this.stop(); this.currentFrame = frameNumber; } /** * Goes to a specific frame and begins playing the AnimatedSprite from that point. * Combines frame navigation and playback start in one operation. * @example * ```ts * // Start from specific frame * sprite.gotoAndPlay(1); // Starts playing from second frame * ``` * @param frameNumber - Frame index to start playing from (0-based) * @throws {Error} If frameNumber is out of bounds * @see {@link AnimatedSprite#gotoAndStop} For going to a frame without playing * @see {@link AnimatedSprite#play} For playing from current frame * @see {@link AnimatedSprite#currentFrame} For getting/setting current frame */ gotoAndPlay(frameNumber) { this.currentFrame = frameNumber; this.play(); } /** * Updates the object transform for rendering. This method handles animation timing, frame updates, * and manages looping behavior. * @example * ```ts * // Create an animated sprite with manual updates * const sprite = new AnimatedSprite({ * textures: [ * Texture.from('frame1.png'), * Texture.from('frame2.png'), * Texture.from('frame3.png') * ], * autoUpdate: false // Disable automatic updates * }); * * // Manual update with app ticker * app.ticker.add((ticker) => { * sprite.update(ticker); * }); * ``` * @param ticker - The ticker to use for updating the animation timing * @see {@link AnimatedSprite#autoUpdate} For controlling automatic updates * @see {@link AnimatedSprite#animationSpeed} For controlling animation speed * @see {@link Ticker} For timing system details */ update(ticker) { if (!this._playing) { return; } const deltaTime = ticker.deltaTime; const elapsed = this.animationSpeed * deltaTime; const previousFrame = this.currentFrame; if (this._durations !== null) { let lag = this._currentTime % 1 * this._durations[this.currentFrame]; lag += elapsed / 60 * 1e3; while (lag < 0) { this._currentTime--; lag += this._durations[this.currentFrame]; } const sign = Math.sign(this.animationSpeed * deltaTime); this._currentTime = Math.floor(this._currentTime); while (lag >= this._durations[this.currentFrame]) { lag -= this._durations[this.currentFrame] * sign; this._currentTime += sign; } this._currentTime += lag / this._durations[this.currentFrame]; } else { this._currentTime += elapsed; } if (this._currentTime < 0 && !this.loop) { this.gotoAndStop(0); if (this.onComplete) { this.onComplete(); } } else if (this._currentTime >= this._textures.length && !this.loop) { this.gotoAndStop(this._textures.length - 1); if (this.onComplete) { this.onComplete(); } } else if (previousFrame !== this.currentFrame) { if (this.loop && this.onLoop) { if (this.animationSpeed > 0 && this.currentFrame < previousFrame || this.animationSpeed < 0 && this.currentFrame > previousFrame) { this.onLoop(); } } this._updateTexture(); } } /** Updates the displayed texture to match the current frame index. */ _updateTexture() { const currentFrame = this.currentFrame; if (this._previousFrame === currentFrame) { return; } this._previousFrame = currentFrame; this.texture = this._textures[currentFrame]; if (this.updateAnchor && this.texture.defaultAnchor) { this.anchor.copyFrom(this.texture.defaultAnchor); } if (this.onFrameChange) { this.onFrameChange(this.currentFrame); } } /** * Stops the AnimatedSprite and destroys it. * @example * ```ts * // Destroy the sprite when done * sprite.destroy(); * ``` */ destroy() { this.stop(); super.destroy(); this.onComplete = null; this.onFrameChange = null; this.onLoop = null; } /** * A short hand way of creating an AnimatedSprite from an array of frame ids. * Uses texture frames from the cache to create an animation sequence. * @example * ```ts * // Create from frame IDs * const frameIds = [ * 'walk_001.png', * 'walk_002.png', * 'walk_003.png' * ]; * * const walkingAnimation = AnimatedSprite.fromFrames(frameIds); * walkingAnimation.play(); * ``` * @param frames - The array of frame ids to use for the animation * @returns A new animated sprite using the frames * @see {@link Texture.from} For texture creation from frames * @see {@link Spritesheet} For loading spritesheets */ static fromFrames(frames) { const textures = []; for (let i = 0; i < frames.length; ++i) { textures.push(Texture.Texture.from(frames[i])); } return new AnimatedSprite(textures); } /** * A short hand way of creating an AnimatedSprite from an array of image urls. * Each image will be used as a frame in the animation. * @example * ```ts * // Create from image URLs * const images = [ * 'assets/walk1.png', * 'assets/walk2.png', * 'assets/walk3.png' * ]; * * const walkingSprite = AnimatedSprite.fromImages(images); * walkingSprite.play(); * ``` * @param images - The array of image urls to use as frames * @returns A new animated sprite using the images as frames * @see {@link Assets} For asset loading and management * @see {@link Texture.from} For texture creation from images */ static fromImages(images) { const textures = []; for (let i = 0; i < images.length; ++i) { textures.push(Texture.Texture.from(images[i])); } return new AnimatedSprite(textures); } /** * The total number of frames in the AnimatedSprite. This is the same as number of textures * assigned to the AnimatedSprite. * @example * ```ts * // Create an animated sprite * const sprite = new AnimatedSprite({ * textures: [ * Texture.from('frame1.png'), * Texture.from('frame2.png'), * Texture.from('frame3.png') * ] * }); * * // Get total frames * console.log(sprite.totalFrames); // Outputs: 3 * * // Use with frame navigation * sprite.gotoAndStop(sprite.totalFrames - 1); // Go to last frame * ``` * @readonly * @see {@link AnimatedSprite#currentFrame} For the current frame index * @see {@link AnimatedSprite#textures} For the array of textures * @returns {number} The total number of frames */ get totalFrames() { return this._textures.length; } /** * The array of textures or frame objects used for the animation sequence. * Can be set to either an array of Textures or an array of FrameObjects with custom timing. * @example * ```ts * // Update textures at runtime * sprite.textures = [ * Texture.from('run1.png'), * Texture.from('run2.png') * ]; * * // Use custom frame timing * sprite.textures = [ * { texture: Texture.from('explosion1.png'), time: 100 }, * { texture: Texture.from('explosion2.png'), time: 200 }, * { texture: Texture.from('explosion3.png'), time: 300 } * ]; * * // Use with spritesheet * const sheet = await Assets.load('animations.json'); * sprite.textures = sheet.animations['walk']; * ``` * @type {AnimatedSpriteFrames} * @see {@link FrameObject} For frame timing options * @see {@link Spritesheet} For loading from spritesheets */ get textures() { return this._textures; } set textures(value) { if (value[0] instanceof Texture.Texture) { this._textures = value; this._durations = null; } else { this._textures = []; this._durations = []; for (let i = 0; i < value.length; i++) { this._textures.push(value[i].texture); this._durations.push(value[i].time); } } this._previousFrame = null; this.gotoAndStop(0); this._updateTexture(); } /** * Gets or sets the current frame index of the animation. * When setting, the value will be clamped between 0 and totalFrames - 1. * @example * ```ts * // Create an animated sprite * const sprite = new AnimatedSprite({ * textures: [ * Texture.from('walk1.png'), * Texture.from('walk2.png'), * Texture.from('walk3.png') * ] * }); * * // Get current frame * console.log(sprite.currentFrame); // 0 * * // Set specific frame * sprite.currentFrame = 1; // Show second frame * * // Use with frame callbacks * sprite.onFrameChange = (frame) => { * console.log(`Now showing frame: ${frame}`); * }; * sprite.currentFrame = 2; * ``` * @throws {Error} If attempting to set a frame index out of bounds * @see {@link AnimatedSprite#totalFrames} For the total number of frames * @see {@link AnimatedSprite#gotoAndPlay} For playing from a specific frame * @see {@link AnimatedSprite#gotoAndStop} For stopping at a specific frame */ get currentFrame() { let currentFrame = Math.floor(this._currentTime) % this._textures.length; if (currentFrame < 0) { currentFrame += this._textures.length; } return currentFrame; } set currentFrame(value) { if (value < 0 || value > this.totalFrames - 1) { throw new Error(`[AnimatedSprite]: Invalid frame index value ${value}, expected to be between 0 and totalFrames ${this.totalFrames}.`); } const previousFrame = this.currentFrame; this._currentTime = value; if (previousFrame !== this.currentFrame) { this._updateTexture(); } } /** * Indicates if the AnimatedSprite is currently playing. * This is a read-only property that reflects the current playback state. * @example * ```ts * // Check if animation is playing * console.log('Playing:', sprite.playing); // true * * // Use with play control * if (!sprite.playing) { * sprite.play(); * } * ``` * @readonly * @returns {boolean} True if the animation is currently playing * @see {@link AnimatedSprite#play} For starting playback * @see {@link AnimatedSprite#stop} For stopping playback * @see {@link AnimatedSprite#loop} For controlling looping behavior */ get playing() { return this._playing; } /** * Controls whether the animation automatically updates using the shared ticker. * When enabled, the animation will update on each frame. When disabled, you must * manually call update() to advance the animation. * @example * ```ts * // Create sprite with auto-update disabled * const sprite = new AnimatedSprite({ * textures: [], * autoUpdate: false * }); * * // Manual update with app ticker * app.ticker.add((ticker) => { * sprite.update(ticker); * }); * * // Enable auto-update later * sprite.autoUpdate = true; * ``` * @default true * @see {@link AnimatedSprite#update} For manual animation updates * @see {@link Ticker} For the timing system */ get autoUpdate() { return this._autoUpdate; } set autoUpdate(value) { if (value !== this._autoUpdate) { this._autoUpdate = value; if (!this._autoUpdate && this._isConnectedToTicker) { Ticker.Ticker.shared.remove(this.update, this); this._isConnectedToTicker = false; } else if (this._autoUpdate && !this._isConnectedToTicker && this._playing) { Ticker.Ticker.shared.add(this.update, this); this._isConnectedToTicker = true; } } } } exports.AnimatedSprite = AnimatedSprite; //# sourceMappingURL=AnimatedSprite.js.map