Skip to content

Commit

Permalink
feat: Add ex.Screen.worldToPagePixelRatio API
Browse files Browse the repository at this point in the history
New `ex.Screen.worldToPagePixelRatio` API that will return the ratio between excalibur pixels and the HTML pixels.
  * Additionally excalibur will now decorate the document root with this same value as a CSS variable `--ex-pixel-ratio`
  * Useful for scaling HTML UIs to match your game
    ```css
    .ui-container {
      pointer-events: none;
      position: absolute;
      transform-origin: 0 0;
      transform: scale(
        calc(var(--pixel-conversion)),
        calc(var(--pixel-conversion)));
    }
    ```
  • Loading branch information
eonarheim committed Aug 6, 2024
1 parent 514dc03 commit a1e7f6f
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 1 deletion.
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,19 @@ This project adheres to [Semantic Versioning](http://semver.org/).

### Added

- New `ex.Screen.worldToPagePixelRatio` API that will return the ratio between excalibur pixels and the HTML pixels.
* Additionally excalibur will now decorate the document root with this same value as a CSS variable `--ex-pixel-ratio`
* Useful for scaling HTML UIs to match your game
```css
.ui-container {
pointer-events: none;
position: absolute;
transform-origin: 0 0;
transform: scale(
calc(var(--pixel-conversion)),
calc(var(--pixel-conversion)));
}
```
- New updates to `ex.coroutine(...)`
* New `ex.CoroutineInstance` is returned (still awaitable)
* Control coroutine autostart with `ex.coroutine(function*(){...}, {autostart: false})`
Expand Down
20 changes: 20 additions & 0 deletions src/engine/Screen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,7 @@ export class Screen {
this._listenForPixelRatio();
this._devicePixelRatio = this._calculateDevicePixelRatio();
this.applyResolutionAndViewport();

this.events.emit('pixelratio', {
pixelRatio: this.pixelRatio
} satisfies PixelRatioChangeEvent);
Expand All @@ -372,6 +373,7 @@ export class Screen {
this._logger.debug('View port resized');
this._setResolutionAndViewportByDisplayMode(parent);
this.applyResolutionAndViewport();

// Emit resize event
this.events.emit('resize', {
resolution: this.resolution,
Expand Down Expand Up @@ -403,6 +405,22 @@ export class Screen {
return this._devicePixelRatio;
}

/**
* This calculates the ratio between excalibur pixels and the HTML pixels.
*
* This is useful for scaling HTML UI so that it matches your game.
*/
public get worldToPagePixelRatio(): number {
if (this._canvas) {
const pageOrigin = this.worldToPageCoordinates(Vector.Zero);
const pageDistance = this.worldToPageCoordinates(vec(1, 0)).sub(pageOrigin);
const pixelConversion = pageDistance.x;
return pixelConversion;
} else {
return 1;
}
}

/**
* Get or set the pixel ratio override
*
Expand Down Expand Up @@ -563,6 +581,8 @@ export class Screen {
if (this.graphicsContext instanceof ExcaliburGraphicsContext2DCanvas) {
this.graphicsContext.scale(this.pixelRatio, this.pixelRatio);
}
// Add the excalibur world pixel to page pixel
document.documentElement.style.setProperty('--ex-pixel-ratio', this.worldToPagePixelRatio.toString());
}

public get antialiasing() {
Expand Down
2 changes: 1 addition & 1 deletion src/engine/Util/Util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { Future } from './Future';
*/
export function getPosition(el: HTMLElement): Vector {
// do we need the scroll too? technically the offset method before did that
if (el) {
if (el && el.getBoundingClientRect) {
const rect = el.getBoundingClientRect();
return vec(rect.x + window.scrollX, rect.y + window.scrollY);
}
Expand Down
48 changes: 48 additions & 0 deletions src/spec/ScreenSpec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -753,6 +753,54 @@ describe('A Screen', () => {
expect(sut.worldToScreenCoordinates(ex.vec(600, 450))).toBeVector(ex.vec(800, 600));
});

it('can calculate the excalibur worldToPagePixelRatio 2x', () => {
Object.defineProperty(window, 'innerWidth', { writable: true, configurable: true, value: 1600 });
Object.defineProperty(window, 'innerHeight', { writable: true, configurable: true, value: 1200 });
const sut = new ex.Screen({
canvas,
context,
browser,
displayMode: ex.DisplayMode.FitScreen,
viewport: { width: 800, height: 600 },
pixelRatio: 2
});

expect(sut.worldToPagePixelRatio).toBe(2);
expect(document.documentElement.style.getPropertyValue('--ex-pixel-ratio')).toBe('2');
});

it('can calculate the excalibur worldToPagePixelRatio .5x', () => {
Object.defineProperty(window, 'innerWidth', { writable: true, configurable: true, value: 400 });
Object.defineProperty(window, 'innerHeight', { writable: true, configurable: true, value: 300 });
const sut = new ex.Screen({
canvas,
context,
browser,
displayMode: ex.DisplayMode.FitScreen,
viewport: { width: 800, height: 600 },
pixelRatio: 2
});

expect(sut.worldToPagePixelRatio).toBe(0.5);
expect(document.documentElement.style.getPropertyValue('--ex-pixel-ratio')).toBe('0.5');
});

it('can calculate the excalibur worldToPagePixelRatio 1.5x', () => {
Object.defineProperty(window, 'innerWidth', { writable: true, configurable: true, value: 1200 });
Object.defineProperty(window, 'innerHeight', { writable: true, configurable: true, value: 1000 });
const sut = new ex.Screen({
canvas,
context,
browser,
displayMode: ex.DisplayMode.FitScreen,
viewport: { width: 800, height: 600 },
pixelRatio: 2
});

expect(sut.worldToPagePixelRatio).toBe(1.5);
expect(document.documentElement.style.getPropertyValue('--ex-pixel-ratio')).toBe('1.5');
});

it('can return world bounds', () => {
const sut = new ex.Screen({
canvas,
Expand Down

0 comments on commit a1e7f6f

Please sign in to comment.