From 160bf1926e2c9173c390b8071646c508157fbca9 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 3 Oct 2026 09:51:39 +0000 Subject: [PATCH] feat(utilities): forward render context options through createGame createGame now takes an optional options object whose renderContext field is passed to createRenderContext, so games built on createGame can set maxPixelRatio (and the other RenderContextOptions). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_012jXdrrhDQTunF1wimhhC5t --- CHANGELOG.md | 4 +++ documentation-site/docs/docs/ecs/game.md | 11 +++++++ .../docs/rendering/world-units-and-cameras.md | 9 ++++++ src/utilities/create-game.test.ts | 20 ++++++++++++ src/utilities/create-game.ts | 31 +++++++++++++++++-- 5 files changed, 73 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e46341a..b64fdc5a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +#### Added + +- **utilities:** `createGame` takes an optional second `options` argument whose `renderContext` field is forwarded to `createRenderContext`, so a game set up with `createGame` can cap its render resolution, e.g. `createGame('game', { renderContext: { maxPixelRatio: 1.5 } })` + ## [0.25.6] - 2026-10-03 #### Added diff --git a/documentation-site/docs/docs/ecs/game.md b/documentation-site/docs/docs/ecs/game.md index 725ea106..5068de28 100644 --- a/documentation-site/docs/docs/ecs/game.md +++ b/documentation-site/docs/docs/ecs/game.md @@ -96,6 +96,17 @@ const { game, world, time, renderContext, resizeSync } = createGame('game'); game.run(); ``` +`createGame` takes an optional second argument. Its `renderContext` field is +forwarded to [`createRenderContext`](/Forge/docs/api/functions/createRenderContext), +so you can, for example, cap the render resolution on high-DPI displays (see +[High-DPI displays](../rendering/world-units-and-cameras.md#high-dpi-displays)): + +```ts +const { game, renderContext } = createGame('game', { + renderContext: { maxPixelRatio: 1.5 }, +}); +``` + Manual setup (when you need fine-grained control): ```ts diff --git a/documentation-site/docs/docs/rendering/world-units-and-cameras.md b/documentation-site/docs/docs/rendering/world-units-and-cameras.md index 39f24d83..63cdadb0 100644 --- a/documentation-site/docs/docs/rendering/world-units-and-cameras.md +++ b/documentation-site/docs/docs/rendering/world-units-and-cameras.md @@ -148,6 +148,15 @@ can cap it with `maxPixelRatio`: const renderContext = createRenderContext(canvas, { maxPixelRatio: 2 }); ``` +If you set up with `createGame`, pass the same options through its +`renderContext` option: + +```ts +const { renderContext } = createGame('game-container', { + renderContext: { maxPixelRatio: 1.5 }, +}); +``` + Pass `maxPixelRatio: 1` to always render at CSS resolution, the engine's behavior before it supported high-DPI displays. diff --git a/src/utilities/create-game.test.ts b/src/utilities/create-game.test.ts index 2d270ad0..cfcef01a 100644 --- a/src/utilities/create-game.test.ts +++ b/src/utilities/create-game.test.ts @@ -56,6 +56,26 @@ describe('createGame', () => { expect(createRenderContext).toHaveBeenCalled(); }); + it('creates the render context with the canvas and no options by default', () => { + const canvas = document.createElement('canvas'); + vi.mocked(createCanvas).mockReturnValueOnce(canvas); + + createGame('game-container'); + + expect(createRenderContext).toHaveBeenLastCalledWith(canvas, {}); + }); + + it('forwards renderContext options to createRenderContext', () => { + const canvas = document.createElement('canvas'); + vi.mocked(createCanvas).mockReturnValueOnce(canvas); + + createGame('game-container', { renderContext: { maxPixelRatio: 1.5 } }); + + expect(createRenderContext).toHaveBeenLastCalledWith(canvas, { + maxPixelRatio: 1.5, + }); + }); + it('returns a resize sync', () => { const { resizeSync } = createGame('game-container'); expect(typeof resizeSync.stop).toBe('function'); diff --git a/src/utilities/create-game.ts b/src/utilities/create-game.ts index c24bf9d6..7e9d91c8 100644 --- a/src/utilities/create-game.ts +++ b/src/utilities/create-game.ts @@ -4,6 +4,7 @@ import { createCanvas, createRenderContext, RenderContext, + RenderContextOptions, } from '../rendering/index.js'; import { ContainerResizeSync, @@ -11,18 +12,44 @@ import { } from './create-container-resize-sync.js'; import { Game } from './game.js'; +/** + * Options for `createGame`. + */ +export interface CreateGameOptions { + /** + * Options forwarded to `createRenderContext` for the game's render context, + * e.g. `{ maxPixelRatio: 1.5 }` to cap the render resolution on high-DPI + * displays. + */ + renderContext?: RenderContextOptions; +} + +const defaultCreateGameOptions = { + renderContext: {}, +}; + /** * Creates a new game instance with the specified container ID. * @param containerId - The ID of the container element where the game will be rendered. + * @param options - Options for the game, such as the `RenderContextOptions` to create its render context with. * @returns An object containing the game instance, ECS world, render context, time, and the resize sync keeping the render context's canvas sized to the container. + * @throws An error if no DOM element with `containerId` exists. */ -export function createGame(containerId: string): { +export function createGame( + containerId: string, + options: CreateGameOptions = {}, +): { game: Game; world: EcsWorld; renderContext: RenderContext; time: Time; resizeSync: ContainerResizeSync; } { + const { renderContext: renderContextOptions } = { + ...defaultCreateGameOptions, + ...options, + }; + const time = new Time(); const world = new EcsWorld(); const container = document.getElementById(containerId); @@ -33,7 +60,7 @@ export function createGame(containerId: string): { const canvas = createCanvas(container); - const renderContext = createRenderContext(canvas); + const renderContext = createRenderContext(canvas, renderContextOptions); const resizeSync = createContainerResizeSync(container, [renderContext]);