Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions docs/getting-started/pattern-demo.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,17 @@ This walkthrough recreates the `Pattern Demo` scene in PhaserForge. It assumes y
## Before You Start

- Open PhaserForge and sign in if needed.
- If you are continuing from older work, reset to a new empty scene from `Project -> Startup & Reset`.
- If you are continuing from older work, reset to a new empty scene from the Project Tree in the upper left. Click `Manage -> Create New`.
- Stay in the same signed-in project flow you established during cloud account setup.
- Set the scene world size to `800 x 600` before you begin placing ships.
- Set the scene world size to `800 x 600` and then the `Fit` button to recenter the canvas to full size before you begin placing ships.

<img src="../assets/screenshots/playwright/world-size.png" alt="Assets dock add menu with the demo pack import option" style="display: block; margin: 0 auto; width: 500px; max-width: 100%; height: auto;" />

<p align="center"><em>Figure 1. World Size dimensions (in pixels).</em></p>

Success check:
- The canvas is empty and the scene graph does not show leftover sprites or formations.
- The World Size is `800 x 600`, as shown in the Viewport panel (Figure 1).
- The World Size is `800 x 600`, as shown in the Viewport panel (Figure 1), and the viewport is fully fit to the canvas.

## Import the Demo Pack Assets

Expand Down
5 changes: 3 additions & 2 deletions src/phaser/EditorScene.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ import {
import { registerSceneGetter, unregisterSceneGetter } from '../testing/testBridge';
import { resolvePointerModifier } from './inputModifiers';
import { getPreferredTextResolution } from './textResolution';
import { applyProjectTextureFilter } from './textureFiltering';
import { applyProjectCanvasRenderMode, applyProjectTextureFilter } from './textureFiltering';
import type { ViewState } from '../util/viewStateStorage';

const PLACEHOLDER_TEXTURE_KEY = '__phaserforge:placeholder-1x1';
Expand Down Expand Up @@ -155,7 +155,7 @@ export class EditorScene extends Phaser.Scene {

create(): void {
this.cameras.main.setBackgroundColor('#0c0f1a');
this.cameras.main.roundPixels = true;
applyProjectCanvasRenderMode(this.game.canvas, this.cameras.main, this.project ? getProjectRenderMode(this.project) : 'pixel-art');
this.bindSceneListeners();
EventBus.emit('current-scene-ready', this);
this.lastViewportSize = { width: this.scale.width, height: this.scale.height };
Expand Down Expand Up @@ -840,6 +840,7 @@ export class EditorScene extends Phaser.Scene {
this.clearScene();
this.project = project;
this.mode = mode;
applyProjectCanvasRenderMode(this.game.canvas, this.cameras.main, project ? getProjectRenderMode(project) : 'pixel-art');
this.varsService = new BasicVarsService({ counters: project?.counters, collections: project?.collections });
this.compiled = compileScene(sceneSpec, { opRegistry: this.opRegistry, vars: this.varsService });
this.referenceCompiled = referenceSceneSpec ? compileScene(referenceSceneSpec, { opRegistry: this.opRegistry, vars: this.varsService }) : undefined;
Expand Down
5 changes: 3 additions & 2 deletions src/phaser/GameScene.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import type { TriggerZoneSpec } from '../model/types';
import { assetSourceKey, resolveAssetSourceUrl } from '../cloud/assetUrls';
import type { ViewState } from '../util/viewStateStorage';
import { getProjectRenderMode } from '../model/projectPixelScale';
import { applyProjectTextureFilter } from './textureFiltering';
import { applyProjectCanvasRenderMode, applyProjectTextureFilter } from './textureFiltering';

const PLACEHOLDER_TEXTURE_KEY = '__phaserforge:placeholder-1x1';
const EMPTY_SCENE_SPEC: SceneSpec = { id: '', entities: {}, groups: {}, attachments: {}, behaviors: {}, actions: {}, conditions: {} };
Expand Down Expand Up @@ -155,7 +155,7 @@ export class GameScene extends Phaser.Scene {
create(): void {
// Match editor canvas background so offscreen space is consistent between edit and preview.
this.cameras.main.setBackgroundColor('#0c0f1a');
this.cameras.main.roundPixels = true;
applyProjectCanvasRenderMode(this.game.canvas, this.cameras.main, this.project ? getProjectRenderMode(this.project) : 'pixel-art');
this.audioService = new BasicAudioService(this.sound as any);
this.inputService = new BasicInputService({
getGamepads: () => (typeof navigator !== 'undefined' && navigator.getGamepads ? Array.from(navigator.getGamepads()) : []),
Expand Down Expand Up @@ -204,6 +204,7 @@ export class GameScene extends Phaser.Scene {
public loadSceneSpec(projectOrScene: ProjectSpec | SceneSpec, maybeScene?: SceneSpec): void {
const project = maybeScene ? (projectOrScene as ProjectSpec) : undefined;
this.project = project;
applyProjectCanvasRenderMode(this.game.canvas, this.cameras.main, project ? getProjectRenderMode(project) : 'pixel-art');
this.varsService = new BasicVarsService({ counters: project?.counters, collections: project?.collections });
const sceneSpec = (maybeScene ?? projectOrScene) as GameSceneSpec;
if (project) {
Expand Down
23 changes: 23 additions & 0 deletions src/phaser/textureFiltering.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,16 @@ type TextureLike = {
setFilter?: (mode: number) => void;
};

type CanvasLike = {
style?: {
imageRendering?: string;
};
};

type CameraLike = {
roundPixels?: boolean;
};

type TextureManagerLike = {
exists: (key: string) => boolean;
get: (key: string) => TextureLike | null | undefined;
Expand All @@ -22,3 +32,16 @@ export function applyProjectTextureFilter(
const texture = textures.get(key);
texture?.setFilter?.(renderMode === 'smooth-2d' ? LINEAR_FILTER_MODE : NEAREST_FILTER_MODE);
}

export function applyProjectCanvasRenderMode(
canvas: CanvasLike | null | undefined,
camera: CameraLike | null | undefined,
renderMode: ProjectRenderMode = 'pixel-art',
): void {
if (canvas?.style) {
canvas.style.imageRendering = renderMode === 'smooth-2d' ? 'auto' : 'pixelated';
}
if (camera) {
camera.roundPixels = renderMode !== 'smooth-2d';
}
}
22 changes: 21 additions & 1 deletion tests/phaser/textureFiltering.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, expect, it, vi } from 'vitest';
import { applyProjectTextureFilter } from '../../src/phaser/textureFiltering';
import { applyProjectCanvasRenderMode, applyProjectTextureFilter } from '../../src/phaser/textureFiltering';

describe('applyProjectTextureFilter', () => {
it('sets the filter mode to nearest for pixel-art projects', () => {
Expand Down Expand Up @@ -31,4 +31,24 @@ describe('applyProjectTextureFilter', () => {

expect(setFilter).not.toHaveBeenCalled();
});

it('uses browser smoothing and subpixel camera placement for smooth-2d projects', () => {
const canvas = { style: { imageRendering: 'pixelated' } };
const camera = { roundPixels: true };

applyProjectCanvasRenderMode(canvas, camera, 'smooth-2d');

expect(canvas.style.imageRendering).toBe('auto');
expect(camera.roundPixels).toBe(false);
});

it('uses pixelated canvas scaling and rounded camera positions for pixel-art projects', () => {
const canvas = { style: { imageRendering: 'auto' } };
const camera = { roundPixels: false };

applyProjectCanvasRenderMode(canvas, camera, 'pixel-art');

expect(canvas.style.imageRendering).toBe('pixelated');
expect(camera.roundPixels).toBe(true);
});
});
Loading