From 907b7e703308bff878d5929d77d507a87a33e432 Mon Sep 17 00:00:00 2001 From: Erik Onarheim Date: Fri, 23 Feb 2024 08:42:52 -0600 Subject: [PATCH] fix: Transition bug when camera is zoomed (#2942) - Fixed issue where start transition did not work properly if deferred - Fixed issue where transitions did not cover the whole screen if camera was zoomed --- CHANGELOG.md | 3 ++- sandbox/tests/router/index.ts | 14 +++++++++++++- src/engine/Director/CrossFade.ts | 3 +-- src/engine/Director/DefaultLoader.ts | 8 ++++---- src/engine/Director/Director.ts | 18 +++++++++++++++--- src/engine/Director/FadeInOut.ts | 8 +++----- src/engine/Director/Transition.ts | 9 +++++---- src/engine/Screen.ts | 24 +++++++++++++++++++++++- src/spec/DirectorSpec.ts | 3 +++ src/spec/FadeInOutSpec.ts | 4 ++-- 10 files changed, 71 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 726747602..d59c2d398 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,7 +20,8 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Fixed -- +- Fixed issue where start transition did not work properly if deferred +- Fixed issue where transitions did not cover the whole screen if camera was zoomed ### Updates diff --git a/sandbox/tests/router/index.ts b/sandbox/tests/router/index.ts index f750ae228..f1dbac1c4 100644 --- a/sandbox/tests/router/index.ts +++ b/sandbox/tests/router/index.ts @@ -6,7 +6,15 @@ scene1.add(new ex.Label({ text: 'Scene 1', z: 99 })) +scene1.onInitialize = () => { + scene1.camera.pos = ex.vec(200, 200); + scene1.camera.zoom = 2; +} var scene2 = new ex.Scene(); +scene2.onInitialize = () => { + scene2.camera.pos = ex.vec(200, 200); + scene2.camera.zoom = 2; +} scene2.add(new ex.Label({ pos: ex.vec(100, 100), color: ex.Color.Violet, @@ -130,7 +138,11 @@ gameWithTransitions.input.keyboard.on('press', evt => { gameWithTransitions.input.pointers.primary.on('down', () => { gameWithTransitions.goto('scene1'); }); -var startTransition = new ex.FadeInOut({duration: 500, direction: 'in', color: ex.Color.ExcaliburBlue}); +var startTransition = new ex.FadeInOut({ + duration: 3500, + direction: 'in', + color: ex.Color.Black +}); // startTransition.events.on('kill', () => { // console.log(game.currentScene.entities); // console.log('killed!'); diff --git a/src/engine/Director/CrossFade.ts b/src/engine/Director/CrossFade.ts index ee4527b6c..1acca790b 100644 --- a/src/engine/Director/CrossFade.ts +++ b/src/engine/Director/CrossFade.ts @@ -31,8 +31,7 @@ export class CrossFade extends Transition { override onInitialize(engine: Engine): void { this.engine = engine; - const bounds = engine.screen.getWorldBounds(); - this.transform.pos = vec(bounds.left, bounds.top); + this.transform.pos = engine.screen.unsafeArea.topLeft; this.screenCover = ImageSource.fromHtmlImageElement(this.image).toSprite(); this.graphics.add(this.screenCover); this.transform.scale = vec(1 / engine.screen.pixelRatio, 1 / engine.screen.pixelRatio); diff --git a/src/engine/Director/DefaultLoader.ts b/src/engine/Director/DefaultLoader.ts index c04a79e7d..555e822b3 100644 --- a/src/engine/Director/DefaultLoader.ts +++ b/src/engine/Director/DefaultLoader.ts @@ -75,8 +75,8 @@ export class DefaultLoader implements Loadable[]> { */ public onInitialize(engine: Engine) { this.engine = engine; - this.canvas.width = this.engine.screen.canvasWidth; - this.canvas.height = this.engine.screen.canvasHeight; + this.canvas.width = this.engine.screen.resolution.width; + this.canvas.height = this.engine.screen.resolution.height; } /** @@ -165,10 +165,10 @@ export class DefaultLoader implements Loadable[]> { const seconds = this._totalTimeMs / 1000; ctx.fillStyle = Color.Black.toRGBA(); - ctx.fillRect(0, 0, this.engine.screen.drawWidth, this.engine.screen.drawHeight); + ctx.fillRect(0, 0, this.engine.screen.resolution.width, this.engine.screen.resolution.height); ctx.save(); - ctx.translate(this.engine.screen.center.x, this.engine.screen.center.y); + ctx.translate(this.engine.screen.resolution.width / 2, this.engine.screen.resolution.height / 2); const speed = seconds * 10; ctx.strokeStyle = 'white'; ctx.lineWidth = 10; diff --git a/src/engine/Director/Director.ts b/src/engine/Director/Director.ts index fffa5e506..42a2e8a6d 100644 --- a/src/engine/Director/Director.ts +++ b/src/engine/Director/Director.ts @@ -102,6 +102,7 @@ export class Director { public events = new EventEmitter(); private _logger = Logger.getInstance(); private _deferredGoto: string; + private _deferredTransition: Transition; private _initialized = false; /** @@ -176,8 +177,13 @@ export class Director { this._initialized = true; if (this._deferredGoto) { const deferredScene = this._deferredGoto; + const deferredTransition = this._deferredTransition; this._deferredGoto = null; + this._deferredTransition = null; await this.swapScene(deferredScene); + if (deferredTransition) { + await this.playTransition(deferredTransition); + } } else { await this.swapScene('root'); } @@ -217,9 +223,10 @@ export class Director { // Fire and forget promise for the initial scene if (maybeStartTransition) { // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.swapScene(this.startScene); - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.playTransition(maybeStartTransition); + this.swapScene(this.startScene).then(() => { + // eslint-disable-next-line @typescript-eslint/no-floating-promises + this.playTransition(maybeStartTransition); + }); } else { // eslint-disable-next-line @typescript-eslint/no-floating-promises this.swapScene(this.startScene); @@ -476,6 +483,11 @@ export class Director { * @param transition */ async playTransition(transition: Transition) { + if (!this.isInitialized) { + this._deferredTransition = transition; + return; + } + if (transition) { this.currentTransition = transition; const currentScene = this._engine.currentScene; diff --git a/src/engine/Director/FadeInOut.ts b/src/engine/Director/FadeInOut.ts index d2cb53106..d1c002843 100644 --- a/src/engine/Director/FadeInOut.ts +++ b/src/engine/Director/FadeInOut.ts @@ -1,6 +1,5 @@ import { Engine } from '../Engine'; import { Color } from '../Color'; -import { vec } from '../Math/vector'; import { Rectangle } from '../Graphics'; import { Transition, TransitionOptions } from './Transition'; @@ -22,11 +21,10 @@ export class FadeInOut extends Transition { } public onInitialize(engine: Engine): void { - const bounds = engine.screen.getWorldBounds(); - this.transform.pos = vec(bounds.left, bounds.top); + this.transform.pos = engine.screen.unsafeArea.topLeft; this.screenCover = new Rectangle({ - width: bounds.width, - height: bounds.height, + width: engine.screen.resolution.width, + height: engine.screen.resolution.height, color: this.color }); this.graphics.add(this.screenCover); diff --git a/src/engine/Director/Transition.ts b/src/engine/Director/Transition.ts index 6e6288760..fe1fc8818 100644 --- a/src/engine/Director/Transition.ts +++ b/src/engine/Director/Transition.ts @@ -195,13 +195,14 @@ export class Transition extends Entity { this.onReset(); } - play(engine: Engine) { + play(engine: Engine, targetScene?: Scene) { if (this.started) { - this._logger.warn(`Attempted to play a transition ${this.name} that is already playing`); - return Promise.resolve(); + this.reset(); + this._logger.warn(`Attempted to play a transition ${this.name} that is already playing, reset transition.`); } - engine.add(this); + const currentScene = targetScene ?? engine.currentScene; + currentScene.add(this); const self = this; return coroutine(engine, function * () { while (!self.complete) { diff --git a/src/engine/Screen.ts b/src/engine/Screen.ts index e32be8aa2..7454fedac 100644 --- a/src/engine/Screen.ts +++ b/src/engine/Screen.ts @@ -806,6 +806,16 @@ export class Screen { return this._contentArea; } + /** + * Returns the unsafe area in screen space, this is the full screen and some space may not be onscreen. + */ + public get unsafeArea(): BoundingBox { + return this._unsafeArea; + } + + private _contentArea: BoundingBox = new BoundingBox(); + private _unsafeArea: BoundingBox = new BoundingBox(); + private _computeFit() { document.body.style.margin = '0px'; document.body.style.overflow = 'hidden'; @@ -825,9 +835,9 @@ export class Screen { height: adjustedHeight }; this._contentArea = BoundingBox.fromDimension(this.resolution.width, this.resolution.height, Vector.Zero); + this._unsafeArea = BoundingBox.fromDimension(this.resolution.width, this.resolution.height, Vector.Zero); } - private _contentArea: BoundingBox = new BoundingBox(); private _computeFitScreenAndFill() { document.body.style.margin = '0px'; document.body.style.overflow = 'hidden'; @@ -866,6 +876,12 @@ export class Screen { right: this._contentResolution.width, bottom: this.resolution.height - clip }); + this._unsafeArea = new BoundingBox({ + top: -clip, + left: 0, + right: this._contentResolution.width, + bottom: this.resolution.height + clip + }); } else { this.resolution = { width: vh * this._contentResolution.height / vh * vw / vh, @@ -878,6 +894,12 @@ export class Screen { right: this.resolution.width - clip, bottom: this._contentResolution.height }); + this._unsafeArea = new BoundingBox({ + top: 0, + left: -clip, + right: this.resolution.width + clip, + bottom: this._contentResolution.height + }); } } diff --git a/src/spec/DirectorSpec.ts b/src/spec/DirectorSpec.ts index 706c99ee8..c977c4e2f 100644 --- a/src/spec/DirectorSpec.ts +++ b/src/spec/DirectorSpec.ts @@ -46,6 +46,7 @@ describe('A Director', () => { it('can configure start, non deferred', async () => { const engine = TestUtils.engine(); + const clock = engine.clock as ex.TestClock; const scene1 = new ex.Scene(); const scene2 = new ex.Scene(); const sut = new ex.Director(engine, { @@ -62,10 +63,12 @@ describe('A Director', () => { loader }); await engine.load(loader); + await TestUtils.flushMicrotasks(clock, 5); expect(sut.currentTransition).toBe(fadeIn); expect(sut.currentSceneName).toBe('scene1'); expect(sut.currentScene).toBe(scene1); + engine.dispose(); }); it('can configure start deferred', async () => { diff --git a/src/spec/FadeInOutSpec.ts b/src/spec/FadeInOutSpec.ts index f30856210..5151b1506 100644 --- a/src/spec/FadeInOutSpec.ts +++ b/src/spec/FadeInOutSpec.ts @@ -56,7 +56,7 @@ describe('A FadeInOut transition', () => { it('can fade out', async () => { const engine = TestUtils.engine({ backgroundColor: ex.Color.ExcaliburBlue }); const clock = engine.clock as ex.TestClock; - TestUtils.runToReady(engine); + await TestUtils.runToReady(engine); engine.add(new ex.Actor({ pos: ex.vec(20, 20), width: 100, @@ -75,7 +75,7 @@ describe('A FadeInOut transition', () => { })); engine.addScene('newScene', scene); - const goto = engine.goto('newScene', { sourceOut: sut }); + const goto = engine.goToScene('newScene', { sourceOut: sut }); await TestUtils.flushMicrotasks(clock, 3); clock.step(900); await Promise.resolve();