Skip to content

Commit

Permalink
feat: [#3312] Add getTiledSprite to SpriteSheet
Browse files Browse the repository at this point in the history
closes: #3312
  • Loading branch information
eonarheim committed Dec 8, 2024
1 parent 886e25c commit 9ab78ee
Show file tree
Hide file tree
Showing 7 changed files with 121 additions and 12 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
29 changes: 28 additions & 1 deletion sandbox/tests/tiling/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand All @@ -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
Expand All @@ -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)
});
Expand Down
43 changes: 40 additions & 3 deletions src/engine/Graphics/SpriteSheet.ts
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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];
Expand All @@ -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<Omit<TiledSpriteOptions & GraphicOptions, 'image'>>): 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})`);
}

/**
Expand Down
17 changes: 13 additions & 4 deletions src/engine/Graphics/TiledSprite.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -31,12 +32,19 @@ export interface TiledSpriteOptions {
export class TiledSprite extends Sprite {
private _ready = new Future<void>();
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;

Expand All @@ -47,8 +55,9 @@ export class TiledSprite extends Sprite {
}
}

public static fromSprite(sprite: Sprite, options?: Partial<Omit<TiledSpriteOptions, 'image'>>): TiledSprite {
public static fromSprite(sprite: Sprite, options?: Partial<Omit<TiledSpriteOptions & GraphicOptions, 'image'>>): TiledSprite {
return new TiledSprite({
sourceView: { ...sprite.sourceView },
width: sprite.width,
height: sprite.height,
...options,
Expand Down
12 changes: 8 additions & 4 deletions src/spec/SpriteSheetSpec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {
Expand Down
31 changes: 31 additions & 0 deletions src/spec/TiledSpriteSpec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
});
});
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 9ab78ee

Please sign in to comment.