diff --git a/sandbox/tests/fitscreen/fitscreen.ts b/sandbox/tests/fitscreen/fitscreen.ts
new file mode 100644
index 000000000..17d85e907
--- /dev/null
+++ b/sandbox/tests/fitscreen/fitscreen.ts
@@ -0,0 +1,34 @@
+
+async function main() {
+ const game = new ex.Engine({
+ width: 3000,
+ height: 3000,
+ suppressHiDPIScaling: true,
+ displayMode: ex.DisplayMode.FitScreen,
+ });
+
+ await game.start();
+
+ const paddle = new ex.Actor({
+ x: 220,
+ y: 220,
+ width: 400,
+ height: 400,
+ color: ex.Color.White,
+ });
+
+ console.log('Game:', game.drawWidth, '·', game.drawHeight);
+ console.log('Canv:', game.canvasWidth, '·', game.canvasHeight);
+ // console.log('Camera: ', game.currentScene.camera.pos);
+ // console.log('Center: ', game.screen.center);
+ // game.currentScene.camera.pos = game.screen.center;
+
+ game.add(paddle);
+ return [game, paddle];
+}
+
+main().then(([game, paddle]) => {
+ console.log('Promise from main()');
+ (window as any).game = game;
+ (window as any).paddle = paddle;
+});
\ No newline at end of file
diff --git a/sandbox/tests/fitscreen/index.html b/sandbox/tests/fitscreen/index.html
new file mode 100644
index 000000000..f79f1d27a
--- /dev/null
+++ b/sandbox/tests/fitscreen/index.html
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+ FitScreen
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/sandbox/tests/gif/animatedGif.ts b/sandbox/tests/gif/animatedGif.ts
index 8cd00426b..e8639be22 100644
--- a/sandbox/tests/gif/animatedGif.ts
+++ b/sandbox/tests/gif/animatedGif.ts
@@ -8,6 +8,8 @@ var game = new ex.Engine({
displayMode: ex.DisplayMode.FitScreen
});
+game.currentScene.camera.zoom = 2;
+
var gif: ex.Gif = new ex.Gif('./sword.gif', ex.Color.Black);
var loader = new ex.Loader([gif]);
game.start(loader).then(() => {
diff --git a/src/engine/Camera.ts b/src/engine/Camera.ts
index 6f5a1211e..e4d5da9de 100644
--- a/src/engine/Camera.ts
+++ b/src/engine/Camera.ts
@@ -586,12 +586,22 @@ export class Camera extends Class implements CanUpdate, CanInitialize {
public _initialize(_engine: Engine) {
if (!this.isInitialized) {
this._engine = _engine;
- this._halfWidth = _engine.halfDrawWidth;
- this._halfHeight = _engine.halfDrawHeight;
- // pos unset apply default position is center screen
+
+ const currentRes = this._engine.screen.resolution;
+ let center = vec(currentRes.width / 2, currentRes.height / 2);
+ if (!this._engine.loadingComplete) {
+ // If there was a loading screen, we peek the configured resolution
+ const res = this._engine.screen.peekResolution();
+ if (res) {
+ center = vec(res.width / 2, res.height / 2);
+ }
+ }
+ this._halfWidth = center.x;
+ this._halfHeight = center.x;
+
+ // If the user has not set the camera pos, apply default center screen position
if (!this._posChanged) {
- this.pos.x = _engine.halfDrawWidth;
- this.pos.y = _engine.halfDrawHeight;
+ this.pos = center;
}
this.onInitialize(_engine);
diff --git a/src/engine/Engine.ts b/src/engine/Engine.ts
index f1a788c77..4099d4bd5 100644
--- a/src/engine/Engine.ts
+++ b/src/engine/Engine.ts
@@ -607,8 +607,6 @@ O|===|* >________________>\n\
pixelRatio: options.suppressHiDPIScaling ? 1 : null
});
- this.screen.applyResolutionAndViewport();
-
if (options.backgroundColor) {
this.backgroundColor = options.backgroundColor.clone();
}
@@ -844,7 +842,7 @@ O|===|* >________________>\n\
* named scene. Calls the [[Scene]] lifecycle events.
* @param key The key of the scene to transition to.
*/
- public goToScene(key: string) {
+ public goToScene(key: string): void {
// if not yet initialized defer goToScene
if (!this.isInitialized) {
this._deferredGoTo = key;
@@ -995,7 +993,9 @@ O|===|* >________________>\n\
this.input.gamepads.update();
return;
}
+
this._overrideInitialize(this);
+
// Publish preupdate events
this._preupdate(delta);
@@ -1129,21 +1129,30 @@ O|===|* >________________>\n\
return this._isDebug;
}
+ private _loadingComplete: boolean = false;
+ /**
+ * Returns true when loading is totally complete and the player has clicked start
+ */
+ public get loadingComplete() {
+ return this._loadingComplete;
+ }
/**
* Starts the internal game loop for Excalibur after loading
* any provided assets.
* @param loader Optional [[Loader]] to use to load resources. The default loader is [[Loader]], override to provide your own
* custom loader.
*/
- public start(loader?: Loader): Promise {
+ public start(loader?: Loader): Promise {
if (!this._compatible) {
return Promise.reject('Excalibur is incompatible with your browser');
}
+ let loadingComplete: Promise;
+ // Push the current user entered resolution/viewport
this.screen.pushResolutionAndViewport();
+ // Configure resolution for loader
this.screen.resolution = this.screen.viewport;
this.screen.applyResolutionAndViewport();
this.graphicsContext.updateViewport();
- let loadingComplete: Promise;
if (loader) {
this._loader = loader;
this._loader.suppressPlayButton = this._suppressPlayButton || this._loader.suppressPlayButton;
@@ -1158,14 +1167,15 @@ O|===|* >________________>\n\
this.screen.applyResolutionAndViewport();
this.graphicsContext.updateViewport();
this.emit('start', new GameStartEvent(this));
+ this._loadingComplete = true;
});
if (!this._hasStarted) {
+ // has started is a slight misnomer, it's really mainloop started
this._hasStarted = true;
this._logger.debug('Starting game...');
this.browser.resume();
Engine.createMainLoop(this, window.requestAnimationFrame, Date.now)();
-
this._logger.debug('Game started');
} else {
// Game already started;
diff --git a/src/engine/Graphics/Context/ExcaliburGraphicsContext.ts b/src/engine/Graphics/Context/ExcaliburGraphicsContext.ts
index 83f73358b..584962f2c 100644
--- a/src/engine/Graphics/Context/ExcaliburGraphicsContext.ts
+++ b/src/engine/Graphics/Context/ExcaliburGraphicsContext.ts
@@ -89,7 +89,7 @@ export interface ExcaliburGraphicsContext {
resetTransform(): void;
/**
- * Update the context with the curren tviewport dimensions (used in resizing)
+ * Update the context with the current viewport dimensions (used in resizing)
*/
updateViewport(): void;
diff --git a/src/engine/Graphics/Context/ExcaliburGraphicsContextWebGL.ts b/src/engine/Graphics/Context/ExcaliburGraphicsContextWebGL.ts
index f0a48ac3a..6d7f7826f 100644
--- a/src/engine/Graphics/Context/ExcaliburGraphicsContextWebGL.ts
+++ b/src/engine/Graphics/Context/ExcaliburGraphicsContextWebGL.ts
@@ -20,6 +20,7 @@ import { PointRenderer } from './point-renderer';
import { Canvas } from '../Canvas';
import { GraphicsDiagnostics } from '../GraphicsDiagnostics';
import { DebugText } from './debug-text';
+import { ScreenDimension } from '../../Screen';
class ExcaliburGraphicsContextWebGLDebug implements DebugDraw {
private _debugText = new DebugText();
@@ -130,6 +131,22 @@ export class ExcaliburGraphicsContextWebGL implements ExcaliburGraphicsContext {
return this.__gl.canvas.height;
}
+ /**
+ * Checks the underlying webgl implementation if the requested internal resolution is supported
+ * @param dim
+ */
+ public checkIfResolutionSupported(dim: ScreenDimension): boolean {
+ // Slight hack based on this thread https://groups.google.com/g/webgl-dev-list/c/AHONvz3oQTo
+ const gl = this.__gl;
+ // If any dimension is greater than max texture size (divide by 4 bytes per pixel)
+ const maxDim = gl.getParameter(gl.MAX_TEXTURE_SIZE) / 4;
+ let supported = true;
+ if (dim.width > maxDim ||dim.height > maxDim) {
+ supported = false;
+ }
+ return supported;
+ }
+
constructor(options: ExcaliburGraphicsContextOptions) {
const { canvasElement, enableTransparency, smoothing, snapToPixel, backgroundColor } = options;
this.__gl = canvasElement.getContext('webgl', {
diff --git a/src/engine/Screen.ts b/src/engine/Screen.ts
index 52879cf32..d5ae83a9a 100644
--- a/src/engine/Screen.ts
+++ b/src/engine/Screen.ts
@@ -5,6 +5,7 @@ import { BrowserEvents } from './Util/Browser';
import { BoundingBox } from './Collision/Index';
import { ExcaliburGraphicsContext } from './Graphics/Context/ExcaliburGraphicsContext';
import { getPosition } from './Util/Util';
+import { ExcaliburGraphicsContextWebGL } from './Graphics/Context/ExcaliburGraphicsContextWebGL';
/**
* Enum representing the different display modes available to Excalibur.
@@ -216,6 +217,7 @@ export class Screen {
this._mediaQueryList.addEventListener('change', this._pixelRatioChangeHandler);
this._canvas.addEventListener('fullscreenchange', this._fullscreenChangeHandler);
+ this.applyResolutionAndViewport();
}
public dispose(): void {
@@ -334,15 +336,39 @@ export class Screen {
this.viewport = { ...this.viewport };
}
+ public peekViewport(): ScreenDimension {
+ return this._viewportStack[this._viewportStack.length - 1];
+ }
+
+ public peekResolution(): ScreenDimension {
+ return this._resolutionStack[this._resolutionStack.length - 1];
+ }
+
public popResolutionAndViewport() {
this.resolution = this._resolutionStack.pop();
this.viewport = this._viewportStack.pop();
}
+ private _alreadyWarned = false;
public applyResolutionAndViewport() {
this._canvas.width = this.scaledWidth;
this._canvas.height = this.scaledHeight;
+ if (this._ctx instanceof ExcaliburGraphicsContextWebGL) {
+ const supported = this._ctx.checkIfResolutionSupported({
+ width: this.scaledWidth,
+ height: this.scaledHeight
+ });
+ if (!supported && !this._alreadyWarned) {
+ this._alreadyWarned = true; // warn once
+ this._logger.warn(
+ `The currently configured resolution (${this.resolution.width}x${this.resolution.height})` +
+ ' is too large for the platform WebGL implementation, this may work but cause WebGL rendering to behave oddly.' +
+ ' Try reducing the resolution or disabling Hi DPI scaling to avoid this' +
+ ' (read more here https://excaliburjs.com/docs/screens#understanding-viewport--resolution).');
+ }
+ }
+
if (this._antialiasing) {
this._canvas.style.imageRendering = 'auto';
} else {
@@ -581,9 +607,9 @@ export class Screen {
*/
public get drawWidth(): number {
if (this._camera) {
- return this.scaledWidth / this._camera.zoom / this.pixelRatio;
+ return this.resolution.width / this._camera.zoom;
}
- return this.scaledWidth / this.pixelRatio;
+ return this.resolution.width;
}
/**
@@ -598,9 +624,9 @@ export class Screen {
*/
public get drawHeight(): number {
if (this._camera) {
- return this.scaledHeight / this._camera.zoom / this.pixelRatio;
+ return this.resolution.height / this._camera.zoom;
}
- return this.scaledHeight / this.pixelRatio;
+ return this.resolution.height;
}
/**
diff --git a/src/spec/CameraSpec.ts b/src/spec/CameraSpec.ts
index cb61d7921..deffdcf51 100644
--- a/src/spec/CameraSpec.ts
+++ b/src/spec/CameraSpec.ts
@@ -39,6 +39,40 @@ describe('A camera', () => {
engine.stop();
});
+ it('should be center screen by default (when loading not complete)', () => {
+ engine = TestUtils.engine({
+ viewport: {width: 100, height: 100},
+ resolution: {width: 1000, height: 1200 }
+ });
+ engine.screen.pushResolutionAndViewport();
+ engine.screen.resolution = {width: 100, height: 1000};
+ spyOnProperty(engine, 'loadingComplete', 'get').and.returnValue(false);
+ spyOn(engine.screen, 'peekResolution').and.callThrough();
+
+ const sut = new ex.Camera();
+ sut.zoom = 2; // zoom should not change the center position
+ sut._initialize(engine);
+
+ expect(sut.pos).toBeVector(ex.vec(500, 600));
+ expect(engine.screen.peekResolution).toHaveBeenCalled();
+ });
+
+ it('should be center screen by default (when loading complete)', () => {
+ engine = TestUtils.engine({
+ viewport: {width: 100, height: 100},
+ resolution: {width: 1000, height: 1200 }
+ });
+
+ spyOnProperty(engine, 'loadingComplete', 'get').and.returnValue(true);
+ spyOn(engine.screen, 'peekResolution').and.callThrough();
+ const sut = new ex.Camera();
+ sut.zoom = 2; // zoom should not change the center position
+ sut._initialize(engine);
+
+ expect(sut.pos).toBeVector(ex.vec(500, 600));
+ expect(engine.screen.peekResolution).not.toHaveBeenCalled();
+ });
+
it('can focus on a point', () => {
// set the focus with positional attributes
Camera.x = 10;
diff --git a/src/spec/ScreenSpec.ts b/src/spec/ScreenSpec.ts
index cd8f2f158..b573638e8 100644
--- a/src/spec/ScreenSpec.ts
+++ b/src/spec/ScreenSpec.ts
@@ -578,4 +578,39 @@ describe('A Screen', () => {
expect(sut.center).toBeVector(ex.vec(200, 150));
});
+
+ it('will warn if the resolution is too large', () => {
+ const logger = ex.Logger.getInstance();
+ spyOn(logger, 'warn');
+
+ const canvasElement = document.createElement('canvas');
+ canvasElement.width = 100;
+ canvasElement.height = 100;
+
+ const context = new ex.ExcaliburGraphicsContextWebGL({
+ canvasElement: canvasElement,
+ enableTransparency: false,
+ snapToPixel: true,
+ backgroundColor: ex.Color.White
+ });
+
+ const sut = new ex.Screen({
+ canvas,
+ context,
+ browser,
+ viewport: { width: 800, height: 600 },
+ pixelRatio: 2
+ });
+
+ spyOn(context, 'checkIfResolutionSupported').and.returnValue(false);
+ sut.resolution = { width: 3000, height: 3000 };
+ sut.applyResolutionAndViewport();
+ expect(context.checkIfResolutionSupported).toHaveBeenCalled();
+ expect(logger.warn).toHaveBeenCalledOnceWith(
+ `The currently configured resolution (${sut.resolution.width}x${sut.resolution.height})` +
+ ' is too large for the platform WebGL implementation, this may work but cause WebGL rendering to behave oddly.' +
+ ' Try reducing the resolution or disabling Hi DPI scaling to avoid this' +
+ ' (read more here https://excaliburjs.com/docs/screens#understanding-viewport--resolution).'
+ );
+ });
});