Skip to content

Commit

Permalink
feat: [#2688] Add actor level graphics flipping as a QOL improvement (#…
Browse files Browse the repository at this point in the history
…2694)

Closes #2688 

## Changes:

- Adds new convenience properties on the `ex.GraphicsComponent` to flip all the graphics on an `ex.Actor`
    * `ex.Actor.graphics.flipHorizontal` - Flips all the graphics horizontally
    * `ex.Actor.graphics.flipVertical` - Flips all the graphics vertically
  • Loading branch information
eonarheim authored Jul 15, 2023
1 parent c6f2884 commit 898a67c
Show file tree
Hide file tree
Showing 10 changed files with 185 additions and 8 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ This project adheres to [Semantic Versioning](http://semver.org/).

### Added

- Added new convenience properties for flipping all the graphics on an Actor
* `ex.Actor.graphics.flipHorizontal` - Flips all the graphics horizontally
* `ex.Actor.graphics.flipVertical` - Flips all the graphics vertically
- Added new `ex.Scene.transfer(actor)` method for transferring actors between scenes, useful if you want to only have an actor in 1 scene at a time.
- Added new `ex.Material` to add custom shaders per `ex.Actor`!
* This feature cant be applied using the `ex.Actor.graphics.material = material` property or by setting the material property on the `ex.ExcaliburGraphicsContext.material = material` with `.save()/.restore()`
Expand Down Expand Up @@ -135,6 +138,7 @@ are returned

### Fixed

- Fixed issue where `ex.Text.flipHorizontal` or `ex.Text.flipVertical` would not work
- Fixed issue where overriding existing components did not work properly because of deferred component removal
- Fixed issue where `ex.ScreenElement` pointer events were not working by default.
- Fixed issue where setting lineWidth on `ex.Circle` was not accounted for in the bitmap
Expand Down
10 changes: 10 additions & 0 deletions src/engine/Graphics/GraphicsComponent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,16 @@ export class GraphicsComponent extends Component<'ex.graphics'> {
*/
public anchor: Vector = Vector.Half;

/**
* Flip all graphics horizontally along the y-axis
*/
public flipHorizontal: boolean = false;

/**
* Flip all graphics vertically along the x-axis
*/
public flipVertical: boolean = false;

/**
* If set to true graphics added to the component will be copied. This can affect performance
*/
Expand Down
25 changes: 23 additions & 2 deletions src/engine/Graphics/GraphicsSystem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,11 +154,15 @@ export class GraphicsSystem extends System<TransformComponent | GraphicsComponen

private _drawGraphicsComponent(graphicsComponent: GraphicsComponent) {
if (graphicsComponent.visible) {
// this should be moved to the graphics system
const flipHorizontal = graphicsComponent.flipHorizontal;
const flipVertical = graphicsComponent.flipVertical;

for (const layer of graphicsComponent.layers.get()) {
for (const { graphic, options } of layer.graphics) {
let anchor = graphicsComponent.anchor;
let offset = graphicsComponent.offset;

// handle layer specific overrides
if (options?.anchor) {
anchor = options.anchor;
}
Expand All @@ -169,7 +173,24 @@ export class GraphicsSystem extends System<TransformComponent | GraphicsComponen
const offsetX = -graphic.width * anchor.x + offset.x;
const offsetY = -graphic.height * anchor.y + offset.y;

graphic?.draw(this._graphicsContext, offsetX + layer.offset.x, offsetY + layer.offset.y);
const oldFlipHorizontal = graphic.flipHorizontal;
const oldFlipVertical = graphic.flipVertical;
if (flipHorizontal || flipVertical) {

// flip any currently flipped graphics
graphic.flipHorizontal = flipHorizontal ? !oldFlipHorizontal : oldFlipHorizontal;
graphic.flipVertical = flipVertical ? !oldFlipVertical : oldFlipVertical;
}

graphic?.draw(
this._graphicsContext,
offsetX + layer.offset.x,
offsetY + layer.offset.y);

if (flipHorizontal || flipVertical) {
graphic.flipHorizontal = oldFlipHorizontal;
graphic.flipVertical = oldFlipVertical;
}

if (this._engine?.isDebug && this._engine.debug.graphics.showBounds) {
const offset = vec(offsetX + layer.offset.x, offsetY + layer.offset.y);
Expand Down
15 changes: 9 additions & 6 deletions src/engine/Graphics/Text.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,12 +108,7 @@ export class Text extends Graphic {
// This override erases the default behavior
}

protected override _drawImage(ex: ExcaliburGraphicsContext, x: number, y: number) {
let color = Color.Black;
if (this.font instanceof Font) {
color = this.color ?? this.font.color;
}

protected override _preDraw(ex: ExcaliburGraphicsContext, x: number, y: number): void {
if (this.isStale() || this.font.isStale()) {
this.font.flipHorizontal = this.flipHorizontal;
this.font.flipVertical = this.flipVertical;
Expand All @@ -122,6 +117,14 @@ export class Text extends Graphic {
this.font.opacity = this.opacity;
}
this.font.tint = this.tint;
super._preDraw(ex, x, y);
}

protected override _drawImage(ex: ExcaliburGraphicsContext, x: number, y: number) {
let color = Color.Black;
if (this.font instanceof Font) {
color = this.color ?? this.font.color;
}

const { width, height } = this.font.measureText(this._text, this.maxWidth);
this._textWidth = width;
Expand Down
139 changes: 139 additions & 0 deletions src/spec/GraphicsSystemSpec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -235,4 +235,143 @@ describe('A Graphics ECS System', () => {
await expectAsync(TestUtils.flushWebGLCanvasTo2D(engine.canvas))
.toEqualImage('src/spec/images/GraphicsSystemSpec/graphics-context-opacity.png');
});

it('can flip graphics horizontally', async () => {
const sut = new ex.GraphicsSystem();
const offscreenSystem = new ex.OffscreenSystem();
engine.currentScene.camera.update(engine, 1);
engine.currentScene._initialize(engine);
engine.screen.setCurrentCamera(engine.currentScene.camera);
offscreenSystem.initialize(engine.currentScene);
sut.initialize(engine.currentScene);

const sword = new ex.ImageSource('src/spec/images/GraphicsSystemSpec/sword.png');
await sword.load();

const actor = new ex.Actor({
x: 50,
y: 50,
height: 100,
width: 100
});
actor.graphics.use(sword.toSprite());
actor.graphics.flipHorizontal = true;

sut.notify(new ex.AddedEntity(actor));

offscreenSystem.update([actor]);

engine.graphicsContext.clear();
sut.preupdate();
sut.update([actor], 1);

engine.graphicsContext.flush();
await expectAsync(TestUtils.flushWebGLCanvasTo2D(engine.canvas))
.toEqualImage('src/spec/images/GraphicsSystemSpec/sword-flip-horizontal.png');
});

it('can flip graphics vertically', async () => {
const sut = new ex.GraphicsSystem();
const offscreenSystem = new ex.OffscreenSystem();
engine.currentScene.camera.update(engine, 1);
engine.currentScene._initialize(engine);
engine.screen.setCurrentCamera(engine.currentScene.camera);
offscreenSystem.initialize(engine.currentScene);
sut.initialize(engine.currentScene);

const sword = new ex.ImageSource('src/spec/images/GraphicsSystemSpec/sword.png');
await sword.load();

const actor = new ex.Actor({
x: 50,
y: 50,
height: 100,
width: 100
});
actor.graphics.use(sword.toSprite());
actor.graphics.flipVertical = true;

sut.notify(new ex.AddedEntity(actor));

offscreenSystem.update([actor]);

engine.graphicsContext.clear();
sut.preupdate();
sut.update([actor], 1);

engine.graphicsContext.flush();
await expectAsync(TestUtils.flushWebGLCanvasTo2D(engine.canvas))
.toEqualImage('src/spec/images/GraphicsSystemSpec/sword-flip-vertical.png');
});

it('can flip graphics both horizontally and vertically', async () => {
const sut = new ex.GraphicsSystem();
const offscreenSystem = new ex.OffscreenSystem();
engine.currentScene.camera.update(engine, 1);
engine.currentScene._initialize(engine);
engine.screen.setCurrentCamera(engine.currentScene.camera);
offscreenSystem.initialize(engine.currentScene);
sut.initialize(engine.currentScene);

const sword = new ex.ImageSource('src/spec/images/GraphicsSystemSpec/sword.png');
await sword.load();

const actor = new ex.Actor({
x: 50,
y: 50,
height: 100,
width: 100
});
actor.graphics.use(sword.toSprite());
actor.graphics.flipVertical = true;
actor.graphics.flipHorizontal = true;

sut.notify(new ex.AddedEntity(actor));

offscreenSystem.update([actor]);

engine.graphicsContext.clear();
sut.preupdate();
sut.update([actor], 1);

engine.graphicsContext.flush();
await expectAsync(TestUtils.flushWebGLCanvasTo2D(engine.canvas))
.toEqualImage('src/spec/images/GraphicsSystemSpec/sword-flip-both.png');
});

it('can flip graphics both horizontally and vertically with an offset', async () => {
const sut = new ex.GraphicsSystem();
const offscreenSystem = new ex.OffscreenSystem();
engine.currentScene.camera.update(engine, 1);
engine.currentScene._initialize(engine);
engine.screen.setCurrentCamera(engine.currentScene.camera);
offscreenSystem.initialize(engine.currentScene);
sut.initialize(engine.currentScene);

const sword = new ex.ImageSource('src/spec/images/GraphicsSystemSpec/sword.png');
await sword.load();

const actor = new ex.Actor({
x: 50,
y: 50,
height: 100,
width: 100
});
actor.graphics.use(sword.toSprite());
actor.graphics.flipVertical = true;
actor.graphics.flipHorizontal = true;
actor.graphics.offset = ex.vec(25, 25);

sut.notify(new ex.AddedEntity(actor));

offscreenSystem.update([actor]);

engine.graphicsContext.clear();
sut.preupdate();
sut.update([actor], 1);

engine.graphicsContext.flush();
await expectAsync(TestUtils.flushWebGLCanvasTo2D(engine.canvas))
.toEqualImage('src/spec/images/GraphicsSystemSpec/sword-flip-both-offset.png');
});
});
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/spec/images/GraphicsSystemSpec/sword.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 898a67c

Please sign in to comment.