diff --git a/CHANGELOG.md b/CHANGELOG.md index 85569b6e5..dca3f247f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -83,6 +83,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Added +- Added `ex.SpriteSheet.getTiledSprite(...)` to help pulling tiling sprites out of a sprite sheet - Alias the `engine.screen.drawWidth/drawHeight` with `engine.screen.width/height`; - Added convenience types `ex.TiledSprite` and `ex.TiledAnimation` for Tiling Sprites and Animations ```typescript diff --git a/sandbox/tests/tiling/index.ts b/sandbox/tests/tiling/index.ts index 4d3542122..1d98e7124 100644 --- a/sandbox/tests/tiling/index.ts +++ b/sandbox/tests/tiling/index.ts @@ -22,6 +22,13 @@ var cardSpriteSheet = ex.SpriteSheet.fromImageSource({ }); cardSpriteSheet.sprites.forEach((s) => (s.scale = ex.vec(2, 2))); + +var tilingCard = cardSpriteSheet.getTiledSprite(0, 0, { + width: 300, + height: 300, + scale: ex.vec(2, 2) +}); + var cardAnimation = ex.Animation.fromSpriteSheet(cardSpriteSheet, ex.range(0, 14 * 4), 200); var groundImage = new ex.ImageSource('./ground.png'); @@ -47,9 +54,17 @@ var tiledGroundSprite = ex.TiledSprite.fromSprite(groundSprite, { } }); +var tiledCardFromCtor = new ex.TiledSprite({ + image: cards, + sourceView: { x: 11, y: 2, width: 42, height: 60 }, + scale: ex.vec(0.5, 0.5), + width: 200, + height: 200 +}); + var tilingAnimation = new ex.TiledAnimation({ animation: cardAnimation, - sourceView: { x: 20, y: 20 }, + // sourceView: { x: 20, y: 20 }, width: 200, height: 200, wrapping: ex.ImageWrapping.Repeat @@ -58,6 +73,18 @@ var tilingAnimation = new ex.TiledAnimation({ // tilingAnimation.sourceView = {x: 0, y: 0}; game.start(loader).then(() => { + var otherCardActor = new ex.Actor({ + pos: ex.vec(200, 200) + }); + otherCardActor.graphics.use(tilingCard); + game.add(otherCardActor); + + var otherOtherCardActor = new ex.Actor({ + pos: ex.vec(600, 200) + }); + otherOtherCardActor.graphics.use(tiledCardFromCtor); + game.add(otherOtherCardActor); + var cardActor = new ex.Actor({ pos: ex.vec(400, 400) }); diff --git a/src/engine/Graphics/SpriteSheet.ts b/src/engine/Graphics/SpriteSheet.ts index a28410e0f..59946bc8d 100644 --- a/src/engine/Graphics/SpriteSheet.ts +++ b/src/engine/Graphics/SpriteSheet.ts @@ -1,6 +1,7 @@ import { ImageSource } from './ImageSource'; import { SourceView, Sprite } from './Sprite'; import { GraphicOptions } from './Graphic'; +import { TiledSprite, TiledSpriteOptions } from './TiledSprite'; /** * Specify sprite sheet spacing options, useful if your sprites are not tightly packed @@ -112,10 +113,10 @@ export class SpriteSheet { */ public getSprite(x: number, y: number, options?: GetSpriteOptions): Sprite { if (x >= this.columns || x < 0) { - throw Error(`No sprite exists in the SpriteSheet at (${x}, ${y}), x: ${x} should be between 0 and ${this.columns - 1}`); + throw Error(`No sprite exists in the SpriteSheet at (${x}, ${y}), x: ${x} should be between 0 and ${this.columns - 1} columns`); } if (y >= this.rows || y < 0) { - throw Error(`No sprite exists in the SpriteSheet at (${x}, ${y}), y: ${y} should be between 0 and ${this.rows - 1}`); + throw Error(`No sprite exists in the SpriteSheet at (${x}, ${y}), y: ${y} should be between 0 and ${this.rows - 1} rows`); } const spriteIndex = x + y * this.columns; const sprite = this.sprites[spriteIndex]; @@ -135,7 +136,43 @@ export class SpriteSheet { } return sprite; } - throw Error(`Invalid sprite coordinates (${x}, ${y}`); + throw Error(`Invalid sprite coordinates (${x}, ${y})`); + } + + /** + * Find a sprite by their x/y integer coordinates in the SpriteSheet and configures tiling to repeat by default, + * for example `getTiledSprite(0, 0)` is the {@apilink TiledSprite} in the top-left + * and `getTiledSprite(1, 0)` is the sprite one to the right. + * + * Example: + * + * ```typescript + * spriteSheet.getTiledSprite(1, 0, { + * width: game.screen.width, + * height: 200, + * wrapping: { + * x: ex.ImageWrapping.Repeat, + * y: ex.ImageWrapping.Clamp + * } + * }); + * ``` + * @param x + * @param y + * @param options + */ + public getTiledSprite(x: number, y: number, options?: Partial>): TiledSprite { + if (x >= this.columns || x < 0) { + throw Error(`No sprite exists in the SpriteSheet at (${x}, ${y}), x: ${x} should be between 0 and ${this.columns - 1} columns`); + } + if (y >= this.rows || y < 0) { + throw Error(`No sprite exists in the SpriteSheet at (${x}, ${y}), y: ${y} should be between 0 and ${this.rows - 1} rows`); + } + const spriteIndex = x + y * this.columns; + const sprite = this.sprites[spriteIndex]; + if (sprite) { + return TiledSprite.fromSprite(sprite, options); + } + throw Error(`Invalid sprite coordinates (${x}, ${y})`); } /** diff --git a/src/engine/Graphics/TiledSprite.ts b/src/engine/Graphics/TiledSprite.ts index 86445e597..406a407ac 100644 --- a/src/engine/Graphics/TiledSprite.ts +++ b/src/engine/Graphics/TiledSprite.ts @@ -1,5 +1,6 @@ import { Future } from '../Util/Future'; import { ImageFiltering } from './Filtering'; +import { GraphicOptions } from './Graphic'; import { ImageSource, ImageWrapConfiguration } from './ImageSource'; import { SourceView, Sprite } from './Sprite'; import { ImageWrapping } from './Wrapping'; @@ -31,12 +32,19 @@ export interface TiledSpriteOptions { export class TiledSprite extends Sprite { private _ready = new Future(); public ready = this._ready.promise; - private _options: TiledSpriteOptions; - constructor(options: TiledSpriteOptions) { + private _options: TiledSpriteOptions & GraphicOptions; + constructor(options: TiledSpriteOptions & GraphicOptions) { super({ image: options.image, sourceView: options.sourceView, - destSize: { width: options.width, height: options.height } + destSize: { width: options.width, height: options.height }, + flipHorizontal: options.flipHorizontal, + flipVertical: options.flipVertical, + rotation: options.rotation, + scale: options.scale, + opacity: options.opacity, + tint: options.tint, + origin: options.origin }); this._options = options; @@ -47,8 +55,9 @@ export class TiledSprite extends Sprite { } } - public static fromSprite(sprite: Sprite, options?: Partial>): TiledSprite { + public static fromSprite(sprite: Sprite, options?: Partial>): TiledSprite { return new TiledSprite({ + sourceView: { ...sprite.sourceView }, width: sprite.width, height: sprite.height, ...options, diff --git a/src/spec/SpriteSheetSpec.ts b/src/spec/SpriteSheetSpec.ts index 4a576c16f..3aa9fbcb2 100644 --- a/src/spec/SpriteSheetSpec.ts +++ b/src/spec/SpriteSheetSpec.ts @@ -122,13 +122,17 @@ describe('A SpriteSheet for Graphics', () => { expect(ss.getSprite(0, 0)).withContext('top left sprite').toEqual(ss.sprites[0]); expect(ss.getSprite(13, 3)).withContext('bottom right sprite').not.toBeNull(); - expect(() => ss.getSprite(13, 4)).toThrowError('No sprite exists in the SpriteSheet at (13, 4), y: 4 should be between 0 and 3'); + expect(() => ss.getSprite(13, 4)).toThrowError('No sprite exists in the SpriteSheet at (13, 4), y: 4 should be between 0 and 3 rows'); - expect(() => ss.getSprite(14, 3)).toThrowError('No sprite exists in the SpriteSheet at (14, 3), x: 14 should be between 0 and 13'); + expect(() => ss.getSprite(14, 3)).toThrowError( + 'No sprite exists in the SpriteSheet at (14, 3), x: 14 should be between 0 and 13 columns' + ); - expect(() => ss.getSprite(-1, 3)).toThrowError('No sprite exists in the SpriteSheet at (-1, 3), x: -1 should be between 0 and 13'); + expect(() => ss.getSprite(-1, 3)).toThrowError( + 'No sprite exists in the SpriteSheet at (-1, 3), x: -1 should be between 0 and 13 columns' + ); - expect(() => ss.getSprite(1, -1)).toThrowError('No sprite exists in the SpriteSheet at (1, -1), y: -1 should be between 0 and 3'); + expect(() => ss.getSprite(1, -1)).toThrowError('No sprite exists in the SpriteSheet at (1, -1), y: -1 should be between 0 and 3 rows'); }); it('can retrieve a sprite by x,y, with options', async () => { diff --git a/src/spec/TiledSpriteSpec.ts b/src/spec/TiledSpriteSpec.ts index 3be72b8b8..707ad20b7 100644 --- a/src/spec/TiledSpriteSpec.ts +++ b/src/spec/TiledSpriteSpec.ts @@ -91,4 +91,35 @@ describe('A TiledSprite', () => { await expectAsync(canvasElement).toEqualImage('src/spec/images/TiledSpriteSpec/tiled.png'); }); + + it('can grab a tiled sprite from a sprite sheet', async () => { + const cardsImage = new ex.ImageSource('src/spec/images/TiledAnimationSpec/kenny-cards.png'); + await cardsImage.load(); + const cardSpriteSheet = ex.SpriteSheet.fromImageSource({ + image: cardsImage, + grid: { + rows: 4, + columns: 14, + spriteWidth: 42, + spriteHeight: 60 + }, + spacing: { + originOffset: { x: 11, y: 2 }, + margin: { x: 23, y: 5 } + } + }); + + const sut = cardSpriteSheet.getTiledSprite(0, 0, { + width: 300, + height: 300, + scale: ex.vec(2, 2) + }); + await sut.ready; + + ctx.clear(); + sut.draw(ctx, 0, 0); + ctx.flush(); + + await expectAsync(canvasElement).toEqualImage('src/spec/images/TiledSpriteSpec/from-spritesheet.png'); + }); }); diff --git a/src/spec/images/TiledSpriteSpec/from-spritesheet.png b/src/spec/images/TiledSpriteSpec/from-spritesheet.png new file mode 100644 index 000000000..0f66d560b Binary files /dev/null and b/src/spec/images/TiledSpriteSpec/from-spritesheet.png differ