diff --git a/packages/core/src/textures/Texture.js b/packages/core/src/textures/Texture.js index 9148656..f9bb8bc 100644 --- a/packages/core/src/textures/Texture.js +++ b/packages/core/src/textures/Texture.js @@ -4,7 +4,7 @@ import TextureUvs from './TextureUvs'; import EventEmitter from 'eventemitter3'; import { settings } from '@pixi/settings'; -import { Rectangle } from '@pixi/math'; +import { Rectangle, Point } from '@pixi/math'; import { uid, TextureCache, getResolutionOfUrl } from '@pixi/utils'; /** @@ -41,8 +41,9 @@ * @param {PIXI.Rectangle} [orig] - The area of original texture * @param {PIXI.Rectangle} [trim] - Trimmed rectangle of original texture * @param {number} [rotate] - indicates how the texture was rotated by texture packer. See {@link PIXI.GroupD8} + * @param {PIXI.Point} [anchor] - Default anchor point used for sprite placement / rotation */ - constructor(baseTexture, frame, orig, trim, rotate) + constructor(baseTexture, frame, orig, trim, rotate, anchor) { super(); @@ -153,6 +154,14 @@ } /** + * Anchor point that is used as default if sprite is created with this texture. + * Changing the `defaultAnchor` at a later point of time will not update Sprite's anchor point. + * @member {PIXI.Point} + * @default {0,0} + */ + this.defaultAnchor = anchor ? new Point(anchor.x, anchor.y) : new Point(0, 0); + + /** * Update ID is observed by sprites and TextureMatrix instances. * Call updateUvs() to increment it. * diff --git a/packages/core/src/textures/Texture.js b/packages/core/src/textures/Texture.js index 9148656..f9bb8bc 100644 --- a/packages/core/src/textures/Texture.js +++ b/packages/core/src/textures/Texture.js @@ -4,7 +4,7 @@ import TextureUvs from './TextureUvs'; import EventEmitter from 'eventemitter3'; import { settings } from '@pixi/settings'; -import { Rectangle } from '@pixi/math'; +import { Rectangle, Point } from '@pixi/math'; import { uid, TextureCache, getResolutionOfUrl } from '@pixi/utils'; /** @@ -41,8 +41,9 @@ * @param {PIXI.Rectangle} [orig] - The area of original texture * @param {PIXI.Rectangle} [trim] - Trimmed rectangle of original texture * @param {number} [rotate] - indicates how the texture was rotated by texture packer. See {@link PIXI.GroupD8} + * @param {PIXI.Point} [anchor] - Default anchor point used for sprite placement / rotation */ - constructor(baseTexture, frame, orig, trim, rotate) + constructor(baseTexture, frame, orig, trim, rotate, anchor) { super(); @@ -153,6 +154,14 @@ } /** + * Anchor point that is used as default if sprite is created with this texture. + * Changing the `defaultAnchor` at a later point of time will not update Sprite's anchor point. + * @member {PIXI.Point} + * @default {0,0} + */ + this.defaultAnchor = anchor ? new Point(anchor.x, anchor.y) : new Point(0, 0); + + /** * Update ID is observed by sprites and TextureMatrix instances. * Call updateUvs() to increment it. * diff --git a/packages/sprite-animated/src/AnimatedSprite.js b/packages/sprite-animated/src/AnimatedSprite.js index d5f5fce..330c683 100644 --- a/packages/sprite-animated/src/AnimatedSprite.js +++ b/packages/sprite-animated/src/AnimatedSprite.js @@ -22,7 +22,20 @@ * textureArray.push(texture); * }; * - * let mc = new PIXI.AnimatedSprite(textureArray); + * let animatedSprite = new PIXI.extras.AnimatedSprite(textureArray); + * ``` + * + * The more efficient and simpler way to create an animated sprite is using a {@link PIXI.Spritesheet} + * containing the animation definitions: + * + * ```js + * PIXI.loader.add("assets/spritesheet.json").load(setup); + * + * function setup() { + * let sheet = PIXI.loader.resources["assets/spritesheet.json"].spritesheet; + * animatedSprite = new PIXI.extras.AnimatedSprite(sheet.animations["image_sequence"]); + * ... + * } * ``` * * @class diff --git a/packages/core/src/textures/Texture.js b/packages/core/src/textures/Texture.js index 9148656..f9bb8bc 100644 --- a/packages/core/src/textures/Texture.js +++ b/packages/core/src/textures/Texture.js @@ -4,7 +4,7 @@ import TextureUvs from './TextureUvs'; import EventEmitter from 'eventemitter3'; import { settings } from '@pixi/settings'; -import { Rectangle } from '@pixi/math'; +import { Rectangle, Point } from '@pixi/math'; import { uid, TextureCache, getResolutionOfUrl } from '@pixi/utils'; /** @@ -41,8 +41,9 @@ * @param {PIXI.Rectangle} [orig] - The area of original texture * @param {PIXI.Rectangle} [trim] - Trimmed rectangle of original texture * @param {number} [rotate] - indicates how the texture was rotated by texture packer. See {@link PIXI.GroupD8} + * @param {PIXI.Point} [anchor] - Default anchor point used for sprite placement / rotation */ - constructor(baseTexture, frame, orig, trim, rotate) + constructor(baseTexture, frame, orig, trim, rotate, anchor) { super(); @@ -153,6 +154,14 @@ } /** + * Anchor point that is used as default if sprite is created with this texture. + * Changing the `defaultAnchor` at a later point of time will not update Sprite's anchor point. + * @member {PIXI.Point} + * @default {0,0} + */ + this.defaultAnchor = anchor ? new Point(anchor.x, anchor.y) : new Point(0, 0); + + /** * Update ID is observed by sprites and TextureMatrix instances. * Call updateUvs() to increment it. * diff --git a/packages/sprite-animated/src/AnimatedSprite.js b/packages/sprite-animated/src/AnimatedSprite.js index d5f5fce..330c683 100644 --- a/packages/sprite-animated/src/AnimatedSprite.js +++ b/packages/sprite-animated/src/AnimatedSprite.js @@ -22,7 +22,20 @@ * textureArray.push(texture); * }; * - * let mc = new PIXI.AnimatedSprite(textureArray); + * let animatedSprite = new PIXI.extras.AnimatedSprite(textureArray); + * ``` + * + * The more efficient and simpler way to create an animated sprite is using a {@link PIXI.Spritesheet} + * containing the animation definitions: + * + * ```js + * PIXI.loader.add("assets/spritesheet.json").load(setup); + * + * function setup() { + * let sheet = PIXI.loader.resources["assets/spritesheet.json"].spritesheet; + * animatedSprite = new PIXI.extras.AnimatedSprite(sheet.animations["image_sequence"]); + * ... + * } * ``` * * @class diff --git a/packages/sprite/src/Sprite.js b/packages/sprite/src/Sprite.js index ebf1f6f..91546cf 100644 --- a/packages/sprite/src/Sprite.js +++ b/packages/sprite/src/Sprite.js @@ -15,6 +15,18 @@ * let sprite = new PIXI.Sprite.from('assets/image.png'); * ``` * + * The more efficient way to create sprites is using a {@link PIXI.Spritesheet}: + * + * ```js + * PIXI.loader.add("assets/spritesheet.json").load(setup); + * + * function setup() { + * let sheet = PIXI.loader.resources["assets/spritesheet.json"].spritesheet; + * let sprite = new PIXI.Sprite(sheet.textures["image.png"]); + * ... + * } + * ``` + * * @class * @extends PIXI.Container * @memberof PIXI @@ -30,14 +42,22 @@ /** * The anchor sets the origin point of the texture. - * The default is 0,0 this means the texture's origin is the top left. + * The default is 0,0 or taken from the {@link PIXI.Texture#defaultAnchor|Texture} + * passed to the constructor. A value of 0,0 means the texture's origin is the top left. * Setting the anchor to 0.5,0.5 means the texture's origin is centered. * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner. + * Note: Updating the {@link PIXI.Texture#defaultAnchor} after a Texture is + * created does _not_ update the Sprite's anchor values. * * @member {PIXI.ObservablePoint} * @private */ - this._anchor = new ObservablePoint(this._onAnchorUpdate, this); + this._anchor = new ObservablePoint( + this._onAnchorUpdate, + this, + (texture ? texture.defaultAnchor.x : 0), + (texture ? texture.defaultAnchor.y : 0) + ); /** * The texture that the sprite is using @@ -484,7 +504,8 @@ } /** - * The anchor sets the origin point of the text. + * The anchor sets the origin point of the text. The default value is taken from the {@link PIXI.Texture|Texture} + * and passed to the constructor. * * The default is `(0,0)`, this means the text's origin is the top left. * diff --git a/packages/core/src/textures/Texture.js b/packages/core/src/textures/Texture.js index 9148656..f9bb8bc 100644 --- a/packages/core/src/textures/Texture.js +++ b/packages/core/src/textures/Texture.js @@ -4,7 +4,7 @@ import TextureUvs from './TextureUvs'; import EventEmitter from 'eventemitter3'; import { settings } from '@pixi/settings'; -import { Rectangle } from '@pixi/math'; +import { Rectangle, Point } from '@pixi/math'; import { uid, TextureCache, getResolutionOfUrl } from '@pixi/utils'; /** @@ -41,8 +41,9 @@ * @param {PIXI.Rectangle} [orig] - The area of original texture * @param {PIXI.Rectangle} [trim] - Trimmed rectangle of original texture * @param {number} [rotate] - indicates how the texture was rotated by texture packer. See {@link PIXI.GroupD8} + * @param {PIXI.Point} [anchor] - Default anchor point used for sprite placement / rotation */ - constructor(baseTexture, frame, orig, trim, rotate) + constructor(baseTexture, frame, orig, trim, rotate, anchor) { super(); @@ -153,6 +154,14 @@ } /** + * Anchor point that is used as default if sprite is created with this texture. + * Changing the `defaultAnchor` at a later point of time will not update Sprite's anchor point. + * @member {PIXI.Point} + * @default {0,0} + */ + this.defaultAnchor = anchor ? new Point(anchor.x, anchor.y) : new Point(0, 0); + + /** * Update ID is observed by sprites and TextureMatrix instances. * Call updateUvs() to increment it. * diff --git a/packages/sprite-animated/src/AnimatedSprite.js b/packages/sprite-animated/src/AnimatedSprite.js index d5f5fce..330c683 100644 --- a/packages/sprite-animated/src/AnimatedSprite.js +++ b/packages/sprite-animated/src/AnimatedSprite.js @@ -22,7 +22,20 @@ * textureArray.push(texture); * }; * - * let mc = new PIXI.AnimatedSprite(textureArray); + * let animatedSprite = new PIXI.extras.AnimatedSprite(textureArray); + * ``` + * + * The more efficient and simpler way to create an animated sprite is using a {@link PIXI.Spritesheet} + * containing the animation definitions: + * + * ```js + * PIXI.loader.add("assets/spritesheet.json").load(setup); + * + * function setup() { + * let sheet = PIXI.loader.resources["assets/spritesheet.json"].spritesheet; + * animatedSprite = new PIXI.extras.AnimatedSprite(sheet.animations["image_sequence"]); + * ... + * } * ``` * * @class diff --git a/packages/sprite/src/Sprite.js b/packages/sprite/src/Sprite.js index ebf1f6f..91546cf 100644 --- a/packages/sprite/src/Sprite.js +++ b/packages/sprite/src/Sprite.js @@ -15,6 +15,18 @@ * let sprite = new PIXI.Sprite.from('assets/image.png'); * ``` * + * The more efficient way to create sprites is using a {@link PIXI.Spritesheet}: + * + * ```js + * PIXI.loader.add("assets/spritesheet.json").load(setup); + * + * function setup() { + * let sheet = PIXI.loader.resources["assets/spritesheet.json"].spritesheet; + * let sprite = new PIXI.Sprite(sheet.textures["image.png"]); + * ... + * } + * ``` + * * @class * @extends PIXI.Container * @memberof PIXI @@ -30,14 +42,22 @@ /** * The anchor sets the origin point of the texture. - * The default is 0,0 this means the texture's origin is the top left. + * The default is 0,0 or taken from the {@link PIXI.Texture#defaultAnchor|Texture} + * passed to the constructor. A value of 0,0 means the texture's origin is the top left. * Setting the anchor to 0.5,0.5 means the texture's origin is centered. * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner. + * Note: Updating the {@link PIXI.Texture#defaultAnchor} after a Texture is + * created does _not_ update the Sprite's anchor values. * * @member {PIXI.ObservablePoint} * @private */ - this._anchor = new ObservablePoint(this._onAnchorUpdate, this); + this._anchor = new ObservablePoint( + this._onAnchorUpdate, + this, + (texture ? texture.defaultAnchor.x : 0), + (texture ? texture.defaultAnchor.y : 0) + ); /** * The texture that the sprite is using @@ -484,7 +504,8 @@ } /** - * The anchor sets the origin point of the text. + * The anchor sets the origin point of the text. The default value is taken from the {@link PIXI.Texture|Texture} + * and passed to the constructor. * * The default is `(0,0)`, this means the text's origin is the top left. * diff --git a/packages/spritesheet/src/Spritesheet.js b/packages/spritesheet/src/Spritesheet.js index 64d932d..28597d7 100644 --- a/packages/spritesheet/src/Spritesheet.js +++ b/packages/spritesheet/src/Spritesheet.js @@ -6,6 +6,23 @@ * Utility class for maintaining reference to a collection * of Textures on a single Spritesheet. * + * To access a sprite sheet from your code pass its JSON data file to Pixi's loader: + * + * ```js + * PIXI.loader.add("images/spritesheet.json").load(setup); + * + * function setup() { + * let sheet = PIXI.loader.resources["images/spritesheet.json"].spritesheet; + * ... + * } + * ``` + * With the `sheet.textures` you can create Sprite objects,`sheet.animations` can be used to create an AnimatedSprite. + * + * Sprite sheets can be packed using tools like {@link https://codeandweb.com/texturepacker|TexturePacker}, + * {@link https://renderhjs.net/shoebox/|Shoebox} or {@link https://github.com/krzysztof-o/spritesheet.js|Spritesheet.js}. + * Default anchor points (see {@link PIXI.Texture#defaultAnchor}) and grouping of animation sprites are currently only + * supported by TexturePacker. + * * @class * @memberof PIXI */ @@ -38,12 +55,26 @@ this.baseTexture = baseTexture; /** - * Map of spritesheet textures. - * @type {Object} + * A map containing all textures of the sprite sheet. + * Can be used to create a {@link PIXI.Sprite|Sprite}: + * ```js + * new PIXI.Sprite(sheet.textures["image.png"]); + * ``` + * @member {Object} */ this.textures = {}; /** + * A map containing the textures for each animation. + * Can be used to create an {@link PIXI.extras.AnimatedSprite|AnimatedSprite}: + * ```js + * new PIXI.extras.AnimatedSprite(sheet.animations["anim_name"]) + * ``` + * @member {Object} + */ + this.animations = {}; + + /** * Reference to the original JSON data. * @type {Object} */ @@ -134,6 +165,7 @@ if (this._frameKeys.length <= Spritesheet.BATCH_SIZE) { this._processFrames(0); + this._processAnimations(); this._parseComplete(); } else @@ -208,7 +240,8 @@ frame, orig, trim, - data.rotated ? 2 : 0 + data.rotated ? 2 : 0, + data.anchor ); // lets also add the frame to pixi's global cache for fromFrame and fromImage functions @@ -220,6 +253,27 @@ } /** + * Parse animations config + * + * @private + */ + _processAnimations() + { + const animations = this.data.animations || {}; + + for (const animName in animations) + { + this.animations[animName] = []; + for (let i = 0; i < animations[animName].length; i++) + { + const frameName = animations[animName][i]; + + this.animations[animName].push(this.textures[frameName]); + } + } + } + + /** * The parse has completed. * * @private @@ -250,6 +304,7 @@ } else { + this._processAnimations(); this._parseComplete(); } }, 0); diff --git a/packages/core/src/textures/Texture.js b/packages/core/src/textures/Texture.js index 9148656..f9bb8bc 100644 --- a/packages/core/src/textures/Texture.js +++ b/packages/core/src/textures/Texture.js @@ -4,7 +4,7 @@ import TextureUvs from './TextureUvs'; import EventEmitter from 'eventemitter3'; import { settings } from '@pixi/settings'; -import { Rectangle } from '@pixi/math'; +import { Rectangle, Point } from '@pixi/math'; import { uid, TextureCache, getResolutionOfUrl } from '@pixi/utils'; /** @@ -41,8 +41,9 @@ * @param {PIXI.Rectangle} [orig] - The area of original texture * @param {PIXI.Rectangle} [trim] - Trimmed rectangle of original texture * @param {number} [rotate] - indicates how the texture was rotated by texture packer. See {@link PIXI.GroupD8} + * @param {PIXI.Point} [anchor] - Default anchor point used for sprite placement / rotation */ - constructor(baseTexture, frame, orig, trim, rotate) + constructor(baseTexture, frame, orig, trim, rotate, anchor) { super(); @@ -153,6 +154,14 @@ } /** + * Anchor point that is used as default if sprite is created with this texture. + * Changing the `defaultAnchor` at a later point of time will not update Sprite's anchor point. + * @member {PIXI.Point} + * @default {0,0} + */ + this.defaultAnchor = anchor ? new Point(anchor.x, anchor.y) : new Point(0, 0); + + /** * Update ID is observed by sprites and TextureMatrix instances. * Call updateUvs() to increment it. * diff --git a/packages/sprite-animated/src/AnimatedSprite.js b/packages/sprite-animated/src/AnimatedSprite.js index d5f5fce..330c683 100644 --- a/packages/sprite-animated/src/AnimatedSprite.js +++ b/packages/sprite-animated/src/AnimatedSprite.js @@ -22,7 +22,20 @@ * textureArray.push(texture); * }; * - * let mc = new PIXI.AnimatedSprite(textureArray); + * let animatedSprite = new PIXI.extras.AnimatedSprite(textureArray); + * ``` + * + * The more efficient and simpler way to create an animated sprite is using a {@link PIXI.Spritesheet} + * containing the animation definitions: + * + * ```js + * PIXI.loader.add("assets/spritesheet.json").load(setup); + * + * function setup() { + * let sheet = PIXI.loader.resources["assets/spritesheet.json"].spritesheet; + * animatedSprite = new PIXI.extras.AnimatedSprite(sheet.animations["image_sequence"]); + * ... + * } * ``` * * @class diff --git a/packages/sprite/src/Sprite.js b/packages/sprite/src/Sprite.js index ebf1f6f..91546cf 100644 --- a/packages/sprite/src/Sprite.js +++ b/packages/sprite/src/Sprite.js @@ -15,6 +15,18 @@ * let sprite = new PIXI.Sprite.from('assets/image.png'); * ``` * + * The more efficient way to create sprites is using a {@link PIXI.Spritesheet}: + * + * ```js + * PIXI.loader.add("assets/spritesheet.json").load(setup); + * + * function setup() { + * let sheet = PIXI.loader.resources["assets/spritesheet.json"].spritesheet; + * let sprite = new PIXI.Sprite(sheet.textures["image.png"]); + * ... + * } + * ``` + * * @class * @extends PIXI.Container * @memberof PIXI @@ -30,14 +42,22 @@ /** * The anchor sets the origin point of the texture. - * The default is 0,0 this means the texture's origin is the top left. + * The default is 0,0 or taken from the {@link PIXI.Texture#defaultAnchor|Texture} + * passed to the constructor. A value of 0,0 means the texture's origin is the top left. * Setting the anchor to 0.5,0.5 means the texture's origin is centered. * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner. + * Note: Updating the {@link PIXI.Texture#defaultAnchor} after a Texture is + * created does _not_ update the Sprite's anchor values. * * @member {PIXI.ObservablePoint} * @private */ - this._anchor = new ObservablePoint(this._onAnchorUpdate, this); + this._anchor = new ObservablePoint( + this._onAnchorUpdate, + this, + (texture ? texture.defaultAnchor.x : 0), + (texture ? texture.defaultAnchor.y : 0) + ); /** * The texture that the sprite is using @@ -484,7 +504,8 @@ } /** - * The anchor sets the origin point of the text. + * The anchor sets the origin point of the text. The default value is taken from the {@link PIXI.Texture|Texture} + * and passed to the constructor. * * The default is `(0,0)`, this means the text's origin is the top left. * diff --git a/packages/spritesheet/src/Spritesheet.js b/packages/spritesheet/src/Spritesheet.js index 64d932d..28597d7 100644 --- a/packages/spritesheet/src/Spritesheet.js +++ b/packages/spritesheet/src/Spritesheet.js @@ -6,6 +6,23 @@ * Utility class for maintaining reference to a collection * of Textures on a single Spritesheet. * + * To access a sprite sheet from your code pass its JSON data file to Pixi's loader: + * + * ```js + * PIXI.loader.add("images/spritesheet.json").load(setup); + * + * function setup() { + * let sheet = PIXI.loader.resources["images/spritesheet.json"].spritesheet; + * ... + * } + * ``` + * With the `sheet.textures` you can create Sprite objects,`sheet.animations` can be used to create an AnimatedSprite. + * + * Sprite sheets can be packed using tools like {@link https://codeandweb.com/texturepacker|TexturePacker}, + * {@link https://renderhjs.net/shoebox/|Shoebox} or {@link https://github.com/krzysztof-o/spritesheet.js|Spritesheet.js}. + * Default anchor points (see {@link PIXI.Texture#defaultAnchor}) and grouping of animation sprites are currently only + * supported by TexturePacker. + * * @class * @memberof PIXI */ @@ -38,12 +55,26 @@ this.baseTexture = baseTexture; /** - * Map of spritesheet textures. - * @type {Object} + * A map containing all textures of the sprite sheet. + * Can be used to create a {@link PIXI.Sprite|Sprite}: + * ```js + * new PIXI.Sprite(sheet.textures["image.png"]); + * ``` + * @member {Object} */ this.textures = {}; /** + * A map containing the textures for each animation. + * Can be used to create an {@link PIXI.extras.AnimatedSprite|AnimatedSprite}: + * ```js + * new PIXI.extras.AnimatedSprite(sheet.animations["anim_name"]) + * ``` + * @member {Object} + */ + this.animations = {}; + + /** * Reference to the original JSON data. * @type {Object} */ @@ -134,6 +165,7 @@ if (this._frameKeys.length <= Spritesheet.BATCH_SIZE) { this._processFrames(0); + this._processAnimations(); this._parseComplete(); } else @@ -208,7 +240,8 @@ frame, orig, trim, - data.rotated ? 2 : 0 + data.rotated ? 2 : 0, + data.anchor ); // lets also add the frame to pixi's global cache for fromFrame and fromImage functions @@ -220,6 +253,27 @@ } /** + * Parse animations config + * + * @private + */ + _processAnimations() + { + const animations = this.data.animations || {}; + + for (const animName in animations) + { + this.animations[animName] = []; + for (let i = 0; i < animations[animName].length; i++) + { + const frameName = animations[animName][i]; + + this.animations[animName].push(this.textures[frameName]); + } + } + } + + /** * The parse has completed. * * @private @@ -250,6 +304,7 @@ } else { + this._processAnimations(); this._parseComplete(); } }, 0); diff --git a/packages/spritesheet/test/Spritesheet.js b/packages/spritesheet/test/Spritesheet.js index b168fe0..5063a45 100644 --- a/packages/spritesheet/test/Spritesheet.js +++ b/packages/spritesheet/test/Spritesheet.js @@ -15,12 +15,20 @@ const width = Math.floor(spritesheet.data.frames[id].frame.w); const height = Math.floor(spritesheet.data.frames[id].frame.h); - expect(Object.keys(textures).length).to.equal(1); - expect(Object.keys(spritesheet.textures).length).to.equal(1); + expect(Object.keys(textures).length).to.equal(5); + expect(Object.keys(spritesheet.textures).length).to.equal(5); expect(textures[id]).to.be.an.instanceof(Texture); expect(textures[id].width).to.equal(width / spritesheet.resolution); expect(textures[id].height).to.equal(height / spritesheet.resolution); + expect(textures[id].defaultAnchor.x).to.equal(0); + expect(textures[id].defaultAnchor.y).to.equal(0); expect(textures[id].textureCacheIds.indexOf(id)).to.equal(0); + + expect(this.animations).to.have.property('star').that.is.an('array'); + expect(this.animations.star.length).to.equal(4); + expect(this.animations.star[0].defaultAnchor.x).to.equal(0.5); + expect(this.animations.star[0].defaultAnchor.y).to.equal(0.5); + spritesheet.destroy(true); expect(spritesheet.textures).to.be.null; expect(spritesheet.baseTexture).to.be.null; diff --git a/packages/core/src/textures/Texture.js b/packages/core/src/textures/Texture.js index 9148656..f9bb8bc 100644 --- a/packages/core/src/textures/Texture.js +++ b/packages/core/src/textures/Texture.js @@ -4,7 +4,7 @@ import TextureUvs from './TextureUvs'; import EventEmitter from 'eventemitter3'; import { settings } from '@pixi/settings'; -import { Rectangle } from '@pixi/math'; +import { Rectangle, Point } from '@pixi/math'; import { uid, TextureCache, getResolutionOfUrl } from '@pixi/utils'; /** @@ -41,8 +41,9 @@ * @param {PIXI.Rectangle} [orig] - The area of original texture * @param {PIXI.Rectangle} [trim] - Trimmed rectangle of original texture * @param {number} [rotate] - indicates how the texture was rotated by texture packer. See {@link PIXI.GroupD8} + * @param {PIXI.Point} [anchor] - Default anchor point used for sprite placement / rotation */ - constructor(baseTexture, frame, orig, trim, rotate) + constructor(baseTexture, frame, orig, trim, rotate, anchor) { super(); @@ -153,6 +154,14 @@ } /** + * Anchor point that is used as default if sprite is created with this texture. + * Changing the `defaultAnchor` at a later point of time will not update Sprite's anchor point. + * @member {PIXI.Point} + * @default {0,0} + */ + this.defaultAnchor = anchor ? new Point(anchor.x, anchor.y) : new Point(0, 0); + + /** * Update ID is observed by sprites and TextureMatrix instances. * Call updateUvs() to increment it. * diff --git a/packages/sprite-animated/src/AnimatedSprite.js b/packages/sprite-animated/src/AnimatedSprite.js index d5f5fce..330c683 100644 --- a/packages/sprite-animated/src/AnimatedSprite.js +++ b/packages/sprite-animated/src/AnimatedSprite.js @@ -22,7 +22,20 @@ * textureArray.push(texture); * }; * - * let mc = new PIXI.AnimatedSprite(textureArray); + * let animatedSprite = new PIXI.extras.AnimatedSprite(textureArray); + * ``` + * + * The more efficient and simpler way to create an animated sprite is using a {@link PIXI.Spritesheet} + * containing the animation definitions: + * + * ```js + * PIXI.loader.add("assets/spritesheet.json").load(setup); + * + * function setup() { + * let sheet = PIXI.loader.resources["assets/spritesheet.json"].spritesheet; + * animatedSprite = new PIXI.extras.AnimatedSprite(sheet.animations["image_sequence"]); + * ... + * } * ``` * * @class diff --git a/packages/sprite/src/Sprite.js b/packages/sprite/src/Sprite.js index ebf1f6f..91546cf 100644 --- a/packages/sprite/src/Sprite.js +++ b/packages/sprite/src/Sprite.js @@ -15,6 +15,18 @@ * let sprite = new PIXI.Sprite.from('assets/image.png'); * ``` * + * The more efficient way to create sprites is using a {@link PIXI.Spritesheet}: + * + * ```js + * PIXI.loader.add("assets/spritesheet.json").load(setup); + * + * function setup() { + * let sheet = PIXI.loader.resources["assets/spritesheet.json"].spritesheet; + * let sprite = new PIXI.Sprite(sheet.textures["image.png"]); + * ... + * } + * ``` + * * @class * @extends PIXI.Container * @memberof PIXI @@ -30,14 +42,22 @@ /** * The anchor sets the origin point of the texture. - * The default is 0,0 this means the texture's origin is the top left. + * The default is 0,0 or taken from the {@link PIXI.Texture#defaultAnchor|Texture} + * passed to the constructor. A value of 0,0 means the texture's origin is the top left. * Setting the anchor to 0.5,0.5 means the texture's origin is centered. * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner. + * Note: Updating the {@link PIXI.Texture#defaultAnchor} after a Texture is + * created does _not_ update the Sprite's anchor values. * * @member {PIXI.ObservablePoint} * @private */ - this._anchor = new ObservablePoint(this._onAnchorUpdate, this); + this._anchor = new ObservablePoint( + this._onAnchorUpdate, + this, + (texture ? texture.defaultAnchor.x : 0), + (texture ? texture.defaultAnchor.y : 0) + ); /** * The texture that the sprite is using @@ -484,7 +504,8 @@ } /** - * The anchor sets the origin point of the text. + * The anchor sets the origin point of the text. The default value is taken from the {@link PIXI.Texture|Texture} + * and passed to the constructor. * * The default is `(0,0)`, this means the text's origin is the top left. * diff --git a/packages/spritesheet/src/Spritesheet.js b/packages/spritesheet/src/Spritesheet.js index 64d932d..28597d7 100644 --- a/packages/spritesheet/src/Spritesheet.js +++ b/packages/spritesheet/src/Spritesheet.js @@ -6,6 +6,23 @@ * Utility class for maintaining reference to a collection * of Textures on a single Spritesheet. * + * To access a sprite sheet from your code pass its JSON data file to Pixi's loader: + * + * ```js + * PIXI.loader.add("images/spritesheet.json").load(setup); + * + * function setup() { + * let sheet = PIXI.loader.resources["images/spritesheet.json"].spritesheet; + * ... + * } + * ``` + * With the `sheet.textures` you can create Sprite objects,`sheet.animations` can be used to create an AnimatedSprite. + * + * Sprite sheets can be packed using tools like {@link https://codeandweb.com/texturepacker|TexturePacker}, + * {@link https://renderhjs.net/shoebox/|Shoebox} or {@link https://github.com/krzysztof-o/spritesheet.js|Spritesheet.js}. + * Default anchor points (see {@link PIXI.Texture#defaultAnchor}) and grouping of animation sprites are currently only + * supported by TexturePacker. + * * @class * @memberof PIXI */ @@ -38,12 +55,26 @@ this.baseTexture = baseTexture; /** - * Map of spritesheet textures. - * @type {Object} + * A map containing all textures of the sprite sheet. + * Can be used to create a {@link PIXI.Sprite|Sprite}: + * ```js + * new PIXI.Sprite(sheet.textures["image.png"]); + * ``` + * @member {Object} */ this.textures = {}; /** + * A map containing the textures for each animation. + * Can be used to create an {@link PIXI.extras.AnimatedSprite|AnimatedSprite}: + * ```js + * new PIXI.extras.AnimatedSprite(sheet.animations["anim_name"]) + * ``` + * @member {Object} + */ + this.animations = {}; + + /** * Reference to the original JSON data. * @type {Object} */ @@ -134,6 +165,7 @@ if (this._frameKeys.length <= Spritesheet.BATCH_SIZE) { this._processFrames(0); + this._processAnimations(); this._parseComplete(); } else @@ -208,7 +240,8 @@ frame, orig, trim, - data.rotated ? 2 : 0 + data.rotated ? 2 : 0, + data.anchor ); // lets also add the frame to pixi's global cache for fromFrame and fromImage functions @@ -220,6 +253,27 @@ } /** + * Parse animations config + * + * @private + */ + _processAnimations() + { + const animations = this.data.animations || {}; + + for (const animName in animations) + { + this.animations[animName] = []; + for (let i = 0; i < animations[animName].length; i++) + { + const frameName = animations[animName][i]; + + this.animations[animName].push(this.textures[frameName]); + } + } + } + + /** * The parse has completed. * * @private @@ -250,6 +304,7 @@ } else { + this._processAnimations(); this._parseComplete(); } }, 0); diff --git a/packages/spritesheet/test/Spritesheet.js b/packages/spritesheet/test/Spritesheet.js index b168fe0..5063a45 100644 --- a/packages/spritesheet/test/Spritesheet.js +++ b/packages/spritesheet/test/Spritesheet.js @@ -15,12 +15,20 @@ const width = Math.floor(spritesheet.data.frames[id].frame.w); const height = Math.floor(spritesheet.data.frames[id].frame.h); - expect(Object.keys(textures).length).to.equal(1); - expect(Object.keys(spritesheet.textures).length).to.equal(1); + expect(Object.keys(textures).length).to.equal(5); + expect(Object.keys(spritesheet.textures).length).to.equal(5); expect(textures[id]).to.be.an.instanceof(Texture); expect(textures[id].width).to.equal(width / spritesheet.resolution); expect(textures[id].height).to.equal(height / spritesheet.resolution); + expect(textures[id].defaultAnchor.x).to.equal(0); + expect(textures[id].defaultAnchor.y).to.equal(0); expect(textures[id].textureCacheIds.indexOf(id)).to.equal(0); + + expect(this.animations).to.have.property('star').that.is.an('array'); + expect(this.animations.star.length).to.equal(4); + expect(this.animations.star[0].defaultAnchor.x).to.equal(0.5); + expect(this.animations.star[0].defaultAnchor.y).to.equal(0.5); + spritesheet.destroy(true); expect(spritesheet.textures).to.be.null; expect(spritesheet.baseTexture).to.be.null; diff --git a/packages/spritesheet/test/SpritesheetLoader.js b/packages/spritesheet/test/SpritesheetLoader.js index 2797100..932064c 100644 --- a/packages/spritesheet/test/SpritesheetLoader.js +++ b/packages/spritesheet/test/SpritesheetLoader.js @@ -75,6 +75,19 @@ .with.keys(Object.keys(getJsonSpritesheet().frames)) .and.has.property('0.png') .that.is.an.instanceof(Texture); + + expect(res.textures['0.png'].frame.x).to.equal(14); + expect(res.textures['0.png'].frame.y).to.equal(28); + expect(res.textures['0.png'].defaultAnchor.x).to.equal(0.3); + expect(res.textures['0.png'].defaultAnchor.y).to.equal(0.4); + expect(res.textures['1.png'].defaultAnchor.x).to.equal(0.0); // default of defaultAnchor is 0,0 + expect(res.textures['1.png'].defaultAnchor.y).to.equal(0.0); + + expect(res).to.have.property('spritesheet') + .to.have.property('animations') + .to.have.property('png123'); + expect(res.spritesheet.animations.png123.length).to.equal(3); + expect(res.spritesheet.animations.png123[0]).to.equal(res.textures['1.png']); }); it('should not load binary images as an image loader type', function (done) @@ -188,7 +201,8 @@ "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} + "sourceSize": {"w":14,"h":14}, + "anchor": {"x":0.3,"y":0.4} }, "1.png": { @@ -262,6 +276,9 @@ "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, "sourceSize": {"w":14,"h":14} }}, + "animations": { + "png123": [ "1.png", "2.png", "3.png" ] + }, "meta": { "app": "http://www.texturepacker.com", "version": "1.0", diff --git a/packages/core/src/textures/Texture.js b/packages/core/src/textures/Texture.js index 9148656..f9bb8bc 100644 --- a/packages/core/src/textures/Texture.js +++ b/packages/core/src/textures/Texture.js @@ -4,7 +4,7 @@ import TextureUvs from './TextureUvs'; import EventEmitter from 'eventemitter3'; import { settings } from '@pixi/settings'; -import { Rectangle } from '@pixi/math'; +import { Rectangle, Point } from '@pixi/math'; import { uid, TextureCache, getResolutionOfUrl } from '@pixi/utils'; /** @@ -41,8 +41,9 @@ * @param {PIXI.Rectangle} [orig] - The area of original texture * @param {PIXI.Rectangle} [trim] - Trimmed rectangle of original texture * @param {number} [rotate] - indicates how the texture was rotated by texture packer. See {@link PIXI.GroupD8} + * @param {PIXI.Point} [anchor] - Default anchor point used for sprite placement / rotation */ - constructor(baseTexture, frame, orig, trim, rotate) + constructor(baseTexture, frame, orig, trim, rotate, anchor) { super(); @@ -153,6 +154,14 @@ } /** + * Anchor point that is used as default if sprite is created with this texture. + * Changing the `defaultAnchor` at a later point of time will not update Sprite's anchor point. + * @member {PIXI.Point} + * @default {0,0} + */ + this.defaultAnchor = anchor ? new Point(anchor.x, anchor.y) : new Point(0, 0); + + /** * Update ID is observed by sprites and TextureMatrix instances. * Call updateUvs() to increment it. * diff --git a/packages/sprite-animated/src/AnimatedSprite.js b/packages/sprite-animated/src/AnimatedSprite.js index d5f5fce..330c683 100644 --- a/packages/sprite-animated/src/AnimatedSprite.js +++ b/packages/sprite-animated/src/AnimatedSprite.js @@ -22,7 +22,20 @@ * textureArray.push(texture); * }; * - * let mc = new PIXI.AnimatedSprite(textureArray); + * let animatedSprite = new PIXI.extras.AnimatedSprite(textureArray); + * ``` + * + * The more efficient and simpler way to create an animated sprite is using a {@link PIXI.Spritesheet} + * containing the animation definitions: + * + * ```js + * PIXI.loader.add("assets/spritesheet.json").load(setup); + * + * function setup() { + * let sheet = PIXI.loader.resources["assets/spritesheet.json"].spritesheet; + * animatedSprite = new PIXI.extras.AnimatedSprite(sheet.animations["image_sequence"]); + * ... + * } * ``` * * @class diff --git a/packages/sprite/src/Sprite.js b/packages/sprite/src/Sprite.js index ebf1f6f..91546cf 100644 --- a/packages/sprite/src/Sprite.js +++ b/packages/sprite/src/Sprite.js @@ -15,6 +15,18 @@ * let sprite = new PIXI.Sprite.from('assets/image.png'); * ``` * + * The more efficient way to create sprites is using a {@link PIXI.Spritesheet}: + * + * ```js + * PIXI.loader.add("assets/spritesheet.json").load(setup); + * + * function setup() { + * let sheet = PIXI.loader.resources["assets/spritesheet.json"].spritesheet; + * let sprite = new PIXI.Sprite(sheet.textures["image.png"]); + * ... + * } + * ``` + * * @class * @extends PIXI.Container * @memberof PIXI @@ -30,14 +42,22 @@ /** * The anchor sets the origin point of the texture. - * The default is 0,0 this means the texture's origin is the top left. + * The default is 0,0 or taken from the {@link PIXI.Texture#defaultAnchor|Texture} + * passed to the constructor. A value of 0,0 means the texture's origin is the top left. * Setting the anchor to 0.5,0.5 means the texture's origin is centered. * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner. + * Note: Updating the {@link PIXI.Texture#defaultAnchor} after a Texture is + * created does _not_ update the Sprite's anchor values. * * @member {PIXI.ObservablePoint} * @private */ - this._anchor = new ObservablePoint(this._onAnchorUpdate, this); + this._anchor = new ObservablePoint( + this._onAnchorUpdate, + this, + (texture ? texture.defaultAnchor.x : 0), + (texture ? texture.defaultAnchor.y : 0) + ); /** * The texture that the sprite is using @@ -484,7 +504,8 @@ } /** - * The anchor sets the origin point of the text. + * The anchor sets the origin point of the text. The default value is taken from the {@link PIXI.Texture|Texture} + * and passed to the constructor. * * The default is `(0,0)`, this means the text's origin is the top left. * diff --git a/packages/spritesheet/src/Spritesheet.js b/packages/spritesheet/src/Spritesheet.js index 64d932d..28597d7 100644 --- a/packages/spritesheet/src/Spritesheet.js +++ b/packages/spritesheet/src/Spritesheet.js @@ -6,6 +6,23 @@ * Utility class for maintaining reference to a collection * of Textures on a single Spritesheet. * + * To access a sprite sheet from your code pass its JSON data file to Pixi's loader: + * + * ```js + * PIXI.loader.add("images/spritesheet.json").load(setup); + * + * function setup() { + * let sheet = PIXI.loader.resources["images/spritesheet.json"].spritesheet; + * ... + * } + * ``` + * With the `sheet.textures` you can create Sprite objects,`sheet.animations` can be used to create an AnimatedSprite. + * + * Sprite sheets can be packed using tools like {@link https://codeandweb.com/texturepacker|TexturePacker}, + * {@link https://renderhjs.net/shoebox/|Shoebox} or {@link https://github.com/krzysztof-o/spritesheet.js|Spritesheet.js}. + * Default anchor points (see {@link PIXI.Texture#defaultAnchor}) and grouping of animation sprites are currently only + * supported by TexturePacker. + * * @class * @memberof PIXI */ @@ -38,12 +55,26 @@ this.baseTexture = baseTexture; /** - * Map of spritesheet textures. - * @type {Object} + * A map containing all textures of the sprite sheet. + * Can be used to create a {@link PIXI.Sprite|Sprite}: + * ```js + * new PIXI.Sprite(sheet.textures["image.png"]); + * ``` + * @member {Object} */ this.textures = {}; /** + * A map containing the textures for each animation. + * Can be used to create an {@link PIXI.extras.AnimatedSprite|AnimatedSprite}: + * ```js + * new PIXI.extras.AnimatedSprite(sheet.animations["anim_name"]) + * ``` + * @member {Object} + */ + this.animations = {}; + + /** * Reference to the original JSON data. * @type {Object} */ @@ -134,6 +165,7 @@ if (this._frameKeys.length <= Spritesheet.BATCH_SIZE) { this._processFrames(0); + this._processAnimations(); this._parseComplete(); } else @@ -208,7 +240,8 @@ frame, orig, trim, - data.rotated ? 2 : 0 + data.rotated ? 2 : 0, + data.anchor ); // lets also add the frame to pixi's global cache for fromFrame and fromImage functions @@ -220,6 +253,27 @@ } /** + * Parse animations config + * + * @private + */ + _processAnimations() + { + const animations = this.data.animations || {}; + + for (const animName in animations) + { + this.animations[animName] = []; + for (let i = 0; i < animations[animName].length; i++) + { + const frameName = animations[animName][i]; + + this.animations[animName].push(this.textures[frameName]); + } + } + } + + /** * The parse has completed. * * @private @@ -250,6 +304,7 @@ } else { + this._processAnimations(); this._parseComplete(); } }, 0); diff --git a/packages/spritesheet/test/Spritesheet.js b/packages/spritesheet/test/Spritesheet.js index b168fe0..5063a45 100644 --- a/packages/spritesheet/test/Spritesheet.js +++ b/packages/spritesheet/test/Spritesheet.js @@ -15,12 +15,20 @@ const width = Math.floor(spritesheet.data.frames[id].frame.w); const height = Math.floor(spritesheet.data.frames[id].frame.h); - expect(Object.keys(textures).length).to.equal(1); - expect(Object.keys(spritesheet.textures).length).to.equal(1); + expect(Object.keys(textures).length).to.equal(5); + expect(Object.keys(spritesheet.textures).length).to.equal(5); expect(textures[id]).to.be.an.instanceof(Texture); expect(textures[id].width).to.equal(width / spritesheet.resolution); expect(textures[id].height).to.equal(height / spritesheet.resolution); + expect(textures[id].defaultAnchor.x).to.equal(0); + expect(textures[id].defaultAnchor.y).to.equal(0); expect(textures[id].textureCacheIds.indexOf(id)).to.equal(0); + + expect(this.animations).to.have.property('star').that.is.an('array'); + expect(this.animations.star.length).to.equal(4); + expect(this.animations.star[0].defaultAnchor.x).to.equal(0.5); + expect(this.animations.star[0].defaultAnchor.y).to.equal(0.5); + spritesheet.destroy(true); expect(spritesheet.textures).to.be.null; expect(spritesheet.baseTexture).to.be.null; diff --git a/packages/spritesheet/test/SpritesheetLoader.js b/packages/spritesheet/test/SpritesheetLoader.js index 2797100..932064c 100644 --- a/packages/spritesheet/test/SpritesheetLoader.js +++ b/packages/spritesheet/test/SpritesheetLoader.js @@ -75,6 +75,19 @@ .with.keys(Object.keys(getJsonSpritesheet().frames)) .and.has.property('0.png') .that.is.an.instanceof(Texture); + + expect(res.textures['0.png'].frame.x).to.equal(14); + expect(res.textures['0.png'].frame.y).to.equal(28); + expect(res.textures['0.png'].defaultAnchor.x).to.equal(0.3); + expect(res.textures['0.png'].defaultAnchor.y).to.equal(0.4); + expect(res.textures['1.png'].defaultAnchor.x).to.equal(0.0); // default of defaultAnchor is 0,0 + expect(res.textures['1.png'].defaultAnchor.y).to.equal(0.0); + + expect(res).to.have.property('spritesheet') + .to.have.property('animations') + .to.have.property('png123'); + expect(res.spritesheet.animations.png123.length).to.equal(3); + expect(res.spritesheet.animations.png123[0]).to.equal(res.textures['1.png']); }); it('should not load binary images as an image loader type', function (done) @@ -188,7 +201,8 @@ "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} + "sourceSize": {"w":14,"h":14}, + "anchor": {"x":0.3,"y":0.4} }, "1.png": { @@ -262,6 +276,9 @@ "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, "sourceSize": {"w":14,"h":14} }}, + "animations": { + "png123": [ "1.png", "2.png", "3.png" ] + }, "meta": { "app": "http://www.texturepacker.com", "version": "1.0", diff --git a/packages/spritesheet/test/resources/building1.json b/packages/spritesheet/test/resources/building1.json old mode 100755 new mode 100644 index 03fa5c1..d8fbfe1 --- a/packages/spritesheet/test/resources/building1.json +++ b/packages/spritesheet/test/resources/building1.json @@ -6,16 +6,54 @@ "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":95,"h":115}, - "sourceSize": {"w":95,"h":115}, - "pivot": {"x":0.5,"y":0.5} + "sourceSize": {"w":95,"h":115} +}, +"star1.png": +{ + "frame": {"x":98,"y":33,"w":28,"h":29}, + "rotated": true, + "trimmed": true, + "spriteSourceSize": {"x":2,"y":2,"w":28,"h":29}, + "sourceSize": {"w":32,"h":32}, + "anchor": {"x":0.5,"y":0.5} +}, +"star2.png": +{ + "frame": {"x":98,"y":95,"w":26,"h":30}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":3,"y":1,"w":26,"h":30}, + "sourceSize": {"w":32,"h":32}, + "anchor": {"x":0.5,"y":0.5} +}, +"star3.png": +{ + "frame": {"x":98,"y":1,"w":30,"h":29}, + "rotated": true, + "trimmed": true, + "spriteSourceSize": {"x":1,"y":2,"w":30,"h":29}, + "sourceSize": {"w":32,"h":32}, + "anchor": {"x":0.5,"y":0.5} +}, +"star4.png": +{ + "frame": {"x":98,"y":63,"w":30,"h":27}, + "rotated": true, + "trimmed": true, + "spriteSourceSize": {"x":1,"y":3,"w":30,"h":27}, + "sourceSize": {"w":32,"h":32}, + "anchor": {"x":0.5,"y":0.5} }}, +"animations": { + "star": ["star1.png","star2.png","star3.png","star4.png"] +}, "meta": { - "app": "http://www.codeandweb.com/texturepacker", + "app": "https://www.codeandweb.com/texturepacker", "version": "1.0", "image": "building1.png", "format": "RGBA8888", - "size": {"w":128,"h":128}, + "size": {"w":128,"h":126}, "scale": "0.5", - "smartupdate": "$TexturePacker:SmartUpdate:d7a5e54c8f8a3fecd508baf190c44807:99a0e3a4dc0f441e4aad77614191ab38:6046b8eb706ddefaa771c33ceb7cb6d5$" + "smartupdate": "$TexturePacker:SmartUpdate:1cb0f14cbdd5e7bcecd332ecd0eaa9f7:8acde9d234ecca966a410602c71bffad:6046b8eb706ddefaa771c33ceb7cb6d5$" } } diff --git a/packages/core/src/textures/Texture.js b/packages/core/src/textures/Texture.js index 9148656..f9bb8bc 100644 --- a/packages/core/src/textures/Texture.js +++ b/packages/core/src/textures/Texture.js @@ -4,7 +4,7 @@ import TextureUvs from './TextureUvs'; import EventEmitter from 'eventemitter3'; import { settings } from '@pixi/settings'; -import { Rectangle } from '@pixi/math'; +import { Rectangle, Point } from '@pixi/math'; import { uid, TextureCache, getResolutionOfUrl } from '@pixi/utils'; /** @@ -41,8 +41,9 @@ * @param {PIXI.Rectangle} [orig] - The area of original texture * @param {PIXI.Rectangle} [trim] - Trimmed rectangle of original texture * @param {number} [rotate] - indicates how the texture was rotated by texture packer. See {@link PIXI.GroupD8} + * @param {PIXI.Point} [anchor] - Default anchor point used for sprite placement / rotation */ - constructor(baseTexture, frame, orig, trim, rotate) + constructor(baseTexture, frame, orig, trim, rotate, anchor) { super(); @@ -153,6 +154,14 @@ } /** + * Anchor point that is used as default if sprite is created with this texture. + * Changing the `defaultAnchor` at a later point of time will not update Sprite's anchor point. + * @member {PIXI.Point} + * @default {0,0} + */ + this.defaultAnchor = anchor ? new Point(anchor.x, anchor.y) : new Point(0, 0); + + /** * Update ID is observed by sprites and TextureMatrix instances. * Call updateUvs() to increment it. * diff --git a/packages/sprite-animated/src/AnimatedSprite.js b/packages/sprite-animated/src/AnimatedSprite.js index d5f5fce..330c683 100644 --- a/packages/sprite-animated/src/AnimatedSprite.js +++ b/packages/sprite-animated/src/AnimatedSprite.js @@ -22,7 +22,20 @@ * textureArray.push(texture); * }; * - * let mc = new PIXI.AnimatedSprite(textureArray); + * let animatedSprite = new PIXI.extras.AnimatedSprite(textureArray); + * ``` + * + * The more efficient and simpler way to create an animated sprite is using a {@link PIXI.Spritesheet} + * containing the animation definitions: + * + * ```js + * PIXI.loader.add("assets/spritesheet.json").load(setup); + * + * function setup() { + * let sheet = PIXI.loader.resources["assets/spritesheet.json"].spritesheet; + * animatedSprite = new PIXI.extras.AnimatedSprite(sheet.animations["image_sequence"]); + * ... + * } * ``` * * @class diff --git a/packages/sprite/src/Sprite.js b/packages/sprite/src/Sprite.js index ebf1f6f..91546cf 100644 --- a/packages/sprite/src/Sprite.js +++ b/packages/sprite/src/Sprite.js @@ -15,6 +15,18 @@ * let sprite = new PIXI.Sprite.from('assets/image.png'); * ``` * + * The more efficient way to create sprites is using a {@link PIXI.Spritesheet}: + * + * ```js + * PIXI.loader.add("assets/spritesheet.json").load(setup); + * + * function setup() { + * let sheet = PIXI.loader.resources["assets/spritesheet.json"].spritesheet; + * let sprite = new PIXI.Sprite(sheet.textures["image.png"]); + * ... + * } + * ``` + * * @class * @extends PIXI.Container * @memberof PIXI @@ -30,14 +42,22 @@ /** * The anchor sets the origin point of the texture. - * The default is 0,0 this means the texture's origin is the top left. + * The default is 0,0 or taken from the {@link PIXI.Texture#defaultAnchor|Texture} + * passed to the constructor. A value of 0,0 means the texture's origin is the top left. * Setting the anchor to 0.5,0.5 means the texture's origin is centered. * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner. + * Note: Updating the {@link PIXI.Texture#defaultAnchor} after a Texture is + * created does _not_ update the Sprite's anchor values. * * @member {PIXI.ObservablePoint} * @private */ - this._anchor = new ObservablePoint(this._onAnchorUpdate, this); + this._anchor = new ObservablePoint( + this._onAnchorUpdate, + this, + (texture ? texture.defaultAnchor.x : 0), + (texture ? texture.defaultAnchor.y : 0) + ); /** * The texture that the sprite is using @@ -484,7 +504,8 @@ } /** - * The anchor sets the origin point of the text. + * The anchor sets the origin point of the text. The default value is taken from the {@link PIXI.Texture|Texture} + * and passed to the constructor. * * The default is `(0,0)`, this means the text's origin is the top left. * diff --git a/packages/spritesheet/src/Spritesheet.js b/packages/spritesheet/src/Spritesheet.js index 64d932d..28597d7 100644 --- a/packages/spritesheet/src/Spritesheet.js +++ b/packages/spritesheet/src/Spritesheet.js @@ -6,6 +6,23 @@ * Utility class for maintaining reference to a collection * of Textures on a single Spritesheet. * + * To access a sprite sheet from your code pass its JSON data file to Pixi's loader: + * + * ```js + * PIXI.loader.add("images/spritesheet.json").load(setup); + * + * function setup() { + * let sheet = PIXI.loader.resources["images/spritesheet.json"].spritesheet; + * ... + * } + * ``` + * With the `sheet.textures` you can create Sprite objects,`sheet.animations` can be used to create an AnimatedSprite. + * + * Sprite sheets can be packed using tools like {@link https://codeandweb.com/texturepacker|TexturePacker}, + * {@link https://renderhjs.net/shoebox/|Shoebox} or {@link https://github.com/krzysztof-o/spritesheet.js|Spritesheet.js}. + * Default anchor points (see {@link PIXI.Texture#defaultAnchor}) and grouping of animation sprites are currently only + * supported by TexturePacker. + * * @class * @memberof PIXI */ @@ -38,12 +55,26 @@ this.baseTexture = baseTexture; /** - * Map of spritesheet textures. - * @type {Object} + * A map containing all textures of the sprite sheet. + * Can be used to create a {@link PIXI.Sprite|Sprite}: + * ```js + * new PIXI.Sprite(sheet.textures["image.png"]); + * ``` + * @member {Object} */ this.textures = {}; /** + * A map containing the textures for each animation. + * Can be used to create an {@link PIXI.extras.AnimatedSprite|AnimatedSprite}: + * ```js + * new PIXI.extras.AnimatedSprite(sheet.animations["anim_name"]) + * ``` + * @member {Object} + */ + this.animations = {}; + + /** * Reference to the original JSON data. * @type {Object} */ @@ -134,6 +165,7 @@ if (this._frameKeys.length <= Spritesheet.BATCH_SIZE) { this._processFrames(0); + this._processAnimations(); this._parseComplete(); } else @@ -208,7 +240,8 @@ frame, orig, trim, - data.rotated ? 2 : 0 + data.rotated ? 2 : 0, + data.anchor ); // lets also add the frame to pixi's global cache for fromFrame and fromImage functions @@ -220,6 +253,27 @@ } /** + * Parse animations config + * + * @private + */ + _processAnimations() + { + const animations = this.data.animations || {}; + + for (const animName in animations) + { + this.animations[animName] = []; + for (let i = 0; i < animations[animName].length; i++) + { + const frameName = animations[animName][i]; + + this.animations[animName].push(this.textures[frameName]); + } + } + } + + /** * The parse has completed. * * @private @@ -250,6 +304,7 @@ } else { + this._processAnimations(); this._parseComplete(); } }, 0); diff --git a/packages/spritesheet/test/Spritesheet.js b/packages/spritesheet/test/Spritesheet.js index b168fe0..5063a45 100644 --- a/packages/spritesheet/test/Spritesheet.js +++ b/packages/spritesheet/test/Spritesheet.js @@ -15,12 +15,20 @@ const width = Math.floor(spritesheet.data.frames[id].frame.w); const height = Math.floor(spritesheet.data.frames[id].frame.h); - expect(Object.keys(textures).length).to.equal(1); - expect(Object.keys(spritesheet.textures).length).to.equal(1); + expect(Object.keys(textures).length).to.equal(5); + expect(Object.keys(spritesheet.textures).length).to.equal(5); expect(textures[id]).to.be.an.instanceof(Texture); expect(textures[id].width).to.equal(width / spritesheet.resolution); expect(textures[id].height).to.equal(height / spritesheet.resolution); + expect(textures[id].defaultAnchor.x).to.equal(0); + expect(textures[id].defaultAnchor.y).to.equal(0); expect(textures[id].textureCacheIds.indexOf(id)).to.equal(0); + + expect(this.animations).to.have.property('star').that.is.an('array'); + expect(this.animations.star.length).to.equal(4); + expect(this.animations.star[0].defaultAnchor.x).to.equal(0.5); + expect(this.animations.star[0].defaultAnchor.y).to.equal(0.5); + spritesheet.destroy(true); expect(spritesheet.textures).to.be.null; expect(spritesheet.baseTexture).to.be.null; diff --git a/packages/spritesheet/test/SpritesheetLoader.js b/packages/spritesheet/test/SpritesheetLoader.js index 2797100..932064c 100644 --- a/packages/spritesheet/test/SpritesheetLoader.js +++ b/packages/spritesheet/test/SpritesheetLoader.js @@ -75,6 +75,19 @@ .with.keys(Object.keys(getJsonSpritesheet().frames)) .and.has.property('0.png') .that.is.an.instanceof(Texture); + + expect(res.textures['0.png'].frame.x).to.equal(14); + expect(res.textures['0.png'].frame.y).to.equal(28); + expect(res.textures['0.png'].defaultAnchor.x).to.equal(0.3); + expect(res.textures['0.png'].defaultAnchor.y).to.equal(0.4); + expect(res.textures['1.png'].defaultAnchor.x).to.equal(0.0); // default of defaultAnchor is 0,0 + expect(res.textures['1.png'].defaultAnchor.y).to.equal(0.0); + + expect(res).to.have.property('spritesheet') + .to.have.property('animations') + .to.have.property('png123'); + expect(res.spritesheet.animations.png123.length).to.equal(3); + expect(res.spritesheet.animations.png123[0]).to.equal(res.textures['1.png']); }); it('should not load binary images as an image loader type', function (done) @@ -188,7 +201,8 @@ "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} + "sourceSize": {"w":14,"h":14}, + "anchor": {"x":0.3,"y":0.4} }, "1.png": { @@ -262,6 +276,9 @@ "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, "sourceSize": {"w":14,"h":14} }}, + "animations": { + "png123": [ "1.png", "2.png", "3.png" ] + }, "meta": { "app": "http://www.texturepacker.com", "version": "1.0", diff --git a/packages/spritesheet/test/resources/building1.json b/packages/spritesheet/test/resources/building1.json old mode 100755 new mode 100644 index 03fa5c1..d8fbfe1 --- a/packages/spritesheet/test/resources/building1.json +++ b/packages/spritesheet/test/resources/building1.json @@ -6,16 +6,54 @@ "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":95,"h":115}, - "sourceSize": {"w":95,"h":115}, - "pivot": {"x":0.5,"y":0.5} + "sourceSize": {"w":95,"h":115} +}, +"star1.png": +{ + "frame": {"x":98,"y":33,"w":28,"h":29}, + "rotated": true, + "trimmed": true, + "spriteSourceSize": {"x":2,"y":2,"w":28,"h":29}, + "sourceSize": {"w":32,"h":32}, + "anchor": {"x":0.5,"y":0.5} +}, +"star2.png": +{ + "frame": {"x":98,"y":95,"w":26,"h":30}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":3,"y":1,"w":26,"h":30}, + "sourceSize": {"w":32,"h":32}, + "anchor": {"x":0.5,"y":0.5} +}, +"star3.png": +{ + "frame": {"x":98,"y":1,"w":30,"h":29}, + "rotated": true, + "trimmed": true, + "spriteSourceSize": {"x":1,"y":2,"w":30,"h":29}, + "sourceSize": {"w":32,"h":32}, + "anchor": {"x":0.5,"y":0.5} +}, +"star4.png": +{ + "frame": {"x":98,"y":63,"w":30,"h":27}, + "rotated": true, + "trimmed": true, + "spriteSourceSize": {"x":1,"y":3,"w":30,"h":27}, + "sourceSize": {"w":32,"h":32}, + "anchor": {"x":0.5,"y":0.5} }}, +"animations": { + "star": ["star1.png","star2.png","star3.png","star4.png"] +}, "meta": { - "app": "http://www.codeandweb.com/texturepacker", + "app": "https://www.codeandweb.com/texturepacker", "version": "1.0", "image": "building1.png", "format": "RGBA8888", - "size": {"w":128,"h":128}, + "size": {"w":128,"h":126}, "scale": "0.5", - "smartupdate": "$TexturePacker:SmartUpdate:d7a5e54c8f8a3fecd508baf190c44807:99a0e3a4dc0f441e4aad77614191ab38:6046b8eb706ddefaa771c33ceb7cb6d5$" + "smartupdate": "$TexturePacker:SmartUpdate:1cb0f14cbdd5e7bcecd332ecd0eaa9f7:8acde9d234ecca966a410602c71bffad:6046b8eb706ddefaa771c33ceb7cb6d5$" } } diff --git a/packages/spritesheet/test/resources/building1.png b/packages/spritesheet/test/resources/building1.png old mode 100755 new mode 100644 index 7e1e114..7e16cda --- a/packages/spritesheet/test/resources/building1.png +++ b/packages/spritesheet/test/resources/building1.png Binary files differ diff --git a/packages/core/src/textures/Texture.js b/packages/core/src/textures/Texture.js index 9148656..f9bb8bc 100644 --- a/packages/core/src/textures/Texture.js +++ b/packages/core/src/textures/Texture.js @@ -4,7 +4,7 @@ import TextureUvs from './TextureUvs'; import EventEmitter from 'eventemitter3'; import { settings } from '@pixi/settings'; -import { Rectangle } from '@pixi/math'; +import { Rectangle, Point } from '@pixi/math'; import { uid, TextureCache, getResolutionOfUrl } from '@pixi/utils'; /** @@ -41,8 +41,9 @@ * @param {PIXI.Rectangle} [orig] - The area of original texture * @param {PIXI.Rectangle} [trim] - Trimmed rectangle of original texture * @param {number} [rotate] - indicates how the texture was rotated by texture packer. See {@link PIXI.GroupD8} + * @param {PIXI.Point} [anchor] - Default anchor point used for sprite placement / rotation */ - constructor(baseTexture, frame, orig, trim, rotate) + constructor(baseTexture, frame, orig, trim, rotate, anchor) { super(); @@ -153,6 +154,14 @@ } /** + * Anchor point that is used as default if sprite is created with this texture. + * Changing the `defaultAnchor` at a later point of time will not update Sprite's anchor point. + * @member {PIXI.Point} + * @default {0,0} + */ + this.defaultAnchor = anchor ? new Point(anchor.x, anchor.y) : new Point(0, 0); + + /** * Update ID is observed by sprites and TextureMatrix instances. * Call updateUvs() to increment it. * diff --git a/packages/sprite-animated/src/AnimatedSprite.js b/packages/sprite-animated/src/AnimatedSprite.js index d5f5fce..330c683 100644 --- a/packages/sprite-animated/src/AnimatedSprite.js +++ b/packages/sprite-animated/src/AnimatedSprite.js @@ -22,7 +22,20 @@ * textureArray.push(texture); * }; * - * let mc = new PIXI.AnimatedSprite(textureArray); + * let animatedSprite = new PIXI.extras.AnimatedSprite(textureArray); + * ``` + * + * The more efficient and simpler way to create an animated sprite is using a {@link PIXI.Spritesheet} + * containing the animation definitions: + * + * ```js + * PIXI.loader.add("assets/spritesheet.json").load(setup); + * + * function setup() { + * let sheet = PIXI.loader.resources["assets/spritesheet.json"].spritesheet; + * animatedSprite = new PIXI.extras.AnimatedSprite(sheet.animations["image_sequence"]); + * ... + * } * ``` * * @class diff --git a/packages/sprite/src/Sprite.js b/packages/sprite/src/Sprite.js index ebf1f6f..91546cf 100644 --- a/packages/sprite/src/Sprite.js +++ b/packages/sprite/src/Sprite.js @@ -15,6 +15,18 @@ * let sprite = new PIXI.Sprite.from('assets/image.png'); * ``` * + * The more efficient way to create sprites is using a {@link PIXI.Spritesheet}: + * + * ```js + * PIXI.loader.add("assets/spritesheet.json").load(setup); + * + * function setup() { + * let sheet = PIXI.loader.resources["assets/spritesheet.json"].spritesheet; + * let sprite = new PIXI.Sprite(sheet.textures["image.png"]); + * ... + * } + * ``` + * * @class * @extends PIXI.Container * @memberof PIXI @@ -30,14 +42,22 @@ /** * The anchor sets the origin point of the texture. - * The default is 0,0 this means the texture's origin is the top left. + * The default is 0,0 or taken from the {@link PIXI.Texture#defaultAnchor|Texture} + * passed to the constructor. A value of 0,0 means the texture's origin is the top left. * Setting the anchor to 0.5,0.5 means the texture's origin is centered. * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner. + * Note: Updating the {@link PIXI.Texture#defaultAnchor} after a Texture is + * created does _not_ update the Sprite's anchor values. * * @member {PIXI.ObservablePoint} * @private */ - this._anchor = new ObservablePoint(this._onAnchorUpdate, this); + this._anchor = new ObservablePoint( + this._onAnchorUpdate, + this, + (texture ? texture.defaultAnchor.x : 0), + (texture ? texture.defaultAnchor.y : 0) + ); /** * The texture that the sprite is using @@ -484,7 +504,8 @@ } /** - * The anchor sets the origin point of the text. + * The anchor sets the origin point of the text. The default value is taken from the {@link PIXI.Texture|Texture} + * and passed to the constructor. * * The default is `(0,0)`, this means the text's origin is the top left. * diff --git a/packages/spritesheet/src/Spritesheet.js b/packages/spritesheet/src/Spritesheet.js index 64d932d..28597d7 100644 --- a/packages/spritesheet/src/Spritesheet.js +++ b/packages/spritesheet/src/Spritesheet.js @@ -6,6 +6,23 @@ * Utility class for maintaining reference to a collection * of Textures on a single Spritesheet. * + * To access a sprite sheet from your code pass its JSON data file to Pixi's loader: + * + * ```js + * PIXI.loader.add("images/spritesheet.json").load(setup); + * + * function setup() { + * let sheet = PIXI.loader.resources["images/spritesheet.json"].spritesheet; + * ... + * } + * ``` + * With the `sheet.textures` you can create Sprite objects,`sheet.animations` can be used to create an AnimatedSprite. + * + * Sprite sheets can be packed using tools like {@link https://codeandweb.com/texturepacker|TexturePacker}, + * {@link https://renderhjs.net/shoebox/|Shoebox} or {@link https://github.com/krzysztof-o/spritesheet.js|Spritesheet.js}. + * Default anchor points (see {@link PIXI.Texture#defaultAnchor}) and grouping of animation sprites are currently only + * supported by TexturePacker. + * * @class * @memberof PIXI */ @@ -38,12 +55,26 @@ this.baseTexture = baseTexture; /** - * Map of spritesheet textures. - * @type {Object} + * A map containing all textures of the sprite sheet. + * Can be used to create a {@link PIXI.Sprite|Sprite}: + * ```js + * new PIXI.Sprite(sheet.textures["image.png"]); + * ``` + * @member {Object} */ this.textures = {}; /** + * A map containing the textures for each animation. + * Can be used to create an {@link PIXI.extras.AnimatedSprite|AnimatedSprite}: + * ```js + * new PIXI.extras.AnimatedSprite(sheet.animations["anim_name"]) + * ``` + * @member {Object} + */ + this.animations = {}; + + /** * Reference to the original JSON data. * @type {Object} */ @@ -134,6 +165,7 @@ if (this._frameKeys.length <= Spritesheet.BATCH_SIZE) { this._processFrames(0); + this._processAnimations(); this._parseComplete(); } else @@ -208,7 +240,8 @@ frame, orig, trim, - data.rotated ? 2 : 0 + data.rotated ? 2 : 0, + data.anchor ); // lets also add the frame to pixi's global cache for fromFrame and fromImage functions @@ -220,6 +253,27 @@ } /** + * Parse animations config + * + * @private + */ + _processAnimations() + { + const animations = this.data.animations || {}; + + for (const animName in animations) + { + this.animations[animName] = []; + for (let i = 0; i < animations[animName].length; i++) + { + const frameName = animations[animName][i]; + + this.animations[animName].push(this.textures[frameName]); + } + } + } + + /** * The parse has completed. * * @private @@ -250,6 +304,7 @@ } else { + this._processAnimations(); this._parseComplete(); } }, 0); diff --git a/packages/spritesheet/test/Spritesheet.js b/packages/spritesheet/test/Spritesheet.js index b168fe0..5063a45 100644 --- a/packages/spritesheet/test/Spritesheet.js +++ b/packages/spritesheet/test/Spritesheet.js @@ -15,12 +15,20 @@ const width = Math.floor(spritesheet.data.frames[id].frame.w); const height = Math.floor(spritesheet.data.frames[id].frame.h); - expect(Object.keys(textures).length).to.equal(1); - expect(Object.keys(spritesheet.textures).length).to.equal(1); + expect(Object.keys(textures).length).to.equal(5); + expect(Object.keys(spritesheet.textures).length).to.equal(5); expect(textures[id]).to.be.an.instanceof(Texture); expect(textures[id].width).to.equal(width / spritesheet.resolution); expect(textures[id].height).to.equal(height / spritesheet.resolution); + expect(textures[id].defaultAnchor.x).to.equal(0); + expect(textures[id].defaultAnchor.y).to.equal(0); expect(textures[id].textureCacheIds.indexOf(id)).to.equal(0); + + expect(this.animations).to.have.property('star').that.is.an('array'); + expect(this.animations.star.length).to.equal(4); + expect(this.animations.star[0].defaultAnchor.x).to.equal(0.5); + expect(this.animations.star[0].defaultAnchor.y).to.equal(0.5); + spritesheet.destroy(true); expect(spritesheet.textures).to.be.null; expect(spritesheet.baseTexture).to.be.null; diff --git a/packages/spritesheet/test/SpritesheetLoader.js b/packages/spritesheet/test/SpritesheetLoader.js index 2797100..932064c 100644 --- a/packages/spritesheet/test/SpritesheetLoader.js +++ b/packages/spritesheet/test/SpritesheetLoader.js @@ -75,6 +75,19 @@ .with.keys(Object.keys(getJsonSpritesheet().frames)) .and.has.property('0.png') .that.is.an.instanceof(Texture); + + expect(res.textures['0.png'].frame.x).to.equal(14); + expect(res.textures['0.png'].frame.y).to.equal(28); + expect(res.textures['0.png'].defaultAnchor.x).to.equal(0.3); + expect(res.textures['0.png'].defaultAnchor.y).to.equal(0.4); + expect(res.textures['1.png'].defaultAnchor.x).to.equal(0.0); // default of defaultAnchor is 0,0 + expect(res.textures['1.png'].defaultAnchor.y).to.equal(0.0); + + expect(res).to.have.property('spritesheet') + .to.have.property('animations') + .to.have.property('png123'); + expect(res.spritesheet.animations.png123.length).to.equal(3); + expect(res.spritesheet.animations.png123[0]).to.equal(res.textures['1.png']); }); it('should not load binary images as an image loader type', function (done) @@ -188,7 +201,8 @@ "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} + "sourceSize": {"w":14,"h":14}, + "anchor": {"x":0.3,"y":0.4} }, "1.png": { @@ -262,6 +276,9 @@ "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, "sourceSize": {"w":14,"h":14} }}, + "animations": { + "png123": [ "1.png", "2.png", "3.png" ] + }, "meta": { "app": "http://www.texturepacker.com", "version": "1.0", diff --git a/packages/spritesheet/test/resources/building1.json b/packages/spritesheet/test/resources/building1.json old mode 100755 new mode 100644 index 03fa5c1..d8fbfe1 --- a/packages/spritesheet/test/resources/building1.json +++ b/packages/spritesheet/test/resources/building1.json @@ -6,16 +6,54 @@ "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":95,"h":115}, - "sourceSize": {"w":95,"h":115}, - "pivot": {"x":0.5,"y":0.5} + "sourceSize": {"w":95,"h":115} +}, +"star1.png": +{ + "frame": {"x":98,"y":33,"w":28,"h":29}, + "rotated": true, + "trimmed": true, + "spriteSourceSize": {"x":2,"y":2,"w":28,"h":29}, + "sourceSize": {"w":32,"h":32}, + "anchor": {"x":0.5,"y":0.5} +}, +"star2.png": +{ + "frame": {"x":98,"y":95,"w":26,"h":30}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":3,"y":1,"w":26,"h":30}, + "sourceSize": {"w":32,"h":32}, + "anchor": {"x":0.5,"y":0.5} +}, +"star3.png": +{ + "frame": {"x":98,"y":1,"w":30,"h":29}, + "rotated": true, + "trimmed": true, + "spriteSourceSize": {"x":1,"y":2,"w":30,"h":29}, + "sourceSize": {"w":32,"h":32}, + "anchor": {"x":0.5,"y":0.5} +}, +"star4.png": +{ + "frame": {"x":98,"y":63,"w":30,"h":27}, + "rotated": true, + "trimmed": true, + "spriteSourceSize": {"x":1,"y":3,"w":30,"h":27}, + "sourceSize": {"w":32,"h":32}, + "anchor": {"x":0.5,"y":0.5} }}, +"animations": { + "star": ["star1.png","star2.png","star3.png","star4.png"] +}, "meta": { - "app": "http://www.codeandweb.com/texturepacker", + "app": "https://www.codeandweb.com/texturepacker", "version": "1.0", "image": "building1.png", "format": "RGBA8888", - "size": {"w":128,"h":128}, + "size": {"w":128,"h":126}, "scale": "0.5", - "smartupdate": "$TexturePacker:SmartUpdate:d7a5e54c8f8a3fecd508baf190c44807:99a0e3a4dc0f441e4aad77614191ab38:6046b8eb706ddefaa771c33ceb7cb6d5$" + "smartupdate": "$TexturePacker:SmartUpdate:1cb0f14cbdd5e7bcecd332ecd0eaa9f7:8acde9d234ecca966a410602c71bffad:6046b8eb706ddefaa771c33ceb7cb6d5$" } } diff --git a/packages/spritesheet/test/resources/building1.png b/packages/spritesheet/test/resources/building1.png old mode 100755 new mode 100644 index 7e1e114..7e16cda --- a/packages/spritesheet/test/resources/building1.png +++ b/packages/spritesheet/test/resources/building1.png Binary files differ diff --git a/packages/spritesheet/test/resources/building1@2x.json b/packages/spritesheet/test/resources/building1@2x.json old mode 100755 new mode 100644 index 24e25ff..c8cb7bf --- a/packages/spritesheet/test/resources/building1@2x.json +++ b/packages/spritesheet/test/resources/building1@2x.json @@ -6,16 +6,54 @@ "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":190,"h":229}, - "sourceSize": {"w":190,"h":229}, - "pivot": {"x":0.5,"y":0.5} + "sourceSize": {"w":190,"h":229} +}, +"star1.png": +{ + "frame": {"x":193,"y":59,"w":54,"h":55}, + "rotated": true, + "trimmed": true, + "spriteSourceSize": {"x":5,"y":5,"w":54,"h":55}, + "sourceSize": {"w":64,"h":64}, + "anchor": {"x":0.5,"y":0.5} +}, +"star2.png": +{ + "frame": {"x":193,"y":115,"w":50,"h":57}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":7,"y":4,"w":50,"h":57}, + "sourceSize": {"w":64,"h":64}, + "anchor": {"x":0.5,"y":0.5} +}, +"star3.png": +{ + "frame": {"x":193,"y":1,"w":56,"h":55}, + "rotated": true, + "trimmed": true, + "spriteSourceSize": {"x":4,"y":5,"w":56,"h":55}, + "sourceSize": {"w":64,"h":64}, + "anchor": {"x":0.5,"y":0.5} +}, +"star4.png": +{ + "frame": {"x":193,"y":174,"w":56,"h":50}, + "rotated": true, + "trimmed": true, + "spriteSourceSize": {"x":4,"y":8,"w":56,"h":50}, + "sourceSize": {"w":64,"h":64}, + "anchor": {"x":0.5,"y":0.5} }}, +"animations": { + "star": ["star1.png","star2.png","star3.png","star4.png"] +}, "meta": { - "app": "http://www.codeandweb.com/texturepacker", + "app": "https://www.codeandweb.com/texturepacker", "version": "1.0", "image": "building1@2x.png", "format": "RGBA8888", - "size": {"w":256,"h":256}, + "size": {"w":249,"h":231}, "scale": "1", - "smartupdate": "$TexturePacker:SmartUpdate:d7a5e54c8f8a3fecd508baf190c44807:99a0e3a4dc0f441e4aad77614191ab38:6046b8eb706ddefaa771c33ceb7cb6d5$" + "smartupdate": "$TexturePacker:SmartUpdate:1cb0f14cbdd5e7bcecd332ecd0eaa9f7:8acde9d234ecca966a410602c71bffad:6046b8eb706ddefaa771c33ceb7cb6d5$" } } diff --git a/packages/core/src/textures/Texture.js b/packages/core/src/textures/Texture.js index 9148656..f9bb8bc 100644 --- a/packages/core/src/textures/Texture.js +++ b/packages/core/src/textures/Texture.js @@ -4,7 +4,7 @@ import TextureUvs from './TextureUvs'; import EventEmitter from 'eventemitter3'; import { settings } from '@pixi/settings'; -import { Rectangle } from '@pixi/math'; +import { Rectangle, Point } from '@pixi/math'; import { uid, TextureCache, getResolutionOfUrl } from '@pixi/utils'; /** @@ -41,8 +41,9 @@ * @param {PIXI.Rectangle} [orig] - The area of original texture * @param {PIXI.Rectangle} [trim] - Trimmed rectangle of original texture * @param {number} [rotate] - indicates how the texture was rotated by texture packer. See {@link PIXI.GroupD8} + * @param {PIXI.Point} [anchor] - Default anchor point used for sprite placement / rotation */ - constructor(baseTexture, frame, orig, trim, rotate) + constructor(baseTexture, frame, orig, trim, rotate, anchor) { super(); @@ -153,6 +154,14 @@ } /** + * Anchor point that is used as default if sprite is created with this texture. + * Changing the `defaultAnchor` at a later point of time will not update Sprite's anchor point. + * @member {PIXI.Point} + * @default {0,0} + */ + this.defaultAnchor = anchor ? new Point(anchor.x, anchor.y) : new Point(0, 0); + + /** * Update ID is observed by sprites and TextureMatrix instances. * Call updateUvs() to increment it. * diff --git a/packages/sprite-animated/src/AnimatedSprite.js b/packages/sprite-animated/src/AnimatedSprite.js index d5f5fce..330c683 100644 --- a/packages/sprite-animated/src/AnimatedSprite.js +++ b/packages/sprite-animated/src/AnimatedSprite.js @@ -22,7 +22,20 @@ * textureArray.push(texture); * }; * - * let mc = new PIXI.AnimatedSprite(textureArray); + * let animatedSprite = new PIXI.extras.AnimatedSprite(textureArray); + * ``` + * + * The more efficient and simpler way to create an animated sprite is using a {@link PIXI.Spritesheet} + * containing the animation definitions: + * + * ```js + * PIXI.loader.add("assets/spritesheet.json").load(setup); + * + * function setup() { + * let sheet = PIXI.loader.resources["assets/spritesheet.json"].spritesheet; + * animatedSprite = new PIXI.extras.AnimatedSprite(sheet.animations["image_sequence"]); + * ... + * } * ``` * * @class diff --git a/packages/sprite/src/Sprite.js b/packages/sprite/src/Sprite.js index ebf1f6f..91546cf 100644 --- a/packages/sprite/src/Sprite.js +++ b/packages/sprite/src/Sprite.js @@ -15,6 +15,18 @@ * let sprite = new PIXI.Sprite.from('assets/image.png'); * ``` * + * The more efficient way to create sprites is using a {@link PIXI.Spritesheet}: + * + * ```js + * PIXI.loader.add("assets/spritesheet.json").load(setup); + * + * function setup() { + * let sheet = PIXI.loader.resources["assets/spritesheet.json"].spritesheet; + * let sprite = new PIXI.Sprite(sheet.textures["image.png"]); + * ... + * } + * ``` + * * @class * @extends PIXI.Container * @memberof PIXI @@ -30,14 +42,22 @@ /** * The anchor sets the origin point of the texture. - * The default is 0,0 this means the texture's origin is the top left. + * The default is 0,0 or taken from the {@link PIXI.Texture#defaultAnchor|Texture} + * passed to the constructor. A value of 0,0 means the texture's origin is the top left. * Setting the anchor to 0.5,0.5 means the texture's origin is centered. * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner. + * Note: Updating the {@link PIXI.Texture#defaultAnchor} after a Texture is + * created does _not_ update the Sprite's anchor values. * * @member {PIXI.ObservablePoint} * @private */ - this._anchor = new ObservablePoint(this._onAnchorUpdate, this); + this._anchor = new ObservablePoint( + this._onAnchorUpdate, + this, + (texture ? texture.defaultAnchor.x : 0), + (texture ? texture.defaultAnchor.y : 0) + ); /** * The texture that the sprite is using @@ -484,7 +504,8 @@ } /** - * The anchor sets the origin point of the text. + * The anchor sets the origin point of the text. The default value is taken from the {@link PIXI.Texture|Texture} + * and passed to the constructor. * * The default is `(0,0)`, this means the text's origin is the top left. * diff --git a/packages/spritesheet/src/Spritesheet.js b/packages/spritesheet/src/Spritesheet.js index 64d932d..28597d7 100644 --- a/packages/spritesheet/src/Spritesheet.js +++ b/packages/spritesheet/src/Spritesheet.js @@ -6,6 +6,23 @@ * Utility class for maintaining reference to a collection * of Textures on a single Spritesheet. * + * To access a sprite sheet from your code pass its JSON data file to Pixi's loader: + * + * ```js + * PIXI.loader.add("images/spritesheet.json").load(setup); + * + * function setup() { + * let sheet = PIXI.loader.resources["images/spritesheet.json"].spritesheet; + * ... + * } + * ``` + * With the `sheet.textures` you can create Sprite objects,`sheet.animations` can be used to create an AnimatedSprite. + * + * Sprite sheets can be packed using tools like {@link https://codeandweb.com/texturepacker|TexturePacker}, + * {@link https://renderhjs.net/shoebox/|Shoebox} or {@link https://github.com/krzysztof-o/spritesheet.js|Spritesheet.js}. + * Default anchor points (see {@link PIXI.Texture#defaultAnchor}) and grouping of animation sprites are currently only + * supported by TexturePacker. + * * @class * @memberof PIXI */ @@ -38,12 +55,26 @@ this.baseTexture = baseTexture; /** - * Map of spritesheet textures. - * @type {Object} + * A map containing all textures of the sprite sheet. + * Can be used to create a {@link PIXI.Sprite|Sprite}: + * ```js + * new PIXI.Sprite(sheet.textures["image.png"]); + * ``` + * @member {Object} */ this.textures = {}; /** + * A map containing the textures for each animation. + * Can be used to create an {@link PIXI.extras.AnimatedSprite|AnimatedSprite}: + * ```js + * new PIXI.extras.AnimatedSprite(sheet.animations["anim_name"]) + * ``` + * @member {Object} + */ + this.animations = {}; + + /** * Reference to the original JSON data. * @type {Object} */ @@ -134,6 +165,7 @@ if (this._frameKeys.length <= Spritesheet.BATCH_SIZE) { this._processFrames(0); + this._processAnimations(); this._parseComplete(); } else @@ -208,7 +240,8 @@ frame, orig, trim, - data.rotated ? 2 : 0 + data.rotated ? 2 : 0, + data.anchor ); // lets also add the frame to pixi's global cache for fromFrame and fromImage functions @@ -220,6 +253,27 @@ } /** + * Parse animations config + * + * @private + */ + _processAnimations() + { + const animations = this.data.animations || {}; + + for (const animName in animations) + { + this.animations[animName] = []; + for (let i = 0; i < animations[animName].length; i++) + { + const frameName = animations[animName][i]; + + this.animations[animName].push(this.textures[frameName]); + } + } + } + + /** * The parse has completed. * * @private @@ -250,6 +304,7 @@ } else { + this._processAnimations(); this._parseComplete(); } }, 0); diff --git a/packages/spritesheet/test/Spritesheet.js b/packages/spritesheet/test/Spritesheet.js index b168fe0..5063a45 100644 --- a/packages/spritesheet/test/Spritesheet.js +++ b/packages/spritesheet/test/Spritesheet.js @@ -15,12 +15,20 @@ const width = Math.floor(spritesheet.data.frames[id].frame.w); const height = Math.floor(spritesheet.data.frames[id].frame.h); - expect(Object.keys(textures).length).to.equal(1); - expect(Object.keys(spritesheet.textures).length).to.equal(1); + expect(Object.keys(textures).length).to.equal(5); + expect(Object.keys(spritesheet.textures).length).to.equal(5); expect(textures[id]).to.be.an.instanceof(Texture); expect(textures[id].width).to.equal(width / spritesheet.resolution); expect(textures[id].height).to.equal(height / spritesheet.resolution); + expect(textures[id].defaultAnchor.x).to.equal(0); + expect(textures[id].defaultAnchor.y).to.equal(0); expect(textures[id].textureCacheIds.indexOf(id)).to.equal(0); + + expect(this.animations).to.have.property('star').that.is.an('array'); + expect(this.animations.star.length).to.equal(4); + expect(this.animations.star[0].defaultAnchor.x).to.equal(0.5); + expect(this.animations.star[0].defaultAnchor.y).to.equal(0.5); + spritesheet.destroy(true); expect(spritesheet.textures).to.be.null; expect(spritesheet.baseTexture).to.be.null; diff --git a/packages/spritesheet/test/SpritesheetLoader.js b/packages/spritesheet/test/SpritesheetLoader.js index 2797100..932064c 100644 --- a/packages/spritesheet/test/SpritesheetLoader.js +++ b/packages/spritesheet/test/SpritesheetLoader.js @@ -75,6 +75,19 @@ .with.keys(Object.keys(getJsonSpritesheet().frames)) .and.has.property('0.png') .that.is.an.instanceof(Texture); + + expect(res.textures['0.png'].frame.x).to.equal(14); + expect(res.textures['0.png'].frame.y).to.equal(28); + expect(res.textures['0.png'].defaultAnchor.x).to.equal(0.3); + expect(res.textures['0.png'].defaultAnchor.y).to.equal(0.4); + expect(res.textures['1.png'].defaultAnchor.x).to.equal(0.0); // default of defaultAnchor is 0,0 + expect(res.textures['1.png'].defaultAnchor.y).to.equal(0.0); + + expect(res).to.have.property('spritesheet') + .to.have.property('animations') + .to.have.property('png123'); + expect(res.spritesheet.animations.png123.length).to.equal(3); + expect(res.spritesheet.animations.png123[0]).to.equal(res.textures['1.png']); }); it('should not load binary images as an image loader type', function (done) @@ -188,7 +201,8 @@ "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} + "sourceSize": {"w":14,"h":14}, + "anchor": {"x":0.3,"y":0.4} }, "1.png": { @@ -262,6 +276,9 @@ "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, "sourceSize": {"w":14,"h":14} }}, + "animations": { + "png123": [ "1.png", "2.png", "3.png" ] + }, "meta": { "app": "http://www.texturepacker.com", "version": "1.0", diff --git a/packages/spritesheet/test/resources/building1.json b/packages/spritesheet/test/resources/building1.json old mode 100755 new mode 100644 index 03fa5c1..d8fbfe1 --- a/packages/spritesheet/test/resources/building1.json +++ b/packages/spritesheet/test/resources/building1.json @@ -6,16 +6,54 @@ "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":95,"h":115}, - "sourceSize": {"w":95,"h":115}, - "pivot": {"x":0.5,"y":0.5} + "sourceSize": {"w":95,"h":115} +}, +"star1.png": +{ + "frame": {"x":98,"y":33,"w":28,"h":29}, + "rotated": true, + "trimmed": true, + "spriteSourceSize": {"x":2,"y":2,"w":28,"h":29}, + "sourceSize": {"w":32,"h":32}, + "anchor": {"x":0.5,"y":0.5} +}, +"star2.png": +{ + "frame": {"x":98,"y":95,"w":26,"h":30}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":3,"y":1,"w":26,"h":30}, + "sourceSize": {"w":32,"h":32}, + "anchor": {"x":0.5,"y":0.5} +}, +"star3.png": +{ + "frame": {"x":98,"y":1,"w":30,"h":29}, + "rotated": true, + "trimmed": true, + "spriteSourceSize": {"x":1,"y":2,"w":30,"h":29}, + "sourceSize": {"w":32,"h":32}, + "anchor": {"x":0.5,"y":0.5} +}, +"star4.png": +{ + "frame": {"x":98,"y":63,"w":30,"h":27}, + "rotated": true, + "trimmed": true, + "spriteSourceSize": {"x":1,"y":3,"w":30,"h":27}, + "sourceSize": {"w":32,"h":32}, + "anchor": {"x":0.5,"y":0.5} }}, +"animations": { + "star": ["star1.png","star2.png","star3.png","star4.png"] +}, "meta": { - "app": "http://www.codeandweb.com/texturepacker", + "app": "https://www.codeandweb.com/texturepacker", "version": "1.0", "image": "building1.png", "format": "RGBA8888", - "size": {"w":128,"h":128}, + "size": {"w":128,"h":126}, "scale": "0.5", - "smartupdate": "$TexturePacker:SmartUpdate:d7a5e54c8f8a3fecd508baf190c44807:99a0e3a4dc0f441e4aad77614191ab38:6046b8eb706ddefaa771c33ceb7cb6d5$" + "smartupdate": "$TexturePacker:SmartUpdate:1cb0f14cbdd5e7bcecd332ecd0eaa9f7:8acde9d234ecca966a410602c71bffad:6046b8eb706ddefaa771c33ceb7cb6d5$" } } diff --git a/packages/spritesheet/test/resources/building1.png b/packages/spritesheet/test/resources/building1.png old mode 100755 new mode 100644 index 7e1e114..7e16cda --- a/packages/spritesheet/test/resources/building1.png +++ b/packages/spritesheet/test/resources/building1.png Binary files differ diff --git a/packages/spritesheet/test/resources/building1@2x.json b/packages/spritesheet/test/resources/building1@2x.json old mode 100755 new mode 100644 index 24e25ff..c8cb7bf --- a/packages/spritesheet/test/resources/building1@2x.json +++ b/packages/spritesheet/test/resources/building1@2x.json @@ -6,16 +6,54 @@ "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":190,"h":229}, - "sourceSize": {"w":190,"h":229}, - "pivot": {"x":0.5,"y":0.5} + "sourceSize": {"w":190,"h":229} +}, +"star1.png": +{ + "frame": {"x":193,"y":59,"w":54,"h":55}, + "rotated": true, + "trimmed": true, + "spriteSourceSize": {"x":5,"y":5,"w":54,"h":55}, + "sourceSize": {"w":64,"h":64}, + "anchor": {"x":0.5,"y":0.5} +}, +"star2.png": +{ + "frame": {"x":193,"y":115,"w":50,"h":57}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":7,"y":4,"w":50,"h":57}, + "sourceSize": {"w":64,"h":64}, + "anchor": {"x":0.5,"y":0.5} +}, +"star3.png": +{ + "frame": {"x":193,"y":1,"w":56,"h":55}, + "rotated": true, + "trimmed": true, + "spriteSourceSize": {"x":4,"y":5,"w":56,"h":55}, + "sourceSize": {"w":64,"h":64}, + "anchor": {"x":0.5,"y":0.5} +}, +"star4.png": +{ + "frame": {"x":193,"y":174,"w":56,"h":50}, + "rotated": true, + "trimmed": true, + "spriteSourceSize": {"x":4,"y":8,"w":56,"h":50}, + "sourceSize": {"w":64,"h":64}, + "anchor": {"x":0.5,"y":0.5} }}, +"animations": { + "star": ["star1.png","star2.png","star3.png","star4.png"] +}, "meta": { - "app": "http://www.codeandweb.com/texturepacker", + "app": "https://www.codeandweb.com/texturepacker", "version": "1.0", "image": "building1@2x.png", "format": "RGBA8888", - "size": {"w":256,"h":256}, + "size": {"w":249,"h":231}, "scale": "1", - "smartupdate": "$TexturePacker:SmartUpdate:d7a5e54c8f8a3fecd508baf190c44807:99a0e3a4dc0f441e4aad77614191ab38:6046b8eb706ddefaa771c33ceb7cb6d5$" + "smartupdate": "$TexturePacker:SmartUpdate:1cb0f14cbdd5e7bcecd332ecd0eaa9f7:8acde9d234ecca966a410602c71bffad:6046b8eb706ddefaa771c33ceb7cb6d5$" } } diff --git a/packages/spritesheet/test/resources/building1@2x.png b/packages/spritesheet/test/resources/building1@2x.png old mode 100755 new mode 100644 index d5ecd04..06e613f --- a/packages/spritesheet/test/resources/building1@2x.png +++ b/packages/spritesheet/test/resources/building1@2x.png Binary files differ diff --git a/packages/core/src/textures/Texture.js b/packages/core/src/textures/Texture.js index 9148656..f9bb8bc 100644 --- a/packages/core/src/textures/Texture.js +++ b/packages/core/src/textures/Texture.js @@ -4,7 +4,7 @@ import TextureUvs from './TextureUvs'; import EventEmitter from 'eventemitter3'; import { settings } from '@pixi/settings'; -import { Rectangle } from '@pixi/math'; +import { Rectangle, Point } from '@pixi/math'; import { uid, TextureCache, getResolutionOfUrl } from '@pixi/utils'; /** @@ -41,8 +41,9 @@ * @param {PIXI.Rectangle} [orig] - The area of original texture * @param {PIXI.Rectangle} [trim] - Trimmed rectangle of original texture * @param {number} [rotate] - indicates how the texture was rotated by texture packer. See {@link PIXI.GroupD8} + * @param {PIXI.Point} [anchor] - Default anchor point used for sprite placement / rotation */ - constructor(baseTexture, frame, orig, trim, rotate) + constructor(baseTexture, frame, orig, trim, rotate, anchor) { super(); @@ -153,6 +154,14 @@ } /** + * Anchor point that is used as default if sprite is created with this texture. + * Changing the `defaultAnchor` at a later point of time will not update Sprite's anchor point. + * @member {PIXI.Point} + * @default {0,0} + */ + this.defaultAnchor = anchor ? new Point(anchor.x, anchor.y) : new Point(0, 0); + + /** * Update ID is observed by sprites and TextureMatrix instances. * Call updateUvs() to increment it. * diff --git a/packages/sprite-animated/src/AnimatedSprite.js b/packages/sprite-animated/src/AnimatedSprite.js index d5f5fce..330c683 100644 --- a/packages/sprite-animated/src/AnimatedSprite.js +++ b/packages/sprite-animated/src/AnimatedSprite.js @@ -22,7 +22,20 @@ * textureArray.push(texture); * }; * - * let mc = new PIXI.AnimatedSprite(textureArray); + * let animatedSprite = new PIXI.extras.AnimatedSprite(textureArray); + * ``` + * + * The more efficient and simpler way to create an animated sprite is using a {@link PIXI.Spritesheet} + * containing the animation definitions: + * + * ```js + * PIXI.loader.add("assets/spritesheet.json").load(setup); + * + * function setup() { + * let sheet = PIXI.loader.resources["assets/spritesheet.json"].spritesheet; + * animatedSprite = new PIXI.extras.AnimatedSprite(sheet.animations["image_sequence"]); + * ... + * } * ``` * * @class diff --git a/packages/sprite/src/Sprite.js b/packages/sprite/src/Sprite.js index ebf1f6f..91546cf 100644 --- a/packages/sprite/src/Sprite.js +++ b/packages/sprite/src/Sprite.js @@ -15,6 +15,18 @@ * let sprite = new PIXI.Sprite.from('assets/image.png'); * ``` * + * The more efficient way to create sprites is using a {@link PIXI.Spritesheet}: + * + * ```js + * PIXI.loader.add("assets/spritesheet.json").load(setup); + * + * function setup() { + * let sheet = PIXI.loader.resources["assets/spritesheet.json"].spritesheet; + * let sprite = new PIXI.Sprite(sheet.textures["image.png"]); + * ... + * } + * ``` + * * @class * @extends PIXI.Container * @memberof PIXI @@ -30,14 +42,22 @@ /** * The anchor sets the origin point of the texture. - * The default is 0,0 this means the texture's origin is the top left. + * The default is 0,0 or taken from the {@link PIXI.Texture#defaultAnchor|Texture} + * passed to the constructor. A value of 0,0 means the texture's origin is the top left. * Setting the anchor to 0.5,0.5 means the texture's origin is centered. * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner. + * Note: Updating the {@link PIXI.Texture#defaultAnchor} after a Texture is + * created does _not_ update the Sprite's anchor values. * * @member {PIXI.ObservablePoint} * @private */ - this._anchor = new ObservablePoint(this._onAnchorUpdate, this); + this._anchor = new ObservablePoint( + this._onAnchorUpdate, + this, + (texture ? texture.defaultAnchor.x : 0), + (texture ? texture.defaultAnchor.y : 0) + ); /** * The texture that the sprite is using @@ -484,7 +504,8 @@ } /** - * The anchor sets the origin point of the text. + * The anchor sets the origin point of the text. The default value is taken from the {@link PIXI.Texture|Texture} + * and passed to the constructor. * * The default is `(0,0)`, this means the text's origin is the top left. * diff --git a/packages/spritesheet/src/Spritesheet.js b/packages/spritesheet/src/Spritesheet.js index 64d932d..28597d7 100644 --- a/packages/spritesheet/src/Spritesheet.js +++ b/packages/spritesheet/src/Spritesheet.js @@ -6,6 +6,23 @@ * Utility class for maintaining reference to a collection * of Textures on a single Spritesheet. * + * To access a sprite sheet from your code pass its JSON data file to Pixi's loader: + * + * ```js + * PIXI.loader.add("images/spritesheet.json").load(setup); + * + * function setup() { + * let sheet = PIXI.loader.resources["images/spritesheet.json"].spritesheet; + * ... + * } + * ``` + * With the `sheet.textures` you can create Sprite objects,`sheet.animations` can be used to create an AnimatedSprite. + * + * Sprite sheets can be packed using tools like {@link https://codeandweb.com/texturepacker|TexturePacker}, + * {@link https://renderhjs.net/shoebox/|Shoebox} or {@link https://github.com/krzysztof-o/spritesheet.js|Spritesheet.js}. + * Default anchor points (see {@link PIXI.Texture#defaultAnchor}) and grouping of animation sprites are currently only + * supported by TexturePacker. + * * @class * @memberof PIXI */ @@ -38,12 +55,26 @@ this.baseTexture = baseTexture; /** - * Map of spritesheet textures. - * @type {Object} + * A map containing all textures of the sprite sheet. + * Can be used to create a {@link PIXI.Sprite|Sprite}: + * ```js + * new PIXI.Sprite(sheet.textures["image.png"]); + * ``` + * @member {Object} */ this.textures = {}; /** + * A map containing the textures for each animation. + * Can be used to create an {@link PIXI.extras.AnimatedSprite|AnimatedSprite}: + * ```js + * new PIXI.extras.AnimatedSprite(sheet.animations["anim_name"]) + * ``` + * @member {Object} + */ + this.animations = {}; + + /** * Reference to the original JSON data. * @type {Object} */ @@ -134,6 +165,7 @@ if (this._frameKeys.length <= Spritesheet.BATCH_SIZE) { this._processFrames(0); + this._processAnimations(); this._parseComplete(); } else @@ -208,7 +240,8 @@ frame, orig, trim, - data.rotated ? 2 : 0 + data.rotated ? 2 : 0, + data.anchor ); // lets also add the frame to pixi's global cache for fromFrame and fromImage functions @@ -220,6 +253,27 @@ } /** + * Parse animations config + * + * @private + */ + _processAnimations() + { + const animations = this.data.animations || {}; + + for (const animName in animations) + { + this.animations[animName] = []; + for (let i = 0; i < animations[animName].length; i++) + { + const frameName = animations[animName][i]; + + this.animations[animName].push(this.textures[frameName]); + } + } + } + + /** * The parse has completed. * * @private @@ -250,6 +304,7 @@ } else { + this._processAnimations(); this._parseComplete(); } }, 0); diff --git a/packages/spritesheet/test/Spritesheet.js b/packages/spritesheet/test/Spritesheet.js index b168fe0..5063a45 100644 --- a/packages/spritesheet/test/Spritesheet.js +++ b/packages/spritesheet/test/Spritesheet.js @@ -15,12 +15,20 @@ const width = Math.floor(spritesheet.data.frames[id].frame.w); const height = Math.floor(spritesheet.data.frames[id].frame.h); - expect(Object.keys(textures).length).to.equal(1); - expect(Object.keys(spritesheet.textures).length).to.equal(1); + expect(Object.keys(textures).length).to.equal(5); + expect(Object.keys(spritesheet.textures).length).to.equal(5); expect(textures[id]).to.be.an.instanceof(Texture); expect(textures[id].width).to.equal(width / spritesheet.resolution); expect(textures[id].height).to.equal(height / spritesheet.resolution); + expect(textures[id].defaultAnchor.x).to.equal(0); + expect(textures[id].defaultAnchor.y).to.equal(0); expect(textures[id].textureCacheIds.indexOf(id)).to.equal(0); + + expect(this.animations).to.have.property('star').that.is.an('array'); + expect(this.animations.star.length).to.equal(4); + expect(this.animations.star[0].defaultAnchor.x).to.equal(0.5); + expect(this.animations.star[0].defaultAnchor.y).to.equal(0.5); + spritesheet.destroy(true); expect(spritesheet.textures).to.be.null; expect(spritesheet.baseTexture).to.be.null; diff --git a/packages/spritesheet/test/SpritesheetLoader.js b/packages/spritesheet/test/SpritesheetLoader.js index 2797100..932064c 100644 --- a/packages/spritesheet/test/SpritesheetLoader.js +++ b/packages/spritesheet/test/SpritesheetLoader.js @@ -75,6 +75,19 @@ .with.keys(Object.keys(getJsonSpritesheet().frames)) .and.has.property('0.png') .that.is.an.instanceof(Texture); + + expect(res.textures['0.png'].frame.x).to.equal(14); + expect(res.textures['0.png'].frame.y).to.equal(28); + expect(res.textures['0.png'].defaultAnchor.x).to.equal(0.3); + expect(res.textures['0.png'].defaultAnchor.y).to.equal(0.4); + expect(res.textures['1.png'].defaultAnchor.x).to.equal(0.0); // default of defaultAnchor is 0,0 + expect(res.textures['1.png'].defaultAnchor.y).to.equal(0.0); + + expect(res).to.have.property('spritesheet') + .to.have.property('animations') + .to.have.property('png123'); + expect(res.spritesheet.animations.png123.length).to.equal(3); + expect(res.spritesheet.animations.png123[0]).to.equal(res.textures['1.png']); }); it('should not load binary images as an image loader type', function (done) @@ -188,7 +201,8 @@ "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} + "sourceSize": {"w":14,"h":14}, + "anchor": {"x":0.3,"y":0.4} }, "1.png": { @@ -262,6 +276,9 @@ "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, "sourceSize": {"w":14,"h":14} }}, + "animations": { + "png123": [ "1.png", "2.png", "3.png" ] + }, "meta": { "app": "http://www.texturepacker.com", "version": "1.0", diff --git a/packages/spritesheet/test/resources/building1.json b/packages/spritesheet/test/resources/building1.json old mode 100755 new mode 100644 index 03fa5c1..d8fbfe1 --- a/packages/spritesheet/test/resources/building1.json +++ b/packages/spritesheet/test/resources/building1.json @@ -6,16 +6,54 @@ "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":95,"h":115}, - "sourceSize": {"w":95,"h":115}, - "pivot": {"x":0.5,"y":0.5} + "sourceSize": {"w":95,"h":115} +}, +"star1.png": +{ + "frame": {"x":98,"y":33,"w":28,"h":29}, + "rotated": true, + "trimmed": true, + "spriteSourceSize": {"x":2,"y":2,"w":28,"h":29}, + "sourceSize": {"w":32,"h":32}, + "anchor": {"x":0.5,"y":0.5} +}, +"star2.png": +{ + "frame": {"x":98,"y":95,"w":26,"h":30}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":3,"y":1,"w":26,"h":30}, + "sourceSize": {"w":32,"h":32}, + "anchor": {"x":0.5,"y":0.5} +}, +"star3.png": +{ + "frame": {"x":98,"y":1,"w":30,"h":29}, + "rotated": true, + "trimmed": true, + "spriteSourceSize": {"x":1,"y":2,"w":30,"h":29}, + "sourceSize": {"w":32,"h":32}, + "anchor": {"x":0.5,"y":0.5} +}, +"star4.png": +{ + "frame": {"x":98,"y":63,"w":30,"h":27}, + "rotated": true, + "trimmed": true, + "spriteSourceSize": {"x":1,"y":3,"w":30,"h":27}, + "sourceSize": {"w":32,"h":32}, + "anchor": {"x":0.5,"y":0.5} }}, +"animations": { + "star": ["star1.png","star2.png","star3.png","star4.png"] +}, "meta": { - "app": "http://www.codeandweb.com/texturepacker", + "app": "https://www.codeandweb.com/texturepacker", "version": "1.0", "image": "building1.png", "format": "RGBA8888", - "size": {"w":128,"h":128}, + "size": {"w":128,"h":126}, "scale": "0.5", - "smartupdate": "$TexturePacker:SmartUpdate:d7a5e54c8f8a3fecd508baf190c44807:99a0e3a4dc0f441e4aad77614191ab38:6046b8eb706ddefaa771c33ceb7cb6d5$" + "smartupdate": "$TexturePacker:SmartUpdate:1cb0f14cbdd5e7bcecd332ecd0eaa9f7:8acde9d234ecca966a410602c71bffad:6046b8eb706ddefaa771c33ceb7cb6d5$" } } diff --git a/packages/spritesheet/test/resources/building1.png b/packages/spritesheet/test/resources/building1.png old mode 100755 new mode 100644 index 7e1e114..7e16cda --- a/packages/spritesheet/test/resources/building1.png +++ b/packages/spritesheet/test/resources/building1.png Binary files differ diff --git a/packages/spritesheet/test/resources/building1@2x.json b/packages/spritesheet/test/resources/building1@2x.json old mode 100755 new mode 100644 index 24e25ff..c8cb7bf --- a/packages/spritesheet/test/resources/building1@2x.json +++ b/packages/spritesheet/test/resources/building1@2x.json @@ -6,16 +6,54 @@ "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":190,"h":229}, - "sourceSize": {"w":190,"h":229}, - "pivot": {"x":0.5,"y":0.5} + "sourceSize": {"w":190,"h":229} +}, +"star1.png": +{ + "frame": {"x":193,"y":59,"w":54,"h":55}, + "rotated": true, + "trimmed": true, + "spriteSourceSize": {"x":5,"y":5,"w":54,"h":55}, + "sourceSize": {"w":64,"h":64}, + "anchor": {"x":0.5,"y":0.5} +}, +"star2.png": +{ + "frame": {"x":193,"y":115,"w":50,"h":57}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":7,"y":4,"w":50,"h":57}, + "sourceSize": {"w":64,"h":64}, + "anchor": {"x":0.5,"y":0.5} +}, +"star3.png": +{ + "frame": {"x":193,"y":1,"w":56,"h":55}, + "rotated": true, + "trimmed": true, + "spriteSourceSize": {"x":4,"y":5,"w":56,"h":55}, + "sourceSize": {"w":64,"h":64}, + "anchor": {"x":0.5,"y":0.5} +}, +"star4.png": +{ + "frame": {"x":193,"y":174,"w":56,"h":50}, + "rotated": true, + "trimmed": true, + "spriteSourceSize": {"x":4,"y":8,"w":56,"h":50}, + "sourceSize": {"w":64,"h":64}, + "anchor": {"x":0.5,"y":0.5} }}, +"animations": { + "star": ["star1.png","star2.png","star3.png","star4.png"] +}, "meta": { - "app": "http://www.codeandweb.com/texturepacker", + "app": "https://www.codeandweb.com/texturepacker", "version": "1.0", "image": "building1@2x.png", "format": "RGBA8888", - "size": {"w":256,"h":256}, + "size": {"w":249,"h":231}, "scale": "1", - "smartupdate": "$TexturePacker:SmartUpdate:d7a5e54c8f8a3fecd508baf190c44807:99a0e3a4dc0f441e4aad77614191ab38:6046b8eb706ddefaa771c33ceb7cb6d5$" + "smartupdate": "$TexturePacker:SmartUpdate:1cb0f14cbdd5e7bcecd332ecd0eaa9f7:8acde9d234ecca966a410602c71bffad:6046b8eb706ddefaa771c33ceb7cb6d5$" } } diff --git a/packages/spritesheet/test/resources/building1@2x.png b/packages/spritesheet/test/resources/building1@2x.png old mode 100755 new mode 100644 index d5ecd04..06e613f --- a/packages/spritesheet/test/resources/building1@2x.png +++ b/packages/spritesheet/test/resources/building1@2x.png Binary files differ diff --git a/test/core/resources/src/goldmine_10_5.png b/test/core/resources/src/goldmine_10_5.png new file mode 100755 index 0000000..de2ba5a --- /dev/null +++ b/test/core/resources/src/goldmine_10_5.png Binary files differ diff --git a/packages/core/src/textures/Texture.js b/packages/core/src/textures/Texture.js index 9148656..f9bb8bc 100644 --- a/packages/core/src/textures/Texture.js +++ b/packages/core/src/textures/Texture.js @@ -4,7 +4,7 @@ import TextureUvs from './TextureUvs'; import EventEmitter from 'eventemitter3'; import { settings } from '@pixi/settings'; -import { Rectangle } from '@pixi/math'; +import { Rectangle, Point } from '@pixi/math'; import { uid, TextureCache, getResolutionOfUrl } from '@pixi/utils'; /** @@ -41,8 +41,9 @@ * @param {PIXI.Rectangle} [orig] - The area of original texture * @param {PIXI.Rectangle} [trim] - Trimmed rectangle of original texture * @param {number} [rotate] - indicates how the texture was rotated by texture packer. See {@link PIXI.GroupD8} + * @param {PIXI.Point} [anchor] - Default anchor point used for sprite placement / rotation */ - constructor(baseTexture, frame, orig, trim, rotate) + constructor(baseTexture, frame, orig, trim, rotate, anchor) { super(); @@ -153,6 +154,14 @@ } /** + * Anchor point that is used as default if sprite is created with this texture. + * Changing the `defaultAnchor` at a later point of time will not update Sprite's anchor point. + * @member {PIXI.Point} + * @default {0,0} + */ + this.defaultAnchor = anchor ? new Point(anchor.x, anchor.y) : new Point(0, 0); + + /** * Update ID is observed by sprites and TextureMatrix instances. * Call updateUvs() to increment it. * diff --git a/packages/sprite-animated/src/AnimatedSprite.js b/packages/sprite-animated/src/AnimatedSprite.js index d5f5fce..330c683 100644 --- a/packages/sprite-animated/src/AnimatedSprite.js +++ b/packages/sprite-animated/src/AnimatedSprite.js @@ -22,7 +22,20 @@ * textureArray.push(texture); * }; * - * let mc = new PIXI.AnimatedSprite(textureArray); + * let animatedSprite = new PIXI.extras.AnimatedSprite(textureArray); + * ``` + * + * The more efficient and simpler way to create an animated sprite is using a {@link PIXI.Spritesheet} + * containing the animation definitions: + * + * ```js + * PIXI.loader.add("assets/spritesheet.json").load(setup); + * + * function setup() { + * let sheet = PIXI.loader.resources["assets/spritesheet.json"].spritesheet; + * animatedSprite = new PIXI.extras.AnimatedSprite(sheet.animations["image_sequence"]); + * ... + * } * ``` * * @class diff --git a/packages/sprite/src/Sprite.js b/packages/sprite/src/Sprite.js index ebf1f6f..91546cf 100644 --- a/packages/sprite/src/Sprite.js +++ b/packages/sprite/src/Sprite.js @@ -15,6 +15,18 @@ * let sprite = new PIXI.Sprite.from('assets/image.png'); * ``` * + * The more efficient way to create sprites is using a {@link PIXI.Spritesheet}: + * + * ```js + * PIXI.loader.add("assets/spritesheet.json").load(setup); + * + * function setup() { + * let sheet = PIXI.loader.resources["assets/spritesheet.json"].spritesheet; + * let sprite = new PIXI.Sprite(sheet.textures["image.png"]); + * ... + * } + * ``` + * * @class * @extends PIXI.Container * @memberof PIXI @@ -30,14 +42,22 @@ /** * The anchor sets the origin point of the texture. - * The default is 0,0 this means the texture's origin is the top left. + * The default is 0,0 or taken from the {@link PIXI.Texture#defaultAnchor|Texture} + * passed to the constructor. A value of 0,0 means the texture's origin is the top left. * Setting the anchor to 0.5,0.5 means the texture's origin is centered. * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner. + * Note: Updating the {@link PIXI.Texture#defaultAnchor} after a Texture is + * created does _not_ update the Sprite's anchor values. * * @member {PIXI.ObservablePoint} * @private */ - this._anchor = new ObservablePoint(this._onAnchorUpdate, this); + this._anchor = new ObservablePoint( + this._onAnchorUpdate, + this, + (texture ? texture.defaultAnchor.x : 0), + (texture ? texture.defaultAnchor.y : 0) + ); /** * The texture that the sprite is using @@ -484,7 +504,8 @@ } /** - * The anchor sets the origin point of the text. + * The anchor sets the origin point of the text. The default value is taken from the {@link PIXI.Texture|Texture} + * and passed to the constructor. * * The default is `(0,0)`, this means the text's origin is the top left. * diff --git a/packages/spritesheet/src/Spritesheet.js b/packages/spritesheet/src/Spritesheet.js index 64d932d..28597d7 100644 --- a/packages/spritesheet/src/Spritesheet.js +++ b/packages/spritesheet/src/Spritesheet.js @@ -6,6 +6,23 @@ * Utility class for maintaining reference to a collection * of Textures on a single Spritesheet. * + * To access a sprite sheet from your code pass its JSON data file to Pixi's loader: + * + * ```js + * PIXI.loader.add("images/spritesheet.json").load(setup); + * + * function setup() { + * let sheet = PIXI.loader.resources["images/spritesheet.json"].spritesheet; + * ... + * } + * ``` + * With the `sheet.textures` you can create Sprite objects,`sheet.animations` can be used to create an AnimatedSprite. + * + * Sprite sheets can be packed using tools like {@link https://codeandweb.com/texturepacker|TexturePacker}, + * {@link https://renderhjs.net/shoebox/|Shoebox} or {@link https://github.com/krzysztof-o/spritesheet.js|Spritesheet.js}. + * Default anchor points (see {@link PIXI.Texture#defaultAnchor}) and grouping of animation sprites are currently only + * supported by TexturePacker. + * * @class * @memberof PIXI */ @@ -38,12 +55,26 @@ this.baseTexture = baseTexture; /** - * Map of spritesheet textures. - * @type {Object} + * A map containing all textures of the sprite sheet. + * Can be used to create a {@link PIXI.Sprite|Sprite}: + * ```js + * new PIXI.Sprite(sheet.textures["image.png"]); + * ``` + * @member {Object} */ this.textures = {}; /** + * A map containing the textures for each animation. + * Can be used to create an {@link PIXI.extras.AnimatedSprite|AnimatedSprite}: + * ```js + * new PIXI.extras.AnimatedSprite(sheet.animations["anim_name"]) + * ``` + * @member {Object} + */ + this.animations = {}; + + /** * Reference to the original JSON data. * @type {Object} */ @@ -134,6 +165,7 @@ if (this._frameKeys.length <= Spritesheet.BATCH_SIZE) { this._processFrames(0); + this._processAnimations(); this._parseComplete(); } else @@ -208,7 +240,8 @@ frame, orig, trim, - data.rotated ? 2 : 0 + data.rotated ? 2 : 0, + data.anchor ); // lets also add the frame to pixi's global cache for fromFrame and fromImage functions @@ -220,6 +253,27 @@ } /** + * Parse animations config + * + * @private + */ + _processAnimations() + { + const animations = this.data.animations || {}; + + for (const animName in animations) + { + this.animations[animName] = []; + for (let i = 0; i < animations[animName].length; i++) + { + const frameName = animations[animName][i]; + + this.animations[animName].push(this.textures[frameName]); + } + } + } + + /** * The parse has completed. * * @private @@ -250,6 +304,7 @@ } else { + this._processAnimations(); this._parseComplete(); } }, 0); diff --git a/packages/spritesheet/test/Spritesheet.js b/packages/spritesheet/test/Spritesheet.js index b168fe0..5063a45 100644 --- a/packages/spritesheet/test/Spritesheet.js +++ b/packages/spritesheet/test/Spritesheet.js @@ -15,12 +15,20 @@ const width = Math.floor(spritesheet.data.frames[id].frame.w); const height = Math.floor(spritesheet.data.frames[id].frame.h); - expect(Object.keys(textures).length).to.equal(1); - expect(Object.keys(spritesheet.textures).length).to.equal(1); + expect(Object.keys(textures).length).to.equal(5); + expect(Object.keys(spritesheet.textures).length).to.equal(5); expect(textures[id]).to.be.an.instanceof(Texture); expect(textures[id].width).to.equal(width / spritesheet.resolution); expect(textures[id].height).to.equal(height / spritesheet.resolution); + expect(textures[id].defaultAnchor.x).to.equal(0); + expect(textures[id].defaultAnchor.y).to.equal(0); expect(textures[id].textureCacheIds.indexOf(id)).to.equal(0); + + expect(this.animations).to.have.property('star').that.is.an('array'); + expect(this.animations.star.length).to.equal(4); + expect(this.animations.star[0].defaultAnchor.x).to.equal(0.5); + expect(this.animations.star[0].defaultAnchor.y).to.equal(0.5); + spritesheet.destroy(true); expect(spritesheet.textures).to.be.null; expect(spritesheet.baseTexture).to.be.null; diff --git a/packages/spritesheet/test/SpritesheetLoader.js b/packages/spritesheet/test/SpritesheetLoader.js index 2797100..932064c 100644 --- a/packages/spritesheet/test/SpritesheetLoader.js +++ b/packages/spritesheet/test/SpritesheetLoader.js @@ -75,6 +75,19 @@ .with.keys(Object.keys(getJsonSpritesheet().frames)) .and.has.property('0.png') .that.is.an.instanceof(Texture); + + expect(res.textures['0.png'].frame.x).to.equal(14); + expect(res.textures['0.png'].frame.y).to.equal(28); + expect(res.textures['0.png'].defaultAnchor.x).to.equal(0.3); + expect(res.textures['0.png'].defaultAnchor.y).to.equal(0.4); + expect(res.textures['1.png'].defaultAnchor.x).to.equal(0.0); // default of defaultAnchor is 0,0 + expect(res.textures['1.png'].defaultAnchor.y).to.equal(0.0); + + expect(res).to.have.property('spritesheet') + .to.have.property('animations') + .to.have.property('png123'); + expect(res.spritesheet.animations.png123.length).to.equal(3); + expect(res.spritesheet.animations.png123[0]).to.equal(res.textures['1.png']); }); it('should not load binary images as an image loader type', function (done) @@ -188,7 +201,8 @@ "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} + "sourceSize": {"w":14,"h":14}, + "anchor": {"x":0.3,"y":0.4} }, "1.png": { @@ -262,6 +276,9 @@ "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, "sourceSize": {"w":14,"h":14} }}, + "animations": { + "png123": [ "1.png", "2.png", "3.png" ] + }, "meta": { "app": "http://www.texturepacker.com", "version": "1.0", diff --git a/packages/spritesheet/test/resources/building1.json b/packages/spritesheet/test/resources/building1.json old mode 100755 new mode 100644 index 03fa5c1..d8fbfe1 --- a/packages/spritesheet/test/resources/building1.json +++ b/packages/spritesheet/test/resources/building1.json @@ -6,16 +6,54 @@ "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":95,"h":115}, - "sourceSize": {"w":95,"h":115}, - "pivot": {"x":0.5,"y":0.5} + "sourceSize": {"w":95,"h":115} +}, +"star1.png": +{ + "frame": {"x":98,"y":33,"w":28,"h":29}, + "rotated": true, + "trimmed": true, + "spriteSourceSize": {"x":2,"y":2,"w":28,"h":29}, + "sourceSize": {"w":32,"h":32}, + "anchor": {"x":0.5,"y":0.5} +}, +"star2.png": +{ + "frame": {"x":98,"y":95,"w":26,"h":30}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":3,"y":1,"w":26,"h":30}, + "sourceSize": {"w":32,"h":32}, + "anchor": {"x":0.5,"y":0.5} +}, +"star3.png": +{ + "frame": {"x":98,"y":1,"w":30,"h":29}, + "rotated": true, + "trimmed": true, + "spriteSourceSize": {"x":1,"y":2,"w":30,"h":29}, + "sourceSize": {"w":32,"h":32}, + "anchor": {"x":0.5,"y":0.5} +}, +"star4.png": +{ + "frame": {"x":98,"y":63,"w":30,"h":27}, + "rotated": true, + "trimmed": true, + "spriteSourceSize": {"x":1,"y":3,"w":30,"h":27}, + "sourceSize": {"w":32,"h":32}, + "anchor": {"x":0.5,"y":0.5} }}, +"animations": { + "star": ["star1.png","star2.png","star3.png","star4.png"] +}, "meta": { - "app": "http://www.codeandweb.com/texturepacker", + "app": "https://www.codeandweb.com/texturepacker", "version": "1.0", "image": "building1.png", "format": "RGBA8888", - "size": {"w":128,"h":128}, + "size": {"w":128,"h":126}, "scale": "0.5", - "smartupdate": "$TexturePacker:SmartUpdate:d7a5e54c8f8a3fecd508baf190c44807:99a0e3a4dc0f441e4aad77614191ab38:6046b8eb706ddefaa771c33ceb7cb6d5$" + "smartupdate": "$TexturePacker:SmartUpdate:1cb0f14cbdd5e7bcecd332ecd0eaa9f7:8acde9d234ecca966a410602c71bffad:6046b8eb706ddefaa771c33ceb7cb6d5$" } } diff --git a/packages/spritesheet/test/resources/building1.png b/packages/spritesheet/test/resources/building1.png old mode 100755 new mode 100644 index 7e1e114..7e16cda --- a/packages/spritesheet/test/resources/building1.png +++ b/packages/spritesheet/test/resources/building1.png Binary files differ diff --git a/packages/spritesheet/test/resources/building1@2x.json b/packages/spritesheet/test/resources/building1@2x.json old mode 100755 new mode 100644 index 24e25ff..c8cb7bf --- a/packages/spritesheet/test/resources/building1@2x.json +++ b/packages/spritesheet/test/resources/building1@2x.json @@ -6,16 +6,54 @@ "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":190,"h":229}, - "sourceSize": {"w":190,"h":229}, - "pivot": {"x":0.5,"y":0.5} + "sourceSize": {"w":190,"h":229} +}, +"star1.png": +{ + "frame": {"x":193,"y":59,"w":54,"h":55}, + "rotated": true, + "trimmed": true, + "spriteSourceSize": {"x":5,"y":5,"w":54,"h":55}, + "sourceSize": {"w":64,"h":64}, + "anchor": {"x":0.5,"y":0.5} +}, +"star2.png": +{ + "frame": {"x":193,"y":115,"w":50,"h":57}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":7,"y":4,"w":50,"h":57}, + "sourceSize": {"w":64,"h":64}, + "anchor": {"x":0.5,"y":0.5} +}, +"star3.png": +{ + "frame": {"x":193,"y":1,"w":56,"h":55}, + "rotated": true, + "trimmed": true, + "spriteSourceSize": {"x":4,"y":5,"w":56,"h":55}, + "sourceSize": {"w":64,"h":64}, + "anchor": {"x":0.5,"y":0.5} +}, +"star4.png": +{ + "frame": {"x":193,"y":174,"w":56,"h":50}, + "rotated": true, + "trimmed": true, + "spriteSourceSize": {"x":4,"y":8,"w":56,"h":50}, + "sourceSize": {"w":64,"h":64}, + "anchor": {"x":0.5,"y":0.5} }}, +"animations": { + "star": ["star1.png","star2.png","star3.png","star4.png"] +}, "meta": { - "app": "http://www.codeandweb.com/texturepacker", + "app": "https://www.codeandweb.com/texturepacker", "version": "1.0", "image": "building1@2x.png", "format": "RGBA8888", - "size": {"w":256,"h":256}, + "size": {"w":249,"h":231}, "scale": "1", - "smartupdate": "$TexturePacker:SmartUpdate:d7a5e54c8f8a3fecd508baf190c44807:99a0e3a4dc0f441e4aad77614191ab38:6046b8eb706ddefaa771c33ceb7cb6d5$" + "smartupdate": "$TexturePacker:SmartUpdate:1cb0f14cbdd5e7bcecd332ecd0eaa9f7:8acde9d234ecca966a410602c71bffad:6046b8eb706ddefaa771c33ceb7cb6d5$" } } diff --git a/packages/spritesheet/test/resources/building1@2x.png b/packages/spritesheet/test/resources/building1@2x.png old mode 100755 new mode 100644 index d5ecd04..06e613f --- a/packages/spritesheet/test/resources/building1@2x.png +++ b/packages/spritesheet/test/resources/building1@2x.png Binary files differ diff --git a/test/core/resources/src/goldmine_10_5.png b/test/core/resources/src/goldmine_10_5.png new file mode 100755 index 0000000..de2ba5a --- /dev/null +++ b/test/core/resources/src/goldmine_10_5.png Binary files differ diff --git a/test/core/resources/src/sheet.tps b/test/core/resources/src/sheet.tps new file mode 100644 index 0000000..d08b10c --- /dev/null +++ b/test/core/resources/src/sheet.tps @@ -0,0 +1,262 @@ + + + + fileFormatVersion + 4 + texturePackerVersion + 4.8.1 + autoSDSettings + + + scale + 1 + extension + @2x + spriteFilter + + acceptFractionalValues + + maxTextureSize + + width + -1 + height + -1 + + + + scale + 0.5 + extension + + spriteFilter + + acceptFractionalValues + + maxTextureSize + + width + -1 + height + -1 + + + + allowRotation + + shapeDebug + + dpi + 72 + dataFormat + pixijs4 + textureFileName + + flipPVR + + pvrCompressionQuality + PVR_QUALITY_NORMAL + atfCompressData + + mipMapMinSize + 32768 + etc1CompressionQuality + ETC1_QUALITY_LOW_PERCEPTUAL + etc2CompressionQuality + ETC2_QUALITY_LOW_PERCEPTUAL + dxtCompressionMode + DXT_PERCEPTUAL + jxrColorFormat + JXR_YUV444 + jxrTrimFlexBits + 0 + jxrCompressionLevel + 0 + ditherType + NearestNeighbour + backgroundColor + 0 + libGdx + + filtering + + x + Linear + y + Linear + + + shapePadding + 0 + jpgQuality + 80 + pngOptimizationLevel + 1 + webpQualityLevel + 101 + textureSubPath + + atfFormats + + textureFormat + png + borderPadding + 0 + maxTextureSize + + width + 2048 + height + 2048 + + fixedTextureSize + + width + -1 + height + -1 + + algorithmSettings + + algorithm + MaxRects + freeSizeMode + Best + sizeConstraints + AnySize + forceSquared + + maxRects + + heuristic + Best + + basic + + sortBy + Best + order + Ascending + + polygon + + alignToGrid + 1 + + + dataFileNames + + data + + name + ../building1{v}.json + + + multiPack + + forceIdenticalLayout + + outputFormat + RGBA8888 + alphaHandling + ClearTransparentPixels + contentProtection + + key + + + autoAliasEnabled + + trimSpriteNames + + prependSmartFolderName + + autodetectAnimations + + globalSpriteSettings + + scale + 1 + scaleMode + Smooth + extrude + 1 + trimThreshold + 1 + trimMargin + 1 + trimMode + Trim + tracerTolerance + 200 + heuristicMask + + defaultPivotPoint + 0,0 + writePivotPoints + + + individualSpriteSettings + + goldmine_10_5.png + + pivotPoint + 0,0 + scale9Enabled + + scale9Borders + 48,57,95,115 + scale9Paddings + 48,57,95,115 + scale9FromFile + + + star1.png + star2.png + star3.png + star4.png + + pivotPoint + 0.5,0.5 + scale9Enabled + + scale9Borders + 16,16,32,32 + scale9Paddings + 16,16,32,32 + scale9FromFile + + + + fileList + + goldmine_10_5.png + star1.png + star2.png + star3.png + star4.png + + ignoreFileList + + replaceList + + ignoredWarnings + + commonDivisorX + 1 + commonDivisorY + 1 + packNormalMaps + + autodetectNormalMaps + + normalMapFilter + + normalMapSuffix + + normalMapSheetFileName + + exporterProperties + + + diff --git a/packages/core/src/textures/Texture.js b/packages/core/src/textures/Texture.js index 9148656..f9bb8bc 100644 --- a/packages/core/src/textures/Texture.js +++ b/packages/core/src/textures/Texture.js @@ -4,7 +4,7 @@ import TextureUvs from './TextureUvs'; import EventEmitter from 'eventemitter3'; import { settings } from '@pixi/settings'; -import { Rectangle } from '@pixi/math'; +import { Rectangle, Point } from '@pixi/math'; import { uid, TextureCache, getResolutionOfUrl } from '@pixi/utils'; /** @@ -41,8 +41,9 @@ * @param {PIXI.Rectangle} [orig] - The area of original texture * @param {PIXI.Rectangle} [trim] - Trimmed rectangle of original texture * @param {number} [rotate] - indicates how the texture was rotated by texture packer. See {@link PIXI.GroupD8} + * @param {PIXI.Point} [anchor] - Default anchor point used for sprite placement / rotation */ - constructor(baseTexture, frame, orig, trim, rotate) + constructor(baseTexture, frame, orig, trim, rotate, anchor) { super(); @@ -153,6 +154,14 @@ } /** + * Anchor point that is used as default if sprite is created with this texture. + * Changing the `defaultAnchor` at a later point of time will not update Sprite's anchor point. + * @member {PIXI.Point} + * @default {0,0} + */ + this.defaultAnchor = anchor ? new Point(anchor.x, anchor.y) : new Point(0, 0); + + /** * Update ID is observed by sprites and TextureMatrix instances. * Call updateUvs() to increment it. * diff --git a/packages/sprite-animated/src/AnimatedSprite.js b/packages/sprite-animated/src/AnimatedSprite.js index d5f5fce..330c683 100644 --- a/packages/sprite-animated/src/AnimatedSprite.js +++ b/packages/sprite-animated/src/AnimatedSprite.js @@ -22,7 +22,20 @@ * textureArray.push(texture); * }; * - * let mc = new PIXI.AnimatedSprite(textureArray); + * let animatedSprite = new PIXI.extras.AnimatedSprite(textureArray); + * ``` + * + * The more efficient and simpler way to create an animated sprite is using a {@link PIXI.Spritesheet} + * containing the animation definitions: + * + * ```js + * PIXI.loader.add("assets/spritesheet.json").load(setup); + * + * function setup() { + * let sheet = PIXI.loader.resources["assets/spritesheet.json"].spritesheet; + * animatedSprite = new PIXI.extras.AnimatedSprite(sheet.animations["image_sequence"]); + * ... + * } * ``` * * @class diff --git a/packages/sprite/src/Sprite.js b/packages/sprite/src/Sprite.js index ebf1f6f..91546cf 100644 --- a/packages/sprite/src/Sprite.js +++ b/packages/sprite/src/Sprite.js @@ -15,6 +15,18 @@ * let sprite = new PIXI.Sprite.from('assets/image.png'); * ``` * + * The more efficient way to create sprites is using a {@link PIXI.Spritesheet}: + * + * ```js + * PIXI.loader.add("assets/spritesheet.json").load(setup); + * + * function setup() { + * let sheet = PIXI.loader.resources["assets/spritesheet.json"].spritesheet; + * let sprite = new PIXI.Sprite(sheet.textures["image.png"]); + * ... + * } + * ``` + * * @class * @extends PIXI.Container * @memberof PIXI @@ -30,14 +42,22 @@ /** * The anchor sets the origin point of the texture. - * The default is 0,0 this means the texture's origin is the top left. + * The default is 0,0 or taken from the {@link PIXI.Texture#defaultAnchor|Texture} + * passed to the constructor. A value of 0,0 means the texture's origin is the top left. * Setting the anchor to 0.5,0.5 means the texture's origin is centered. * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner. + * Note: Updating the {@link PIXI.Texture#defaultAnchor} after a Texture is + * created does _not_ update the Sprite's anchor values. * * @member {PIXI.ObservablePoint} * @private */ - this._anchor = new ObservablePoint(this._onAnchorUpdate, this); + this._anchor = new ObservablePoint( + this._onAnchorUpdate, + this, + (texture ? texture.defaultAnchor.x : 0), + (texture ? texture.defaultAnchor.y : 0) + ); /** * The texture that the sprite is using @@ -484,7 +504,8 @@ } /** - * The anchor sets the origin point of the text. + * The anchor sets the origin point of the text. The default value is taken from the {@link PIXI.Texture|Texture} + * and passed to the constructor. * * The default is `(0,0)`, this means the text's origin is the top left. * diff --git a/packages/spritesheet/src/Spritesheet.js b/packages/spritesheet/src/Spritesheet.js index 64d932d..28597d7 100644 --- a/packages/spritesheet/src/Spritesheet.js +++ b/packages/spritesheet/src/Spritesheet.js @@ -6,6 +6,23 @@ * Utility class for maintaining reference to a collection * of Textures on a single Spritesheet. * + * To access a sprite sheet from your code pass its JSON data file to Pixi's loader: + * + * ```js + * PIXI.loader.add("images/spritesheet.json").load(setup); + * + * function setup() { + * let sheet = PIXI.loader.resources["images/spritesheet.json"].spritesheet; + * ... + * } + * ``` + * With the `sheet.textures` you can create Sprite objects,`sheet.animations` can be used to create an AnimatedSprite. + * + * Sprite sheets can be packed using tools like {@link https://codeandweb.com/texturepacker|TexturePacker}, + * {@link https://renderhjs.net/shoebox/|Shoebox} or {@link https://github.com/krzysztof-o/spritesheet.js|Spritesheet.js}. + * Default anchor points (see {@link PIXI.Texture#defaultAnchor}) and grouping of animation sprites are currently only + * supported by TexturePacker. + * * @class * @memberof PIXI */ @@ -38,12 +55,26 @@ this.baseTexture = baseTexture; /** - * Map of spritesheet textures. - * @type {Object} + * A map containing all textures of the sprite sheet. + * Can be used to create a {@link PIXI.Sprite|Sprite}: + * ```js + * new PIXI.Sprite(sheet.textures["image.png"]); + * ``` + * @member {Object} */ this.textures = {}; /** + * A map containing the textures for each animation. + * Can be used to create an {@link PIXI.extras.AnimatedSprite|AnimatedSprite}: + * ```js + * new PIXI.extras.AnimatedSprite(sheet.animations["anim_name"]) + * ``` + * @member {Object} + */ + this.animations = {}; + + /** * Reference to the original JSON data. * @type {Object} */ @@ -134,6 +165,7 @@ if (this._frameKeys.length <= Spritesheet.BATCH_SIZE) { this._processFrames(0); + this._processAnimations(); this._parseComplete(); } else @@ -208,7 +240,8 @@ frame, orig, trim, - data.rotated ? 2 : 0 + data.rotated ? 2 : 0, + data.anchor ); // lets also add the frame to pixi's global cache for fromFrame and fromImage functions @@ -220,6 +253,27 @@ } /** + * Parse animations config + * + * @private + */ + _processAnimations() + { + const animations = this.data.animations || {}; + + for (const animName in animations) + { + this.animations[animName] = []; + for (let i = 0; i < animations[animName].length; i++) + { + const frameName = animations[animName][i]; + + this.animations[animName].push(this.textures[frameName]); + } + } + } + + /** * The parse has completed. * * @private @@ -250,6 +304,7 @@ } else { + this._processAnimations(); this._parseComplete(); } }, 0); diff --git a/packages/spritesheet/test/Spritesheet.js b/packages/spritesheet/test/Spritesheet.js index b168fe0..5063a45 100644 --- a/packages/spritesheet/test/Spritesheet.js +++ b/packages/spritesheet/test/Spritesheet.js @@ -15,12 +15,20 @@ const width = Math.floor(spritesheet.data.frames[id].frame.w); const height = Math.floor(spritesheet.data.frames[id].frame.h); - expect(Object.keys(textures).length).to.equal(1); - expect(Object.keys(spritesheet.textures).length).to.equal(1); + expect(Object.keys(textures).length).to.equal(5); + expect(Object.keys(spritesheet.textures).length).to.equal(5); expect(textures[id]).to.be.an.instanceof(Texture); expect(textures[id].width).to.equal(width / spritesheet.resolution); expect(textures[id].height).to.equal(height / spritesheet.resolution); + expect(textures[id].defaultAnchor.x).to.equal(0); + expect(textures[id].defaultAnchor.y).to.equal(0); expect(textures[id].textureCacheIds.indexOf(id)).to.equal(0); + + expect(this.animations).to.have.property('star').that.is.an('array'); + expect(this.animations.star.length).to.equal(4); + expect(this.animations.star[0].defaultAnchor.x).to.equal(0.5); + expect(this.animations.star[0].defaultAnchor.y).to.equal(0.5); + spritesheet.destroy(true); expect(spritesheet.textures).to.be.null; expect(spritesheet.baseTexture).to.be.null; diff --git a/packages/spritesheet/test/SpritesheetLoader.js b/packages/spritesheet/test/SpritesheetLoader.js index 2797100..932064c 100644 --- a/packages/spritesheet/test/SpritesheetLoader.js +++ b/packages/spritesheet/test/SpritesheetLoader.js @@ -75,6 +75,19 @@ .with.keys(Object.keys(getJsonSpritesheet().frames)) .and.has.property('0.png') .that.is.an.instanceof(Texture); + + expect(res.textures['0.png'].frame.x).to.equal(14); + expect(res.textures['0.png'].frame.y).to.equal(28); + expect(res.textures['0.png'].defaultAnchor.x).to.equal(0.3); + expect(res.textures['0.png'].defaultAnchor.y).to.equal(0.4); + expect(res.textures['1.png'].defaultAnchor.x).to.equal(0.0); // default of defaultAnchor is 0,0 + expect(res.textures['1.png'].defaultAnchor.y).to.equal(0.0); + + expect(res).to.have.property('spritesheet') + .to.have.property('animations') + .to.have.property('png123'); + expect(res.spritesheet.animations.png123.length).to.equal(3); + expect(res.spritesheet.animations.png123[0]).to.equal(res.textures['1.png']); }); it('should not load binary images as an image loader type', function (done) @@ -188,7 +201,8 @@ "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} + "sourceSize": {"w":14,"h":14}, + "anchor": {"x":0.3,"y":0.4} }, "1.png": { @@ -262,6 +276,9 @@ "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, "sourceSize": {"w":14,"h":14} }}, + "animations": { + "png123": [ "1.png", "2.png", "3.png" ] + }, "meta": { "app": "http://www.texturepacker.com", "version": "1.0", diff --git a/packages/spritesheet/test/resources/building1.json b/packages/spritesheet/test/resources/building1.json old mode 100755 new mode 100644 index 03fa5c1..d8fbfe1 --- a/packages/spritesheet/test/resources/building1.json +++ b/packages/spritesheet/test/resources/building1.json @@ -6,16 +6,54 @@ "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":95,"h":115}, - "sourceSize": {"w":95,"h":115}, - "pivot": {"x":0.5,"y":0.5} + "sourceSize": {"w":95,"h":115} +}, +"star1.png": +{ + "frame": {"x":98,"y":33,"w":28,"h":29}, + "rotated": true, + "trimmed": true, + "spriteSourceSize": {"x":2,"y":2,"w":28,"h":29}, + "sourceSize": {"w":32,"h":32}, + "anchor": {"x":0.5,"y":0.5} +}, +"star2.png": +{ + "frame": {"x":98,"y":95,"w":26,"h":30}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":3,"y":1,"w":26,"h":30}, + "sourceSize": {"w":32,"h":32}, + "anchor": {"x":0.5,"y":0.5} +}, +"star3.png": +{ + "frame": {"x":98,"y":1,"w":30,"h":29}, + "rotated": true, + "trimmed": true, + "spriteSourceSize": {"x":1,"y":2,"w":30,"h":29}, + "sourceSize": {"w":32,"h":32}, + "anchor": {"x":0.5,"y":0.5} +}, +"star4.png": +{ + "frame": {"x":98,"y":63,"w":30,"h":27}, + "rotated": true, + "trimmed": true, + "spriteSourceSize": {"x":1,"y":3,"w":30,"h":27}, + "sourceSize": {"w":32,"h":32}, + "anchor": {"x":0.5,"y":0.5} }}, +"animations": { + "star": ["star1.png","star2.png","star3.png","star4.png"] +}, "meta": { - "app": "http://www.codeandweb.com/texturepacker", + "app": "https://www.codeandweb.com/texturepacker", "version": "1.0", "image": "building1.png", "format": "RGBA8888", - "size": {"w":128,"h":128}, + "size": {"w":128,"h":126}, "scale": "0.5", - "smartupdate": "$TexturePacker:SmartUpdate:d7a5e54c8f8a3fecd508baf190c44807:99a0e3a4dc0f441e4aad77614191ab38:6046b8eb706ddefaa771c33ceb7cb6d5$" + "smartupdate": "$TexturePacker:SmartUpdate:1cb0f14cbdd5e7bcecd332ecd0eaa9f7:8acde9d234ecca966a410602c71bffad:6046b8eb706ddefaa771c33ceb7cb6d5$" } } diff --git a/packages/spritesheet/test/resources/building1.png b/packages/spritesheet/test/resources/building1.png old mode 100755 new mode 100644 index 7e1e114..7e16cda --- a/packages/spritesheet/test/resources/building1.png +++ b/packages/spritesheet/test/resources/building1.png Binary files differ diff --git a/packages/spritesheet/test/resources/building1@2x.json b/packages/spritesheet/test/resources/building1@2x.json old mode 100755 new mode 100644 index 24e25ff..c8cb7bf --- a/packages/spritesheet/test/resources/building1@2x.json +++ b/packages/spritesheet/test/resources/building1@2x.json @@ -6,16 +6,54 @@ "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":190,"h":229}, - "sourceSize": {"w":190,"h":229}, - "pivot": {"x":0.5,"y":0.5} + "sourceSize": {"w":190,"h":229} +}, +"star1.png": +{ + "frame": {"x":193,"y":59,"w":54,"h":55}, + "rotated": true, + "trimmed": true, + "spriteSourceSize": {"x":5,"y":5,"w":54,"h":55}, + "sourceSize": {"w":64,"h":64}, + "anchor": {"x":0.5,"y":0.5} +}, +"star2.png": +{ + "frame": {"x":193,"y":115,"w":50,"h":57}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":7,"y":4,"w":50,"h":57}, + "sourceSize": {"w":64,"h":64}, + "anchor": {"x":0.5,"y":0.5} +}, +"star3.png": +{ + "frame": {"x":193,"y":1,"w":56,"h":55}, + "rotated": true, + "trimmed": true, + "spriteSourceSize": {"x":4,"y":5,"w":56,"h":55}, + "sourceSize": {"w":64,"h":64}, + "anchor": {"x":0.5,"y":0.5} +}, +"star4.png": +{ + "frame": {"x":193,"y":174,"w":56,"h":50}, + "rotated": true, + "trimmed": true, + "spriteSourceSize": {"x":4,"y":8,"w":56,"h":50}, + "sourceSize": {"w":64,"h":64}, + "anchor": {"x":0.5,"y":0.5} }}, +"animations": { + "star": ["star1.png","star2.png","star3.png","star4.png"] +}, "meta": { - "app": "http://www.codeandweb.com/texturepacker", + "app": "https://www.codeandweb.com/texturepacker", "version": "1.0", "image": "building1@2x.png", "format": "RGBA8888", - "size": {"w":256,"h":256}, + "size": {"w":249,"h":231}, "scale": "1", - "smartupdate": "$TexturePacker:SmartUpdate:d7a5e54c8f8a3fecd508baf190c44807:99a0e3a4dc0f441e4aad77614191ab38:6046b8eb706ddefaa771c33ceb7cb6d5$" + "smartupdate": "$TexturePacker:SmartUpdate:1cb0f14cbdd5e7bcecd332ecd0eaa9f7:8acde9d234ecca966a410602c71bffad:6046b8eb706ddefaa771c33ceb7cb6d5$" } } diff --git a/packages/spritesheet/test/resources/building1@2x.png b/packages/spritesheet/test/resources/building1@2x.png old mode 100755 new mode 100644 index d5ecd04..06e613f --- a/packages/spritesheet/test/resources/building1@2x.png +++ b/packages/spritesheet/test/resources/building1@2x.png Binary files differ diff --git a/test/core/resources/src/goldmine_10_5.png b/test/core/resources/src/goldmine_10_5.png new file mode 100755 index 0000000..de2ba5a --- /dev/null +++ b/test/core/resources/src/goldmine_10_5.png Binary files differ diff --git a/test/core/resources/src/sheet.tps b/test/core/resources/src/sheet.tps new file mode 100644 index 0000000..d08b10c --- /dev/null +++ b/test/core/resources/src/sheet.tps @@ -0,0 +1,262 @@ + + + + fileFormatVersion + 4 + texturePackerVersion + 4.8.1 + autoSDSettings + + + scale + 1 + extension + @2x + spriteFilter + + acceptFractionalValues + + maxTextureSize + + width + -1 + height + -1 + + + + scale + 0.5 + extension + + spriteFilter + + acceptFractionalValues + + maxTextureSize + + width + -1 + height + -1 + + + + allowRotation + + shapeDebug + + dpi + 72 + dataFormat + pixijs4 + textureFileName + + flipPVR + + pvrCompressionQuality + PVR_QUALITY_NORMAL + atfCompressData + + mipMapMinSize + 32768 + etc1CompressionQuality + ETC1_QUALITY_LOW_PERCEPTUAL + etc2CompressionQuality + ETC2_QUALITY_LOW_PERCEPTUAL + dxtCompressionMode + DXT_PERCEPTUAL + jxrColorFormat + JXR_YUV444 + jxrTrimFlexBits + 0 + jxrCompressionLevel + 0 + ditherType + NearestNeighbour + backgroundColor + 0 + libGdx + + filtering + + x + Linear + y + Linear + + + shapePadding + 0 + jpgQuality + 80 + pngOptimizationLevel + 1 + webpQualityLevel + 101 + textureSubPath + + atfFormats + + textureFormat + png + borderPadding + 0 + maxTextureSize + + width + 2048 + height + 2048 + + fixedTextureSize + + width + -1 + height + -1 + + algorithmSettings + + algorithm + MaxRects + freeSizeMode + Best + sizeConstraints + AnySize + forceSquared + + maxRects + + heuristic + Best + + basic + + sortBy + Best + order + Ascending + + polygon + + alignToGrid + 1 + + + dataFileNames + + data + + name + ../building1{v}.json + + + multiPack + + forceIdenticalLayout + + outputFormat + RGBA8888 + alphaHandling + ClearTransparentPixels + contentProtection + + key + + + autoAliasEnabled + + trimSpriteNames + + prependSmartFolderName + + autodetectAnimations + + globalSpriteSettings + + scale + 1 + scaleMode + Smooth + extrude + 1 + trimThreshold + 1 + trimMargin + 1 + trimMode + Trim + tracerTolerance + 200 + heuristicMask + + defaultPivotPoint + 0,0 + writePivotPoints + + + individualSpriteSettings + + goldmine_10_5.png + + pivotPoint + 0,0 + scale9Enabled + + scale9Borders + 48,57,95,115 + scale9Paddings + 48,57,95,115 + scale9FromFile + + + star1.png + star2.png + star3.png + star4.png + + pivotPoint + 0.5,0.5 + scale9Enabled + + scale9Borders + 16,16,32,32 + scale9Paddings + 16,16,32,32 + scale9FromFile + + + + fileList + + goldmine_10_5.png + star1.png + star2.png + star3.png + star4.png + + ignoreFileList + + replaceList + + ignoredWarnings + + commonDivisorX + 1 + commonDivisorY + 1 + packNormalMaps + + autodetectNormalMaps + + normalMapFilter + + normalMapSuffix + + normalMapSheetFileName + + exporterProperties + + + diff --git a/test/core/resources/src/star1.png b/test/core/resources/src/star1.png new file mode 100644 index 0000000..32824fd --- /dev/null +++ b/test/core/resources/src/star1.png Binary files differ diff --git a/packages/core/src/textures/Texture.js b/packages/core/src/textures/Texture.js index 9148656..f9bb8bc 100644 --- a/packages/core/src/textures/Texture.js +++ b/packages/core/src/textures/Texture.js @@ -4,7 +4,7 @@ import TextureUvs from './TextureUvs'; import EventEmitter from 'eventemitter3'; import { settings } from '@pixi/settings'; -import { Rectangle } from '@pixi/math'; +import { Rectangle, Point } from '@pixi/math'; import { uid, TextureCache, getResolutionOfUrl } from '@pixi/utils'; /** @@ -41,8 +41,9 @@ * @param {PIXI.Rectangle} [orig] - The area of original texture * @param {PIXI.Rectangle} [trim] - Trimmed rectangle of original texture * @param {number} [rotate] - indicates how the texture was rotated by texture packer. See {@link PIXI.GroupD8} + * @param {PIXI.Point} [anchor] - Default anchor point used for sprite placement / rotation */ - constructor(baseTexture, frame, orig, trim, rotate) + constructor(baseTexture, frame, orig, trim, rotate, anchor) { super(); @@ -153,6 +154,14 @@ } /** + * Anchor point that is used as default if sprite is created with this texture. + * Changing the `defaultAnchor` at a later point of time will not update Sprite's anchor point. + * @member {PIXI.Point} + * @default {0,0} + */ + this.defaultAnchor = anchor ? new Point(anchor.x, anchor.y) : new Point(0, 0); + + /** * Update ID is observed by sprites and TextureMatrix instances. * Call updateUvs() to increment it. * diff --git a/packages/sprite-animated/src/AnimatedSprite.js b/packages/sprite-animated/src/AnimatedSprite.js index d5f5fce..330c683 100644 --- a/packages/sprite-animated/src/AnimatedSprite.js +++ b/packages/sprite-animated/src/AnimatedSprite.js @@ -22,7 +22,20 @@ * textureArray.push(texture); * }; * - * let mc = new PIXI.AnimatedSprite(textureArray); + * let animatedSprite = new PIXI.extras.AnimatedSprite(textureArray); + * ``` + * + * The more efficient and simpler way to create an animated sprite is using a {@link PIXI.Spritesheet} + * containing the animation definitions: + * + * ```js + * PIXI.loader.add("assets/spritesheet.json").load(setup); + * + * function setup() { + * let sheet = PIXI.loader.resources["assets/spritesheet.json"].spritesheet; + * animatedSprite = new PIXI.extras.AnimatedSprite(sheet.animations["image_sequence"]); + * ... + * } * ``` * * @class diff --git a/packages/sprite/src/Sprite.js b/packages/sprite/src/Sprite.js index ebf1f6f..91546cf 100644 --- a/packages/sprite/src/Sprite.js +++ b/packages/sprite/src/Sprite.js @@ -15,6 +15,18 @@ * let sprite = new PIXI.Sprite.from('assets/image.png'); * ``` * + * The more efficient way to create sprites is using a {@link PIXI.Spritesheet}: + * + * ```js + * PIXI.loader.add("assets/spritesheet.json").load(setup); + * + * function setup() { + * let sheet = PIXI.loader.resources["assets/spritesheet.json"].spritesheet; + * let sprite = new PIXI.Sprite(sheet.textures["image.png"]); + * ... + * } + * ``` + * * @class * @extends PIXI.Container * @memberof PIXI @@ -30,14 +42,22 @@ /** * The anchor sets the origin point of the texture. - * The default is 0,0 this means the texture's origin is the top left. + * The default is 0,0 or taken from the {@link PIXI.Texture#defaultAnchor|Texture} + * passed to the constructor. A value of 0,0 means the texture's origin is the top left. * Setting the anchor to 0.5,0.5 means the texture's origin is centered. * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner. + * Note: Updating the {@link PIXI.Texture#defaultAnchor} after a Texture is + * created does _not_ update the Sprite's anchor values. * * @member {PIXI.ObservablePoint} * @private */ - this._anchor = new ObservablePoint(this._onAnchorUpdate, this); + this._anchor = new ObservablePoint( + this._onAnchorUpdate, + this, + (texture ? texture.defaultAnchor.x : 0), + (texture ? texture.defaultAnchor.y : 0) + ); /** * The texture that the sprite is using @@ -484,7 +504,8 @@ } /** - * The anchor sets the origin point of the text. + * The anchor sets the origin point of the text. The default value is taken from the {@link PIXI.Texture|Texture} + * and passed to the constructor. * * The default is `(0,0)`, this means the text's origin is the top left. * diff --git a/packages/spritesheet/src/Spritesheet.js b/packages/spritesheet/src/Spritesheet.js index 64d932d..28597d7 100644 --- a/packages/spritesheet/src/Spritesheet.js +++ b/packages/spritesheet/src/Spritesheet.js @@ -6,6 +6,23 @@ * Utility class for maintaining reference to a collection * of Textures on a single Spritesheet. * + * To access a sprite sheet from your code pass its JSON data file to Pixi's loader: + * + * ```js + * PIXI.loader.add("images/spritesheet.json").load(setup); + * + * function setup() { + * let sheet = PIXI.loader.resources["images/spritesheet.json"].spritesheet; + * ... + * } + * ``` + * With the `sheet.textures` you can create Sprite objects,`sheet.animations` can be used to create an AnimatedSprite. + * + * Sprite sheets can be packed using tools like {@link https://codeandweb.com/texturepacker|TexturePacker}, + * {@link https://renderhjs.net/shoebox/|Shoebox} or {@link https://github.com/krzysztof-o/spritesheet.js|Spritesheet.js}. + * Default anchor points (see {@link PIXI.Texture#defaultAnchor}) and grouping of animation sprites are currently only + * supported by TexturePacker. + * * @class * @memberof PIXI */ @@ -38,12 +55,26 @@ this.baseTexture = baseTexture; /** - * Map of spritesheet textures. - * @type {Object} + * A map containing all textures of the sprite sheet. + * Can be used to create a {@link PIXI.Sprite|Sprite}: + * ```js + * new PIXI.Sprite(sheet.textures["image.png"]); + * ``` + * @member {Object} */ this.textures = {}; /** + * A map containing the textures for each animation. + * Can be used to create an {@link PIXI.extras.AnimatedSprite|AnimatedSprite}: + * ```js + * new PIXI.extras.AnimatedSprite(sheet.animations["anim_name"]) + * ``` + * @member {Object} + */ + this.animations = {}; + + /** * Reference to the original JSON data. * @type {Object} */ @@ -134,6 +165,7 @@ if (this._frameKeys.length <= Spritesheet.BATCH_SIZE) { this._processFrames(0); + this._processAnimations(); this._parseComplete(); } else @@ -208,7 +240,8 @@ frame, orig, trim, - data.rotated ? 2 : 0 + data.rotated ? 2 : 0, + data.anchor ); // lets also add the frame to pixi's global cache for fromFrame and fromImage functions @@ -220,6 +253,27 @@ } /** + * Parse animations config + * + * @private + */ + _processAnimations() + { + const animations = this.data.animations || {}; + + for (const animName in animations) + { + this.animations[animName] = []; + for (let i = 0; i < animations[animName].length; i++) + { + const frameName = animations[animName][i]; + + this.animations[animName].push(this.textures[frameName]); + } + } + } + + /** * The parse has completed. * * @private @@ -250,6 +304,7 @@ } else { + this._processAnimations(); this._parseComplete(); } }, 0); diff --git a/packages/spritesheet/test/Spritesheet.js b/packages/spritesheet/test/Spritesheet.js index b168fe0..5063a45 100644 --- a/packages/spritesheet/test/Spritesheet.js +++ b/packages/spritesheet/test/Spritesheet.js @@ -15,12 +15,20 @@ const width = Math.floor(spritesheet.data.frames[id].frame.w); const height = Math.floor(spritesheet.data.frames[id].frame.h); - expect(Object.keys(textures).length).to.equal(1); - expect(Object.keys(spritesheet.textures).length).to.equal(1); + expect(Object.keys(textures).length).to.equal(5); + expect(Object.keys(spritesheet.textures).length).to.equal(5); expect(textures[id]).to.be.an.instanceof(Texture); expect(textures[id].width).to.equal(width / spritesheet.resolution); expect(textures[id].height).to.equal(height / spritesheet.resolution); + expect(textures[id].defaultAnchor.x).to.equal(0); + expect(textures[id].defaultAnchor.y).to.equal(0); expect(textures[id].textureCacheIds.indexOf(id)).to.equal(0); + + expect(this.animations).to.have.property('star').that.is.an('array'); + expect(this.animations.star.length).to.equal(4); + expect(this.animations.star[0].defaultAnchor.x).to.equal(0.5); + expect(this.animations.star[0].defaultAnchor.y).to.equal(0.5); + spritesheet.destroy(true); expect(spritesheet.textures).to.be.null; expect(spritesheet.baseTexture).to.be.null; diff --git a/packages/spritesheet/test/SpritesheetLoader.js b/packages/spritesheet/test/SpritesheetLoader.js index 2797100..932064c 100644 --- a/packages/spritesheet/test/SpritesheetLoader.js +++ b/packages/spritesheet/test/SpritesheetLoader.js @@ -75,6 +75,19 @@ .with.keys(Object.keys(getJsonSpritesheet().frames)) .and.has.property('0.png') .that.is.an.instanceof(Texture); + + expect(res.textures['0.png'].frame.x).to.equal(14); + expect(res.textures['0.png'].frame.y).to.equal(28); + expect(res.textures['0.png'].defaultAnchor.x).to.equal(0.3); + expect(res.textures['0.png'].defaultAnchor.y).to.equal(0.4); + expect(res.textures['1.png'].defaultAnchor.x).to.equal(0.0); // default of defaultAnchor is 0,0 + expect(res.textures['1.png'].defaultAnchor.y).to.equal(0.0); + + expect(res).to.have.property('spritesheet') + .to.have.property('animations') + .to.have.property('png123'); + expect(res.spritesheet.animations.png123.length).to.equal(3); + expect(res.spritesheet.animations.png123[0]).to.equal(res.textures['1.png']); }); it('should not load binary images as an image loader type', function (done) @@ -188,7 +201,8 @@ "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} + "sourceSize": {"w":14,"h":14}, + "anchor": {"x":0.3,"y":0.4} }, "1.png": { @@ -262,6 +276,9 @@ "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, "sourceSize": {"w":14,"h":14} }}, + "animations": { + "png123": [ "1.png", "2.png", "3.png" ] + }, "meta": { "app": "http://www.texturepacker.com", "version": "1.0", diff --git a/packages/spritesheet/test/resources/building1.json b/packages/spritesheet/test/resources/building1.json old mode 100755 new mode 100644 index 03fa5c1..d8fbfe1 --- a/packages/spritesheet/test/resources/building1.json +++ b/packages/spritesheet/test/resources/building1.json @@ -6,16 +6,54 @@ "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":95,"h":115}, - "sourceSize": {"w":95,"h":115}, - "pivot": {"x":0.5,"y":0.5} + "sourceSize": {"w":95,"h":115} +}, +"star1.png": +{ + "frame": {"x":98,"y":33,"w":28,"h":29}, + "rotated": true, + "trimmed": true, + "spriteSourceSize": {"x":2,"y":2,"w":28,"h":29}, + "sourceSize": {"w":32,"h":32}, + "anchor": {"x":0.5,"y":0.5} +}, +"star2.png": +{ + "frame": {"x":98,"y":95,"w":26,"h":30}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":3,"y":1,"w":26,"h":30}, + "sourceSize": {"w":32,"h":32}, + "anchor": {"x":0.5,"y":0.5} +}, +"star3.png": +{ + "frame": {"x":98,"y":1,"w":30,"h":29}, + "rotated": true, + "trimmed": true, + "spriteSourceSize": {"x":1,"y":2,"w":30,"h":29}, + "sourceSize": {"w":32,"h":32}, + "anchor": {"x":0.5,"y":0.5} +}, +"star4.png": +{ + "frame": {"x":98,"y":63,"w":30,"h":27}, + "rotated": true, + "trimmed": true, + "spriteSourceSize": {"x":1,"y":3,"w":30,"h":27}, + "sourceSize": {"w":32,"h":32}, + "anchor": {"x":0.5,"y":0.5} }}, +"animations": { + "star": ["star1.png","star2.png","star3.png","star4.png"] +}, "meta": { - "app": "http://www.codeandweb.com/texturepacker", + "app": "https://www.codeandweb.com/texturepacker", "version": "1.0", "image": "building1.png", "format": "RGBA8888", - "size": {"w":128,"h":128}, + "size": {"w":128,"h":126}, "scale": "0.5", - "smartupdate": "$TexturePacker:SmartUpdate:d7a5e54c8f8a3fecd508baf190c44807:99a0e3a4dc0f441e4aad77614191ab38:6046b8eb706ddefaa771c33ceb7cb6d5$" + "smartupdate": "$TexturePacker:SmartUpdate:1cb0f14cbdd5e7bcecd332ecd0eaa9f7:8acde9d234ecca966a410602c71bffad:6046b8eb706ddefaa771c33ceb7cb6d5$" } } diff --git a/packages/spritesheet/test/resources/building1.png b/packages/spritesheet/test/resources/building1.png old mode 100755 new mode 100644 index 7e1e114..7e16cda --- a/packages/spritesheet/test/resources/building1.png +++ b/packages/spritesheet/test/resources/building1.png Binary files differ diff --git a/packages/spritesheet/test/resources/building1@2x.json b/packages/spritesheet/test/resources/building1@2x.json old mode 100755 new mode 100644 index 24e25ff..c8cb7bf --- a/packages/spritesheet/test/resources/building1@2x.json +++ b/packages/spritesheet/test/resources/building1@2x.json @@ -6,16 +6,54 @@ "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":190,"h":229}, - "sourceSize": {"w":190,"h":229}, - "pivot": {"x":0.5,"y":0.5} + "sourceSize": {"w":190,"h":229} +}, +"star1.png": +{ + "frame": {"x":193,"y":59,"w":54,"h":55}, + "rotated": true, + "trimmed": true, + "spriteSourceSize": {"x":5,"y":5,"w":54,"h":55}, + "sourceSize": {"w":64,"h":64}, + "anchor": {"x":0.5,"y":0.5} +}, +"star2.png": +{ + "frame": {"x":193,"y":115,"w":50,"h":57}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":7,"y":4,"w":50,"h":57}, + "sourceSize": {"w":64,"h":64}, + "anchor": {"x":0.5,"y":0.5} +}, +"star3.png": +{ + "frame": {"x":193,"y":1,"w":56,"h":55}, + "rotated": true, + "trimmed": true, + "spriteSourceSize": {"x":4,"y":5,"w":56,"h":55}, + "sourceSize": {"w":64,"h":64}, + "anchor": {"x":0.5,"y":0.5} +}, +"star4.png": +{ + "frame": {"x":193,"y":174,"w":56,"h":50}, + "rotated": true, + "trimmed": true, + "spriteSourceSize": {"x":4,"y":8,"w":56,"h":50}, + "sourceSize": {"w":64,"h":64}, + "anchor": {"x":0.5,"y":0.5} }}, +"animations": { + "star": ["star1.png","star2.png","star3.png","star4.png"] +}, "meta": { - "app": "http://www.codeandweb.com/texturepacker", + "app": "https://www.codeandweb.com/texturepacker", "version": "1.0", "image": "building1@2x.png", "format": "RGBA8888", - "size": {"w":256,"h":256}, + "size": {"w":249,"h":231}, "scale": "1", - "smartupdate": "$TexturePacker:SmartUpdate:d7a5e54c8f8a3fecd508baf190c44807:99a0e3a4dc0f441e4aad77614191ab38:6046b8eb706ddefaa771c33ceb7cb6d5$" + "smartupdate": "$TexturePacker:SmartUpdate:1cb0f14cbdd5e7bcecd332ecd0eaa9f7:8acde9d234ecca966a410602c71bffad:6046b8eb706ddefaa771c33ceb7cb6d5$" } } diff --git a/packages/spritesheet/test/resources/building1@2x.png b/packages/spritesheet/test/resources/building1@2x.png old mode 100755 new mode 100644 index d5ecd04..06e613f --- a/packages/spritesheet/test/resources/building1@2x.png +++ b/packages/spritesheet/test/resources/building1@2x.png Binary files differ diff --git a/test/core/resources/src/goldmine_10_5.png b/test/core/resources/src/goldmine_10_5.png new file mode 100755 index 0000000..de2ba5a --- /dev/null +++ b/test/core/resources/src/goldmine_10_5.png Binary files differ diff --git a/test/core/resources/src/sheet.tps b/test/core/resources/src/sheet.tps new file mode 100644 index 0000000..d08b10c --- /dev/null +++ b/test/core/resources/src/sheet.tps @@ -0,0 +1,262 @@ + + + + fileFormatVersion + 4 + texturePackerVersion + 4.8.1 + autoSDSettings + + + scale + 1 + extension + @2x + spriteFilter + + acceptFractionalValues + + maxTextureSize + + width + -1 + height + -1 + + + + scale + 0.5 + extension + + spriteFilter + + acceptFractionalValues + + maxTextureSize + + width + -1 + height + -1 + + + + allowRotation + + shapeDebug + + dpi + 72 + dataFormat + pixijs4 + textureFileName + + flipPVR + + pvrCompressionQuality + PVR_QUALITY_NORMAL + atfCompressData + + mipMapMinSize + 32768 + etc1CompressionQuality + ETC1_QUALITY_LOW_PERCEPTUAL + etc2CompressionQuality + ETC2_QUALITY_LOW_PERCEPTUAL + dxtCompressionMode + DXT_PERCEPTUAL + jxrColorFormat + JXR_YUV444 + jxrTrimFlexBits + 0 + jxrCompressionLevel + 0 + ditherType + NearestNeighbour + backgroundColor + 0 + libGdx + + filtering + + x + Linear + y + Linear + + + shapePadding + 0 + jpgQuality + 80 + pngOptimizationLevel + 1 + webpQualityLevel + 101 + textureSubPath + + atfFormats + + textureFormat + png + borderPadding + 0 + maxTextureSize + + width + 2048 + height + 2048 + + fixedTextureSize + + width + -1 + height + -1 + + algorithmSettings + + algorithm + MaxRects + freeSizeMode + Best + sizeConstraints + AnySize + forceSquared + + maxRects + + heuristic + Best + + basic + + sortBy + Best + order + Ascending + + polygon + + alignToGrid + 1 + + + dataFileNames + + data + + name + ../building1{v}.json + + + multiPack + + forceIdenticalLayout + + outputFormat + RGBA8888 + alphaHandling + ClearTransparentPixels + contentProtection + + key + + + autoAliasEnabled + + trimSpriteNames + + prependSmartFolderName + + autodetectAnimations + + globalSpriteSettings + + scale + 1 + scaleMode + Smooth + extrude + 1 + trimThreshold + 1 + trimMargin + 1 + trimMode + Trim + tracerTolerance + 200 + heuristicMask + + defaultPivotPoint + 0,0 + writePivotPoints + + + individualSpriteSettings + + goldmine_10_5.png + + pivotPoint + 0,0 + scale9Enabled + + scale9Borders + 48,57,95,115 + scale9Paddings + 48,57,95,115 + scale9FromFile + + + star1.png + star2.png + star3.png + star4.png + + pivotPoint + 0.5,0.5 + scale9Enabled + + scale9Borders + 16,16,32,32 + scale9Paddings + 16,16,32,32 + scale9FromFile + + + + fileList + + goldmine_10_5.png + star1.png + star2.png + star3.png + star4.png + + ignoreFileList + + replaceList + + ignoredWarnings + + commonDivisorX + 1 + commonDivisorY + 1 + packNormalMaps + + autodetectNormalMaps + + normalMapFilter + + normalMapSuffix + + normalMapSheetFileName + + exporterProperties + + + diff --git a/test/core/resources/src/star1.png b/test/core/resources/src/star1.png new file mode 100644 index 0000000..32824fd --- /dev/null +++ b/test/core/resources/src/star1.png Binary files differ diff --git a/test/core/resources/src/star2.png b/test/core/resources/src/star2.png new file mode 100644 index 0000000..8432cb5 --- /dev/null +++ b/test/core/resources/src/star2.png Binary files differ diff --git a/packages/core/src/textures/Texture.js b/packages/core/src/textures/Texture.js index 9148656..f9bb8bc 100644 --- a/packages/core/src/textures/Texture.js +++ b/packages/core/src/textures/Texture.js @@ -4,7 +4,7 @@ import TextureUvs from './TextureUvs'; import EventEmitter from 'eventemitter3'; import { settings } from '@pixi/settings'; -import { Rectangle } from '@pixi/math'; +import { Rectangle, Point } from '@pixi/math'; import { uid, TextureCache, getResolutionOfUrl } from '@pixi/utils'; /** @@ -41,8 +41,9 @@ * @param {PIXI.Rectangle} [orig] - The area of original texture * @param {PIXI.Rectangle} [trim] - Trimmed rectangle of original texture * @param {number} [rotate] - indicates how the texture was rotated by texture packer. See {@link PIXI.GroupD8} + * @param {PIXI.Point} [anchor] - Default anchor point used for sprite placement / rotation */ - constructor(baseTexture, frame, orig, trim, rotate) + constructor(baseTexture, frame, orig, trim, rotate, anchor) { super(); @@ -153,6 +154,14 @@ } /** + * Anchor point that is used as default if sprite is created with this texture. + * Changing the `defaultAnchor` at a later point of time will not update Sprite's anchor point. + * @member {PIXI.Point} + * @default {0,0} + */ + this.defaultAnchor = anchor ? new Point(anchor.x, anchor.y) : new Point(0, 0); + + /** * Update ID is observed by sprites and TextureMatrix instances. * Call updateUvs() to increment it. * diff --git a/packages/sprite-animated/src/AnimatedSprite.js b/packages/sprite-animated/src/AnimatedSprite.js index d5f5fce..330c683 100644 --- a/packages/sprite-animated/src/AnimatedSprite.js +++ b/packages/sprite-animated/src/AnimatedSprite.js @@ -22,7 +22,20 @@ * textureArray.push(texture); * }; * - * let mc = new PIXI.AnimatedSprite(textureArray); + * let animatedSprite = new PIXI.extras.AnimatedSprite(textureArray); + * ``` + * + * The more efficient and simpler way to create an animated sprite is using a {@link PIXI.Spritesheet} + * containing the animation definitions: + * + * ```js + * PIXI.loader.add("assets/spritesheet.json").load(setup); + * + * function setup() { + * let sheet = PIXI.loader.resources["assets/spritesheet.json"].spritesheet; + * animatedSprite = new PIXI.extras.AnimatedSprite(sheet.animations["image_sequence"]); + * ... + * } * ``` * * @class diff --git a/packages/sprite/src/Sprite.js b/packages/sprite/src/Sprite.js index ebf1f6f..91546cf 100644 --- a/packages/sprite/src/Sprite.js +++ b/packages/sprite/src/Sprite.js @@ -15,6 +15,18 @@ * let sprite = new PIXI.Sprite.from('assets/image.png'); * ``` * + * The more efficient way to create sprites is using a {@link PIXI.Spritesheet}: + * + * ```js + * PIXI.loader.add("assets/spritesheet.json").load(setup); + * + * function setup() { + * let sheet = PIXI.loader.resources["assets/spritesheet.json"].spritesheet; + * let sprite = new PIXI.Sprite(sheet.textures["image.png"]); + * ... + * } + * ``` + * * @class * @extends PIXI.Container * @memberof PIXI @@ -30,14 +42,22 @@ /** * The anchor sets the origin point of the texture. - * The default is 0,0 this means the texture's origin is the top left. + * The default is 0,0 or taken from the {@link PIXI.Texture#defaultAnchor|Texture} + * passed to the constructor. A value of 0,0 means the texture's origin is the top left. * Setting the anchor to 0.5,0.5 means the texture's origin is centered. * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner. + * Note: Updating the {@link PIXI.Texture#defaultAnchor} after a Texture is + * created does _not_ update the Sprite's anchor values. * * @member {PIXI.ObservablePoint} * @private */ - this._anchor = new ObservablePoint(this._onAnchorUpdate, this); + this._anchor = new ObservablePoint( + this._onAnchorUpdate, + this, + (texture ? texture.defaultAnchor.x : 0), + (texture ? texture.defaultAnchor.y : 0) + ); /** * The texture that the sprite is using @@ -484,7 +504,8 @@ } /** - * The anchor sets the origin point of the text. + * The anchor sets the origin point of the text. The default value is taken from the {@link PIXI.Texture|Texture} + * and passed to the constructor. * * The default is `(0,0)`, this means the text's origin is the top left. * diff --git a/packages/spritesheet/src/Spritesheet.js b/packages/spritesheet/src/Spritesheet.js index 64d932d..28597d7 100644 --- a/packages/spritesheet/src/Spritesheet.js +++ b/packages/spritesheet/src/Spritesheet.js @@ -6,6 +6,23 @@ * Utility class for maintaining reference to a collection * of Textures on a single Spritesheet. * + * To access a sprite sheet from your code pass its JSON data file to Pixi's loader: + * + * ```js + * PIXI.loader.add("images/spritesheet.json").load(setup); + * + * function setup() { + * let sheet = PIXI.loader.resources["images/spritesheet.json"].spritesheet; + * ... + * } + * ``` + * With the `sheet.textures` you can create Sprite objects,`sheet.animations` can be used to create an AnimatedSprite. + * + * Sprite sheets can be packed using tools like {@link https://codeandweb.com/texturepacker|TexturePacker}, + * {@link https://renderhjs.net/shoebox/|Shoebox} or {@link https://github.com/krzysztof-o/spritesheet.js|Spritesheet.js}. + * Default anchor points (see {@link PIXI.Texture#defaultAnchor}) and grouping of animation sprites are currently only + * supported by TexturePacker. + * * @class * @memberof PIXI */ @@ -38,12 +55,26 @@ this.baseTexture = baseTexture; /** - * Map of spritesheet textures. - * @type {Object} + * A map containing all textures of the sprite sheet. + * Can be used to create a {@link PIXI.Sprite|Sprite}: + * ```js + * new PIXI.Sprite(sheet.textures["image.png"]); + * ``` + * @member {Object} */ this.textures = {}; /** + * A map containing the textures for each animation. + * Can be used to create an {@link PIXI.extras.AnimatedSprite|AnimatedSprite}: + * ```js + * new PIXI.extras.AnimatedSprite(sheet.animations["anim_name"]) + * ``` + * @member {Object} + */ + this.animations = {}; + + /** * Reference to the original JSON data. * @type {Object} */ @@ -134,6 +165,7 @@ if (this._frameKeys.length <= Spritesheet.BATCH_SIZE) { this._processFrames(0); + this._processAnimations(); this._parseComplete(); } else @@ -208,7 +240,8 @@ frame, orig, trim, - data.rotated ? 2 : 0 + data.rotated ? 2 : 0, + data.anchor ); // lets also add the frame to pixi's global cache for fromFrame and fromImage functions @@ -220,6 +253,27 @@ } /** + * Parse animations config + * + * @private + */ + _processAnimations() + { + const animations = this.data.animations || {}; + + for (const animName in animations) + { + this.animations[animName] = []; + for (let i = 0; i < animations[animName].length; i++) + { + const frameName = animations[animName][i]; + + this.animations[animName].push(this.textures[frameName]); + } + } + } + + /** * The parse has completed. * * @private @@ -250,6 +304,7 @@ } else { + this._processAnimations(); this._parseComplete(); } }, 0); diff --git a/packages/spritesheet/test/Spritesheet.js b/packages/spritesheet/test/Spritesheet.js index b168fe0..5063a45 100644 --- a/packages/spritesheet/test/Spritesheet.js +++ b/packages/spritesheet/test/Spritesheet.js @@ -15,12 +15,20 @@ const width = Math.floor(spritesheet.data.frames[id].frame.w); const height = Math.floor(spritesheet.data.frames[id].frame.h); - expect(Object.keys(textures).length).to.equal(1); - expect(Object.keys(spritesheet.textures).length).to.equal(1); + expect(Object.keys(textures).length).to.equal(5); + expect(Object.keys(spritesheet.textures).length).to.equal(5); expect(textures[id]).to.be.an.instanceof(Texture); expect(textures[id].width).to.equal(width / spritesheet.resolution); expect(textures[id].height).to.equal(height / spritesheet.resolution); + expect(textures[id].defaultAnchor.x).to.equal(0); + expect(textures[id].defaultAnchor.y).to.equal(0); expect(textures[id].textureCacheIds.indexOf(id)).to.equal(0); + + expect(this.animations).to.have.property('star').that.is.an('array'); + expect(this.animations.star.length).to.equal(4); + expect(this.animations.star[0].defaultAnchor.x).to.equal(0.5); + expect(this.animations.star[0].defaultAnchor.y).to.equal(0.5); + spritesheet.destroy(true); expect(spritesheet.textures).to.be.null; expect(spritesheet.baseTexture).to.be.null; diff --git a/packages/spritesheet/test/SpritesheetLoader.js b/packages/spritesheet/test/SpritesheetLoader.js index 2797100..932064c 100644 --- a/packages/spritesheet/test/SpritesheetLoader.js +++ b/packages/spritesheet/test/SpritesheetLoader.js @@ -75,6 +75,19 @@ .with.keys(Object.keys(getJsonSpritesheet().frames)) .and.has.property('0.png') .that.is.an.instanceof(Texture); + + expect(res.textures['0.png'].frame.x).to.equal(14); + expect(res.textures['0.png'].frame.y).to.equal(28); + expect(res.textures['0.png'].defaultAnchor.x).to.equal(0.3); + expect(res.textures['0.png'].defaultAnchor.y).to.equal(0.4); + expect(res.textures['1.png'].defaultAnchor.x).to.equal(0.0); // default of defaultAnchor is 0,0 + expect(res.textures['1.png'].defaultAnchor.y).to.equal(0.0); + + expect(res).to.have.property('spritesheet') + .to.have.property('animations') + .to.have.property('png123'); + expect(res.spritesheet.animations.png123.length).to.equal(3); + expect(res.spritesheet.animations.png123[0]).to.equal(res.textures['1.png']); }); it('should not load binary images as an image loader type', function (done) @@ -188,7 +201,8 @@ "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} + "sourceSize": {"w":14,"h":14}, + "anchor": {"x":0.3,"y":0.4} }, "1.png": { @@ -262,6 +276,9 @@ "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, "sourceSize": {"w":14,"h":14} }}, + "animations": { + "png123": [ "1.png", "2.png", "3.png" ] + }, "meta": { "app": "http://www.texturepacker.com", "version": "1.0", diff --git a/packages/spritesheet/test/resources/building1.json b/packages/spritesheet/test/resources/building1.json old mode 100755 new mode 100644 index 03fa5c1..d8fbfe1 --- a/packages/spritesheet/test/resources/building1.json +++ b/packages/spritesheet/test/resources/building1.json @@ -6,16 +6,54 @@ "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":95,"h":115}, - "sourceSize": {"w":95,"h":115}, - "pivot": {"x":0.5,"y":0.5} + "sourceSize": {"w":95,"h":115} +}, +"star1.png": +{ + "frame": {"x":98,"y":33,"w":28,"h":29}, + "rotated": true, + "trimmed": true, + "spriteSourceSize": {"x":2,"y":2,"w":28,"h":29}, + "sourceSize": {"w":32,"h":32}, + "anchor": {"x":0.5,"y":0.5} +}, +"star2.png": +{ + "frame": {"x":98,"y":95,"w":26,"h":30}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":3,"y":1,"w":26,"h":30}, + "sourceSize": {"w":32,"h":32}, + "anchor": {"x":0.5,"y":0.5} +}, +"star3.png": +{ + "frame": {"x":98,"y":1,"w":30,"h":29}, + "rotated": true, + "trimmed": true, + "spriteSourceSize": {"x":1,"y":2,"w":30,"h":29}, + "sourceSize": {"w":32,"h":32}, + "anchor": {"x":0.5,"y":0.5} +}, +"star4.png": +{ + "frame": {"x":98,"y":63,"w":30,"h":27}, + "rotated": true, + "trimmed": true, + "spriteSourceSize": {"x":1,"y":3,"w":30,"h":27}, + "sourceSize": {"w":32,"h":32}, + "anchor": {"x":0.5,"y":0.5} }}, +"animations": { + "star": ["star1.png","star2.png","star3.png","star4.png"] +}, "meta": { - "app": "http://www.codeandweb.com/texturepacker", + "app": "https://www.codeandweb.com/texturepacker", "version": "1.0", "image": "building1.png", "format": "RGBA8888", - "size": {"w":128,"h":128}, + "size": {"w":128,"h":126}, "scale": "0.5", - "smartupdate": "$TexturePacker:SmartUpdate:d7a5e54c8f8a3fecd508baf190c44807:99a0e3a4dc0f441e4aad77614191ab38:6046b8eb706ddefaa771c33ceb7cb6d5$" + "smartupdate": "$TexturePacker:SmartUpdate:1cb0f14cbdd5e7bcecd332ecd0eaa9f7:8acde9d234ecca966a410602c71bffad:6046b8eb706ddefaa771c33ceb7cb6d5$" } } diff --git a/packages/spritesheet/test/resources/building1.png b/packages/spritesheet/test/resources/building1.png old mode 100755 new mode 100644 index 7e1e114..7e16cda --- a/packages/spritesheet/test/resources/building1.png +++ b/packages/spritesheet/test/resources/building1.png Binary files differ diff --git a/packages/spritesheet/test/resources/building1@2x.json b/packages/spritesheet/test/resources/building1@2x.json old mode 100755 new mode 100644 index 24e25ff..c8cb7bf --- a/packages/spritesheet/test/resources/building1@2x.json +++ b/packages/spritesheet/test/resources/building1@2x.json @@ -6,16 +6,54 @@ "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":190,"h":229}, - "sourceSize": {"w":190,"h":229}, - "pivot": {"x":0.5,"y":0.5} + "sourceSize": {"w":190,"h":229} +}, +"star1.png": +{ + "frame": {"x":193,"y":59,"w":54,"h":55}, + "rotated": true, + "trimmed": true, + "spriteSourceSize": {"x":5,"y":5,"w":54,"h":55}, + "sourceSize": {"w":64,"h":64}, + "anchor": {"x":0.5,"y":0.5} +}, +"star2.png": +{ + "frame": {"x":193,"y":115,"w":50,"h":57}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":7,"y":4,"w":50,"h":57}, + "sourceSize": {"w":64,"h":64}, + "anchor": {"x":0.5,"y":0.5} +}, +"star3.png": +{ + "frame": {"x":193,"y":1,"w":56,"h":55}, + "rotated": true, + "trimmed": true, + "spriteSourceSize": {"x":4,"y":5,"w":56,"h":55}, + "sourceSize": {"w":64,"h":64}, + "anchor": {"x":0.5,"y":0.5} +}, +"star4.png": +{ + "frame": {"x":193,"y":174,"w":56,"h":50}, + "rotated": true, + "trimmed": true, + "spriteSourceSize": {"x":4,"y":8,"w":56,"h":50}, + "sourceSize": {"w":64,"h":64}, + "anchor": {"x":0.5,"y":0.5} }}, +"animations": { + "star": ["star1.png","star2.png","star3.png","star4.png"] +}, "meta": { - "app": "http://www.codeandweb.com/texturepacker", + "app": "https://www.codeandweb.com/texturepacker", "version": "1.0", "image": "building1@2x.png", "format": "RGBA8888", - "size": {"w":256,"h":256}, + "size": {"w":249,"h":231}, "scale": "1", - "smartupdate": "$TexturePacker:SmartUpdate:d7a5e54c8f8a3fecd508baf190c44807:99a0e3a4dc0f441e4aad77614191ab38:6046b8eb706ddefaa771c33ceb7cb6d5$" + "smartupdate": "$TexturePacker:SmartUpdate:1cb0f14cbdd5e7bcecd332ecd0eaa9f7:8acde9d234ecca966a410602c71bffad:6046b8eb706ddefaa771c33ceb7cb6d5$" } } diff --git a/packages/spritesheet/test/resources/building1@2x.png b/packages/spritesheet/test/resources/building1@2x.png old mode 100755 new mode 100644 index d5ecd04..06e613f --- a/packages/spritesheet/test/resources/building1@2x.png +++ b/packages/spritesheet/test/resources/building1@2x.png Binary files differ diff --git a/test/core/resources/src/goldmine_10_5.png b/test/core/resources/src/goldmine_10_5.png new file mode 100755 index 0000000..de2ba5a --- /dev/null +++ b/test/core/resources/src/goldmine_10_5.png Binary files differ diff --git a/test/core/resources/src/sheet.tps b/test/core/resources/src/sheet.tps new file mode 100644 index 0000000..d08b10c --- /dev/null +++ b/test/core/resources/src/sheet.tps @@ -0,0 +1,262 @@ + + + + fileFormatVersion + 4 + texturePackerVersion + 4.8.1 + autoSDSettings + + + scale + 1 + extension + @2x + spriteFilter + + acceptFractionalValues + + maxTextureSize + + width + -1 + height + -1 + + + + scale + 0.5 + extension + + spriteFilter + + acceptFractionalValues + + maxTextureSize + + width + -1 + height + -1 + + + + allowRotation + + shapeDebug + + dpi + 72 + dataFormat + pixijs4 + textureFileName + + flipPVR + + pvrCompressionQuality + PVR_QUALITY_NORMAL + atfCompressData + + mipMapMinSize + 32768 + etc1CompressionQuality + ETC1_QUALITY_LOW_PERCEPTUAL + etc2CompressionQuality + ETC2_QUALITY_LOW_PERCEPTUAL + dxtCompressionMode + DXT_PERCEPTUAL + jxrColorFormat + JXR_YUV444 + jxrTrimFlexBits + 0 + jxrCompressionLevel + 0 + ditherType + NearestNeighbour + backgroundColor + 0 + libGdx + + filtering + + x + Linear + y + Linear + + + shapePadding + 0 + jpgQuality + 80 + pngOptimizationLevel + 1 + webpQualityLevel + 101 + textureSubPath + + atfFormats + + textureFormat + png + borderPadding + 0 + maxTextureSize + + width + 2048 + height + 2048 + + fixedTextureSize + + width + -1 + height + -1 + + algorithmSettings + + algorithm + MaxRects + freeSizeMode + Best + sizeConstraints + AnySize + forceSquared + + maxRects + + heuristic + Best + + basic + + sortBy + Best + order + Ascending + + polygon + + alignToGrid + 1 + + + dataFileNames + + data + + name + ../building1{v}.json + + + multiPack + + forceIdenticalLayout + + outputFormat + RGBA8888 + alphaHandling + ClearTransparentPixels + contentProtection + + key + + + autoAliasEnabled + + trimSpriteNames + + prependSmartFolderName + + autodetectAnimations + + globalSpriteSettings + + scale + 1 + scaleMode + Smooth + extrude + 1 + trimThreshold + 1 + trimMargin + 1 + trimMode + Trim + tracerTolerance + 200 + heuristicMask + + defaultPivotPoint + 0,0 + writePivotPoints + + + individualSpriteSettings + + goldmine_10_5.png + + pivotPoint + 0,0 + scale9Enabled + + scale9Borders + 48,57,95,115 + scale9Paddings + 48,57,95,115 + scale9FromFile + + + star1.png + star2.png + star3.png + star4.png + + pivotPoint + 0.5,0.5 + scale9Enabled + + scale9Borders + 16,16,32,32 + scale9Paddings + 16,16,32,32 + scale9FromFile + + + + fileList + + goldmine_10_5.png + star1.png + star2.png + star3.png + star4.png + + ignoreFileList + + replaceList + + ignoredWarnings + + commonDivisorX + 1 + commonDivisorY + 1 + packNormalMaps + + autodetectNormalMaps + + normalMapFilter + + normalMapSuffix + + normalMapSheetFileName + + exporterProperties + + + diff --git a/test/core/resources/src/star1.png b/test/core/resources/src/star1.png new file mode 100644 index 0000000..32824fd --- /dev/null +++ b/test/core/resources/src/star1.png Binary files differ diff --git a/test/core/resources/src/star2.png b/test/core/resources/src/star2.png new file mode 100644 index 0000000..8432cb5 --- /dev/null +++ b/test/core/resources/src/star2.png Binary files differ diff --git a/test/core/resources/src/star3.png b/test/core/resources/src/star3.png new file mode 100644 index 0000000..a91eadc --- /dev/null +++ b/test/core/resources/src/star3.png Binary files differ diff --git a/packages/core/src/textures/Texture.js b/packages/core/src/textures/Texture.js index 9148656..f9bb8bc 100644 --- a/packages/core/src/textures/Texture.js +++ b/packages/core/src/textures/Texture.js @@ -4,7 +4,7 @@ import TextureUvs from './TextureUvs'; import EventEmitter from 'eventemitter3'; import { settings } from '@pixi/settings'; -import { Rectangle } from '@pixi/math'; +import { Rectangle, Point } from '@pixi/math'; import { uid, TextureCache, getResolutionOfUrl } from '@pixi/utils'; /** @@ -41,8 +41,9 @@ * @param {PIXI.Rectangle} [orig] - The area of original texture * @param {PIXI.Rectangle} [trim] - Trimmed rectangle of original texture * @param {number} [rotate] - indicates how the texture was rotated by texture packer. See {@link PIXI.GroupD8} + * @param {PIXI.Point} [anchor] - Default anchor point used for sprite placement / rotation */ - constructor(baseTexture, frame, orig, trim, rotate) + constructor(baseTexture, frame, orig, trim, rotate, anchor) { super(); @@ -153,6 +154,14 @@ } /** + * Anchor point that is used as default if sprite is created with this texture. + * Changing the `defaultAnchor` at a later point of time will not update Sprite's anchor point. + * @member {PIXI.Point} + * @default {0,0} + */ + this.defaultAnchor = anchor ? new Point(anchor.x, anchor.y) : new Point(0, 0); + + /** * Update ID is observed by sprites and TextureMatrix instances. * Call updateUvs() to increment it. * diff --git a/packages/sprite-animated/src/AnimatedSprite.js b/packages/sprite-animated/src/AnimatedSprite.js index d5f5fce..330c683 100644 --- a/packages/sprite-animated/src/AnimatedSprite.js +++ b/packages/sprite-animated/src/AnimatedSprite.js @@ -22,7 +22,20 @@ * textureArray.push(texture); * }; * - * let mc = new PIXI.AnimatedSprite(textureArray); + * let animatedSprite = new PIXI.extras.AnimatedSprite(textureArray); + * ``` + * + * The more efficient and simpler way to create an animated sprite is using a {@link PIXI.Spritesheet} + * containing the animation definitions: + * + * ```js + * PIXI.loader.add("assets/spritesheet.json").load(setup); + * + * function setup() { + * let sheet = PIXI.loader.resources["assets/spritesheet.json"].spritesheet; + * animatedSprite = new PIXI.extras.AnimatedSprite(sheet.animations["image_sequence"]); + * ... + * } * ``` * * @class diff --git a/packages/sprite/src/Sprite.js b/packages/sprite/src/Sprite.js index ebf1f6f..91546cf 100644 --- a/packages/sprite/src/Sprite.js +++ b/packages/sprite/src/Sprite.js @@ -15,6 +15,18 @@ * let sprite = new PIXI.Sprite.from('assets/image.png'); * ``` * + * The more efficient way to create sprites is using a {@link PIXI.Spritesheet}: + * + * ```js + * PIXI.loader.add("assets/spritesheet.json").load(setup); + * + * function setup() { + * let sheet = PIXI.loader.resources["assets/spritesheet.json"].spritesheet; + * let sprite = new PIXI.Sprite(sheet.textures["image.png"]); + * ... + * } + * ``` + * * @class * @extends PIXI.Container * @memberof PIXI @@ -30,14 +42,22 @@ /** * The anchor sets the origin point of the texture. - * The default is 0,0 this means the texture's origin is the top left. + * The default is 0,0 or taken from the {@link PIXI.Texture#defaultAnchor|Texture} + * passed to the constructor. A value of 0,0 means the texture's origin is the top left. * Setting the anchor to 0.5,0.5 means the texture's origin is centered. * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner. + * Note: Updating the {@link PIXI.Texture#defaultAnchor} after a Texture is + * created does _not_ update the Sprite's anchor values. * * @member {PIXI.ObservablePoint} * @private */ - this._anchor = new ObservablePoint(this._onAnchorUpdate, this); + this._anchor = new ObservablePoint( + this._onAnchorUpdate, + this, + (texture ? texture.defaultAnchor.x : 0), + (texture ? texture.defaultAnchor.y : 0) + ); /** * The texture that the sprite is using @@ -484,7 +504,8 @@ } /** - * The anchor sets the origin point of the text. + * The anchor sets the origin point of the text. The default value is taken from the {@link PIXI.Texture|Texture} + * and passed to the constructor. * * The default is `(0,0)`, this means the text's origin is the top left. * diff --git a/packages/spritesheet/src/Spritesheet.js b/packages/spritesheet/src/Spritesheet.js index 64d932d..28597d7 100644 --- a/packages/spritesheet/src/Spritesheet.js +++ b/packages/spritesheet/src/Spritesheet.js @@ -6,6 +6,23 @@ * Utility class for maintaining reference to a collection * of Textures on a single Spritesheet. * + * To access a sprite sheet from your code pass its JSON data file to Pixi's loader: + * + * ```js + * PIXI.loader.add("images/spritesheet.json").load(setup); + * + * function setup() { + * let sheet = PIXI.loader.resources["images/spritesheet.json"].spritesheet; + * ... + * } + * ``` + * With the `sheet.textures` you can create Sprite objects,`sheet.animations` can be used to create an AnimatedSprite. + * + * Sprite sheets can be packed using tools like {@link https://codeandweb.com/texturepacker|TexturePacker}, + * {@link https://renderhjs.net/shoebox/|Shoebox} or {@link https://github.com/krzysztof-o/spritesheet.js|Spritesheet.js}. + * Default anchor points (see {@link PIXI.Texture#defaultAnchor}) and grouping of animation sprites are currently only + * supported by TexturePacker. + * * @class * @memberof PIXI */ @@ -38,12 +55,26 @@ this.baseTexture = baseTexture; /** - * Map of spritesheet textures. - * @type {Object} + * A map containing all textures of the sprite sheet. + * Can be used to create a {@link PIXI.Sprite|Sprite}: + * ```js + * new PIXI.Sprite(sheet.textures["image.png"]); + * ``` + * @member {Object} */ this.textures = {}; /** + * A map containing the textures for each animation. + * Can be used to create an {@link PIXI.extras.AnimatedSprite|AnimatedSprite}: + * ```js + * new PIXI.extras.AnimatedSprite(sheet.animations["anim_name"]) + * ``` + * @member {Object} + */ + this.animations = {}; + + /** * Reference to the original JSON data. * @type {Object} */ @@ -134,6 +165,7 @@ if (this._frameKeys.length <= Spritesheet.BATCH_SIZE) { this._processFrames(0); + this._processAnimations(); this._parseComplete(); } else @@ -208,7 +240,8 @@ frame, orig, trim, - data.rotated ? 2 : 0 + data.rotated ? 2 : 0, + data.anchor ); // lets also add the frame to pixi's global cache for fromFrame and fromImage functions @@ -220,6 +253,27 @@ } /** + * Parse animations config + * + * @private + */ + _processAnimations() + { + const animations = this.data.animations || {}; + + for (const animName in animations) + { + this.animations[animName] = []; + for (let i = 0; i < animations[animName].length; i++) + { + const frameName = animations[animName][i]; + + this.animations[animName].push(this.textures[frameName]); + } + } + } + + /** * The parse has completed. * * @private @@ -250,6 +304,7 @@ } else { + this._processAnimations(); this._parseComplete(); } }, 0); diff --git a/packages/spritesheet/test/Spritesheet.js b/packages/spritesheet/test/Spritesheet.js index b168fe0..5063a45 100644 --- a/packages/spritesheet/test/Spritesheet.js +++ b/packages/spritesheet/test/Spritesheet.js @@ -15,12 +15,20 @@ const width = Math.floor(spritesheet.data.frames[id].frame.w); const height = Math.floor(spritesheet.data.frames[id].frame.h); - expect(Object.keys(textures).length).to.equal(1); - expect(Object.keys(spritesheet.textures).length).to.equal(1); + expect(Object.keys(textures).length).to.equal(5); + expect(Object.keys(spritesheet.textures).length).to.equal(5); expect(textures[id]).to.be.an.instanceof(Texture); expect(textures[id].width).to.equal(width / spritesheet.resolution); expect(textures[id].height).to.equal(height / spritesheet.resolution); + expect(textures[id].defaultAnchor.x).to.equal(0); + expect(textures[id].defaultAnchor.y).to.equal(0); expect(textures[id].textureCacheIds.indexOf(id)).to.equal(0); + + expect(this.animations).to.have.property('star').that.is.an('array'); + expect(this.animations.star.length).to.equal(4); + expect(this.animations.star[0].defaultAnchor.x).to.equal(0.5); + expect(this.animations.star[0].defaultAnchor.y).to.equal(0.5); + spritesheet.destroy(true); expect(spritesheet.textures).to.be.null; expect(spritesheet.baseTexture).to.be.null; diff --git a/packages/spritesheet/test/SpritesheetLoader.js b/packages/spritesheet/test/SpritesheetLoader.js index 2797100..932064c 100644 --- a/packages/spritesheet/test/SpritesheetLoader.js +++ b/packages/spritesheet/test/SpritesheetLoader.js @@ -75,6 +75,19 @@ .with.keys(Object.keys(getJsonSpritesheet().frames)) .and.has.property('0.png') .that.is.an.instanceof(Texture); + + expect(res.textures['0.png'].frame.x).to.equal(14); + expect(res.textures['0.png'].frame.y).to.equal(28); + expect(res.textures['0.png'].defaultAnchor.x).to.equal(0.3); + expect(res.textures['0.png'].defaultAnchor.y).to.equal(0.4); + expect(res.textures['1.png'].defaultAnchor.x).to.equal(0.0); // default of defaultAnchor is 0,0 + expect(res.textures['1.png'].defaultAnchor.y).to.equal(0.0); + + expect(res).to.have.property('spritesheet') + .to.have.property('animations') + .to.have.property('png123'); + expect(res.spritesheet.animations.png123.length).to.equal(3); + expect(res.spritesheet.animations.png123[0]).to.equal(res.textures['1.png']); }); it('should not load binary images as an image loader type', function (done) @@ -188,7 +201,8 @@ "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, - "sourceSize": {"w":14,"h":14} + "sourceSize": {"w":14,"h":14}, + "anchor": {"x":0.3,"y":0.4} }, "1.png": { @@ -262,6 +276,9 @@ "spriteSourceSize": {"x":0,"y":0,"w":14,"h":14}, "sourceSize": {"w":14,"h":14} }}, + "animations": { + "png123": [ "1.png", "2.png", "3.png" ] + }, "meta": { "app": "http://www.texturepacker.com", "version": "1.0", diff --git a/packages/spritesheet/test/resources/building1.json b/packages/spritesheet/test/resources/building1.json old mode 100755 new mode 100644 index 03fa5c1..d8fbfe1 --- a/packages/spritesheet/test/resources/building1.json +++ b/packages/spritesheet/test/resources/building1.json @@ -6,16 +6,54 @@ "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":95,"h":115}, - "sourceSize": {"w":95,"h":115}, - "pivot": {"x":0.5,"y":0.5} + "sourceSize": {"w":95,"h":115} +}, +"star1.png": +{ + "frame": {"x":98,"y":33,"w":28,"h":29}, + "rotated": true, + "trimmed": true, + "spriteSourceSize": {"x":2,"y":2,"w":28,"h":29}, + "sourceSize": {"w":32,"h":32}, + "anchor": {"x":0.5,"y":0.5} +}, +"star2.png": +{ + "frame": {"x":98,"y":95,"w":26,"h":30}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":3,"y":1,"w":26,"h":30}, + "sourceSize": {"w":32,"h":32}, + "anchor": {"x":0.5,"y":0.5} +}, +"star3.png": +{ + "frame": {"x":98,"y":1,"w":30,"h":29}, + "rotated": true, + "trimmed": true, + "spriteSourceSize": {"x":1,"y":2,"w":30,"h":29}, + "sourceSize": {"w":32,"h":32}, + "anchor": {"x":0.5,"y":0.5} +}, +"star4.png": +{ + "frame": {"x":98,"y":63,"w":30,"h":27}, + "rotated": true, + "trimmed": true, + "spriteSourceSize": {"x":1,"y":3,"w":30,"h":27}, + "sourceSize": {"w":32,"h":32}, + "anchor": {"x":0.5,"y":0.5} }}, +"animations": { + "star": ["star1.png","star2.png","star3.png","star4.png"] +}, "meta": { - "app": "http://www.codeandweb.com/texturepacker", + "app": "https://www.codeandweb.com/texturepacker", "version": "1.0", "image": "building1.png", "format": "RGBA8888", - "size": {"w":128,"h":128}, + "size": {"w":128,"h":126}, "scale": "0.5", - "smartupdate": "$TexturePacker:SmartUpdate:d7a5e54c8f8a3fecd508baf190c44807:99a0e3a4dc0f441e4aad77614191ab38:6046b8eb706ddefaa771c33ceb7cb6d5$" + "smartupdate": "$TexturePacker:SmartUpdate:1cb0f14cbdd5e7bcecd332ecd0eaa9f7:8acde9d234ecca966a410602c71bffad:6046b8eb706ddefaa771c33ceb7cb6d5$" } } diff --git a/packages/spritesheet/test/resources/building1.png b/packages/spritesheet/test/resources/building1.png old mode 100755 new mode 100644 index 7e1e114..7e16cda --- a/packages/spritesheet/test/resources/building1.png +++ b/packages/spritesheet/test/resources/building1.png Binary files differ diff --git a/packages/spritesheet/test/resources/building1@2x.json b/packages/spritesheet/test/resources/building1@2x.json old mode 100755 new mode 100644 index 24e25ff..c8cb7bf --- a/packages/spritesheet/test/resources/building1@2x.json +++ b/packages/spritesheet/test/resources/building1@2x.json @@ -6,16 +6,54 @@ "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":190,"h":229}, - "sourceSize": {"w":190,"h":229}, - "pivot": {"x":0.5,"y":0.5} + "sourceSize": {"w":190,"h":229} +}, +"star1.png": +{ + "frame": {"x":193,"y":59,"w":54,"h":55}, + "rotated": true, + "trimmed": true, + "spriteSourceSize": {"x":5,"y":5,"w":54,"h":55}, + "sourceSize": {"w":64,"h":64}, + "anchor": {"x":0.5,"y":0.5} +}, +"star2.png": +{ + "frame": {"x":193,"y":115,"w":50,"h":57}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":7,"y":4,"w":50,"h":57}, + "sourceSize": {"w":64,"h":64}, + "anchor": {"x":0.5,"y":0.5} +}, +"star3.png": +{ + "frame": {"x":193,"y":1,"w":56,"h":55}, + "rotated": true, + "trimmed": true, + "spriteSourceSize": {"x":4,"y":5,"w":56,"h":55}, + "sourceSize": {"w":64,"h":64}, + "anchor": {"x":0.5,"y":0.5} +}, +"star4.png": +{ + "frame": {"x":193,"y":174,"w":56,"h":50}, + "rotated": true, + "trimmed": true, + "spriteSourceSize": {"x":4,"y":8,"w":56,"h":50}, + "sourceSize": {"w":64,"h":64}, + "anchor": {"x":0.5,"y":0.5} }}, +"animations": { + "star": ["star1.png","star2.png","star3.png","star4.png"] +}, "meta": { - "app": "http://www.codeandweb.com/texturepacker", + "app": "https://www.codeandweb.com/texturepacker", "version": "1.0", "image": "building1@2x.png", "format": "RGBA8888", - "size": {"w":256,"h":256}, + "size": {"w":249,"h":231}, "scale": "1", - "smartupdate": "$TexturePacker:SmartUpdate:d7a5e54c8f8a3fecd508baf190c44807:99a0e3a4dc0f441e4aad77614191ab38:6046b8eb706ddefaa771c33ceb7cb6d5$" + "smartupdate": "$TexturePacker:SmartUpdate:1cb0f14cbdd5e7bcecd332ecd0eaa9f7:8acde9d234ecca966a410602c71bffad:6046b8eb706ddefaa771c33ceb7cb6d5$" } } diff --git a/packages/spritesheet/test/resources/building1@2x.png b/packages/spritesheet/test/resources/building1@2x.png old mode 100755 new mode 100644 index d5ecd04..06e613f --- a/packages/spritesheet/test/resources/building1@2x.png +++ b/packages/spritesheet/test/resources/building1@2x.png Binary files differ diff --git a/test/core/resources/src/goldmine_10_5.png b/test/core/resources/src/goldmine_10_5.png new file mode 100755 index 0000000..de2ba5a --- /dev/null +++ b/test/core/resources/src/goldmine_10_5.png Binary files differ diff --git a/test/core/resources/src/sheet.tps b/test/core/resources/src/sheet.tps new file mode 100644 index 0000000..d08b10c --- /dev/null +++ b/test/core/resources/src/sheet.tps @@ -0,0 +1,262 @@ + + + + fileFormatVersion + 4 + texturePackerVersion + 4.8.1 + autoSDSettings + + + scale + 1 + extension + @2x + spriteFilter + + acceptFractionalValues + + maxTextureSize + + width + -1 + height + -1 + + + + scale + 0.5 + extension + + spriteFilter + + acceptFractionalValues + + maxTextureSize + + width + -1 + height + -1 + + + + allowRotation + + shapeDebug + + dpi + 72 + dataFormat + pixijs4 + textureFileName + + flipPVR + + pvrCompressionQuality + PVR_QUALITY_NORMAL + atfCompressData + + mipMapMinSize + 32768 + etc1CompressionQuality + ETC1_QUALITY_LOW_PERCEPTUAL + etc2CompressionQuality + ETC2_QUALITY_LOW_PERCEPTUAL + dxtCompressionMode + DXT_PERCEPTUAL + jxrColorFormat + JXR_YUV444 + jxrTrimFlexBits + 0 + jxrCompressionLevel + 0 + ditherType + NearestNeighbour + backgroundColor + 0 + libGdx + + filtering + + x + Linear + y + Linear + + + shapePadding + 0 + jpgQuality + 80 + pngOptimizationLevel + 1 + webpQualityLevel + 101 + textureSubPath + + atfFormats + + textureFormat + png + borderPadding + 0 + maxTextureSize + + width + 2048 + height + 2048 + + fixedTextureSize + + width + -1 + height + -1 + + algorithmSettings + + algorithm + MaxRects + freeSizeMode + Best + sizeConstraints + AnySize + forceSquared + + maxRects + + heuristic + Best + + basic + + sortBy + Best + order + Ascending + + polygon + + alignToGrid + 1 + + + dataFileNames + + data + + name + ../building1{v}.json + + + multiPack + + forceIdenticalLayout + + outputFormat + RGBA8888 + alphaHandling + ClearTransparentPixels + contentProtection + + key + + + autoAliasEnabled + + trimSpriteNames + + prependSmartFolderName + + autodetectAnimations + + globalSpriteSettings + + scale + 1 + scaleMode + Smooth + extrude + 1 + trimThreshold + 1 + trimMargin + 1 + trimMode + Trim + tracerTolerance + 200 + heuristicMask + + defaultPivotPoint + 0,0 + writePivotPoints + + + individualSpriteSettings + + goldmine_10_5.png + + pivotPoint + 0,0 + scale9Enabled + + scale9Borders + 48,57,95,115 + scale9Paddings + 48,57,95,115 + scale9FromFile + + + star1.png + star2.png + star3.png + star4.png + + pivotPoint + 0.5,0.5 + scale9Enabled + + scale9Borders + 16,16,32,32 + scale9Paddings + 16,16,32,32 + scale9FromFile + + + + fileList + + goldmine_10_5.png + star1.png + star2.png + star3.png + star4.png + + ignoreFileList + + replaceList + + ignoredWarnings + + commonDivisorX + 1 + commonDivisorY + 1 + packNormalMaps + + autodetectNormalMaps + + normalMapFilter + + normalMapSuffix + + normalMapSheetFileName + + exporterProperties + + + diff --git a/test/core/resources/src/star1.png b/test/core/resources/src/star1.png new file mode 100644 index 0000000..32824fd --- /dev/null +++ b/test/core/resources/src/star1.png Binary files differ diff --git a/test/core/resources/src/star2.png b/test/core/resources/src/star2.png new file mode 100644 index 0000000..8432cb5 --- /dev/null +++ b/test/core/resources/src/star2.png Binary files differ diff --git a/test/core/resources/src/star3.png b/test/core/resources/src/star3.png new file mode 100644 index 0000000..a91eadc --- /dev/null +++ b/test/core/resources/src/star3.png Binary files differ diff --git a/test/core/resources/src/star4.png b/test/core/resources/src/star4.png new file mode 100644 index 0000000..c629571 --- /dev/null +++ b/test/core/resources/src/star4.png Binary files differ