804a97b0a9f867816c409ce8ba037e93fd78c195
9
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
1665576fb9 |
Let a pixel perfect camera draw on the game view's pixel grid
A camera that follows smoothly stands between pixels most of the time, and pixel art then wanders between them. The new camera switch draws what the camera shows on the game view's own grid, while positions in the model keep their fractions. - `Pixel perfect` is a camera row, off by default. It works at every fixed game scale and at Auto, but not at Fit and not in the Scene tab, where one works with the fractions. - The grid has one step per device pixel of a world unit. At 100% that is whole units, at 500% fifths. The view carries it (`View.grid`), so no view draws with the grid of another. - The view looks from a point that puts the world's origin on a whole device pixel (`lookFrom`). This also covers odd camera sizes and views that cut the picture to an odd size. - Sprites, texts and tilemaps snap their top left corner when they are unturned, so an odd size lands on whole pixels too. A turned object snaps its pivot (`snapPlacement` in `pixelGrid.ts`). - Under a camera shader the picture is taken the same way, and the objects in it use the picture's grid. With Camera pixels that is one step per camera pixel. - An overlay camera has its own switch and snaps only while the main picture is at a fixed scale. - Particles are drawn where they are. - New test hook `__gm.gameDrawnRect(id)` reads where a sprite's picture is drawn, which `gameRect` (the model's) cannot tell. Found: the e2e fixture at 300% drew the crate at y 205.5 device pixels without moving the camera, because the view cut to an odd height puts its centre on half a pixel. With the switch on it is at 206. Tests: - Gate: 237 files, 3587 tests (+10 in pixel-grid.test.ts) - e2e 2,3: ALL PASSED, 75 checks (+1, 3j) - Counter-checks: without the origin snap 3 unit tests fail, with the centre snapped instead of the corner 2 fail Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
70f3608733 |
Let Mouse position and Is hovered see through any camera (#304)
An overlay camera from #302 can stand elsewhere and zoom differently.
A HUD button only it shows lay somewhere else in the world than the
mouse of the main camera, so Is hovered never found it. Both nodes now
take a camera and see the mouse through that camera's picture.
- New input `camera` on Mouse position and Is hovered. Left open, both
read the main camera exactly as before.
- `pointerThrough` in core/properties/camera is the rule as a pure
function. It fits the camera's picture into the main one and centres
it, the same way overlayView draws it.
- The game view reports where the mouse lies on the main picture, as a
part of its width and height. The world turns that into a camera's
view, so a main camera on the move does not drag the mouse across
the HUD while the mouse stands still.
- `in view` is true only on the camera's own picture. A camera that is
not drawn (switched off, a second plain one, not a camera) never has
the mouse on it.
- On click and Mouse button stay with the main camera, as agreed in
the issue.
Also fixes a unit test that
|
||
|
|
b2326085ed |
Show each camera only its layers and lay overlay cameras on top (#302)
A camera could only show the whole scene, so a HUD drawn in the world would run through a camera shader (#299) with everything else. Render layers say which cameras see an object, and overlay cameras draw their own picture over the main one. - The project has a list of up to 16 render layers, edited in Project settings next to the collision layers. It is a list of its own, read by the same rules (`layerNamesOf`). - Sprites, texts, tilemaps, particle sources and containers carry a row `Render layer` under their switches. Its default `Inherit` takes the parent's layer, at the top the first one. - A camera has `Sees` (all layers by default) and `Overlay`. The main camera is the first one switched on without Overlay, so two plain cameras still draw through the first one only. - Every view through a camera keeps only what that camera sees and lays one see-through view per overlay camera over itself. That view is a `WorldViewport` of its own, handed its view and frame from below. - An overlay is fitted into the main camera's whole picture without distortion. One of the same size and zoom covers it exactly. - `z` sorts within one camera only. The pictures lie on top of each other as a whole, which is what a shader per camera (#299) needs. - The Game tab's preview now collects the scene once per redraw. Before it collected it twice, with the cameras it would have been five times. - `SetCollisionLayersCommand` became `SetLayerNamesCommand`, which takes the list it sets. Follow-ups: #304 (the mouse through a chosen camera) and #305 (change the render layer at run time). Tests: - Gate: 226 files, 3487 tests (3480 + 7) - e2e 63 (new): 4 checks. A green overlay sprite at z -10 covers a red main sprite at z 1000, in the preview and in the running game. A `UI` sprite in front of the main camera and a `Default` one in front of the overlay stay away. Without Overlay the second camera draws nothing. The settings dialog adds a render layer at the end. - Counter-checks: inheriting from parents switched off fails 1 unit test. Overlays never drawn fails the two overlay checks in e2e 63. - e2e total 1720 (1716 + 4), computed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
c88f57cf0a |
Let a project choose how its game link is zoomed (#279)
The page of a released game always drew the camera at Auto, the
largest whole step that fits. A camera of 300x180 in a window of
1280x800 stands at step 4 and leaves a border left and right. The
issue asks for Fit next to it. Answered there: a setting of its own
rather than the scale of the Game tab, and no switch for the player.
- The project has a field gameLinkZoom, 'auto' or 'fit'. A document
without it reads as 'auto', so nothing changes for a project that
is there already. No format bump.
- Project settings has a row "Link zoom" with a menu of Auto and
Fit. It is an undo step of its own, SetGameLinkZoomCommand.
- GameScreen hands the field to cameraView where it passed a fixed
'auto'.
- gameScale, the scale of the Game tab, is not touched and does not
reach the link.
- The released copy is frozen, so the change reaches the link only
with "Update release".
Decided here:
- The row is called "Link zoom", not "Game link zoom" as proposed.
The longer label is wider than the label column of the dialog and
pushed its button 12 px out of line with the others.
- The undo step keeps the name "Game link zoom", which is what the
tooltip of the undo button shows.
- A note under the row says "The released game at the largest whole
step that fits, or filling the window."
Tests:
- Gate on melektaus-atx: 211 files, 3227 tests (3219 + 8), exit 0
- e2e 60 on melektaus-atx: 19 checks, all pass
- e2e 35,46,59,60: 74 checks, all pass
- No full run and no smokes. The sum of 1671 (1668 + 3) is reckoned,
1668 is what CI counted for
|
||
|
|
32a0af6e2c |
Put the text and boxes of the interface on device pixels
On a screen scaled to 125 % or 150 % the editor looked soft beside a page the browser draws itself. The layout counts in whole CSS pixels, and most of those fall between two device pixels there. The GPU averages both: text loses its edges, and a 1 px line becomes a pixel and a half of mixed colour. Now text and boxes are rounded to device pixels, and a line is one device pixel wide. - ui/pixelGrid.ts holds the rule. crispText() makes a text with Pixi's roundPixels, crisp() does the same for a Graphics of upright boxes, and hairline() is the width of one device pixel. - Every text of the interface is made with crispText() instead of new Text(). - crisp() only rounds where the scale is not a whole number. At 100 % and 200 % nothing changes. - drawControlBox, drawHeaderBar and drawSectionBand draw their lines a hairline wide and flush with the edge. The widgets that draw lines of their own do the same. - Two bands share exactly the line they draw, and a column still ends on a whole pixel. - The stage is nudged by 1/32 px where the scale is not whole. At 150 % every odd pixel is an exact half, and without the nudge one band came out a row shorter than its neighbours. - Curves, icons and everything of the game stay unrounded. Rounding their points would bend them. - The harness takes deviceScaleFactor as an option, and the hooks deviceColumn, edgeContrast and pixelGrid read the frame as the screen gets it. Not in here: the subpixel smoothing a browser gives its own text. Pixi draws a text into a transparent picture first, and there only greyscale smoothing is possible. Measured at 150 %, edge contrast before and after: top bar 31.5 to 47.8, the lists on the left 68.6 to 86.4, inspector 27.6 to 38.4. At 100 % it is 46.5, 90.1 and 37.9, and the picture there is the same as before pixel for pixel, apart from the fps display. Tests: - Gate on melektaus-atx: 206 files, 3114 tests (3104 + 10), exit 0 - Full run on melektaus-atx: 1655 of 1656 checks passed, smokes 24/19/21. The one that failed was a condition of the new step 62 itself: it scrolled an inspector that had nothing to scroll. The step now makes the window lower for that reading. - e2e 61,62 after that fix: 14 checks. The full run was not repeated, and CI is switched off for now. - Ten unit tests, nine in tests/ui/pixel-grid.test.ts - Eight e2e checks in the new step 62, which runs at 150 % - Eleven unit counter-probes fail tests - Four browser counter-probes fail their checks. They ran before the fix to step 62 and were not repeated for it. - Screenshots looked at for 125 % and 150 % Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
1aa824ecb4 |
Keep objects through a Load scene with a switch (#165)
A Load scene destroyed every object of the old scene. A player, a HUD or a music object had to be built again in each scene, with its state pushed through shared variables. A top-level object with the new switch Keep on scene load now travels into the next scene as the same entity, with everything below it. - The switch is a common property right under Enabled, off by default, on the asset and on a placed object. The inspector shows the row on top-level objects of a scene only, as answered in the issue. - runtime/keptObjects.ts holds the rules as pure functions. keptEntities picks what travels, objectsToBring copies the stored objects as the run parents them, and bringObjects puts them into the next scene. - World.instantiate takes a handed entity as it is instead of making one. The next syncToModel then writes the run's values into the scene. - BehaviorSystem.release takes the running instances out before the old world is destroyed, and the next system adopts them. They hear no On destroy and no second On start, and their sounds play on. - PhysicsSystem.resume gives a dynamic body the velocity and spin it had. - Duplicates: the object brought along wins. The next scene does not build its own object with the same id, which is the way back into the scene the object came from. - Decided here: the next scene does not build a top-level object of the same asset that is kept itself either. The issue only names the id. With the id alone a level that places its own player would hold two after a swap. - Decided here: kept objects land behind the scene's own, the switch is no animation row, and collisions are found anew. A finding, fixed along with it: Load scene did not build a scene as it is stored, but as the run had left it. A moved object stayed moved and a destroyed one stayed gone. The way back into a scene needs a clean build. - GameRun copies a scene's objects and groups before it first builds it and puts them back before it builds it again. - GameRun.end puts every such scene back. The player calls it on stop, so spawned objects no longer stay for "Click to play again". - The consequence: an edit made in a scene during Play is gone the next time that scene is loaded. Tests: - Gate: 199 files, 2944 tests (2929 + 15), exit 0 - e2e, full run: 1625 checks passed (1619 + 6), exit 0 - Smokes: 24 / 19 / 21, exit 0 - 15 unit tests in tests/core/keep-on-scene-load.test.ts - Six e2e checks in module 35: the switch is set with a real mouse, the child has no row, the object is in the new scene, its counter still reads 1, its sound plays on, and the stored scenes are unchanged after Stop - Eleven tests with fixed row lists only count the new schema row - 16 unit counter-probes all fail tests, run on stein2. One did not fail at first (same id not left out), it has its own test now. - The browser counter-probe fails 4 of the 6 new checks - Not looked at in the browser: the switch as a port of the Properties node, and a released game with a kept object Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
cab1c0177f |
Show only the game view in fullscreen, in the editor and the link (#267)
A game is easier to judge and to play without the editor around it. The Game tab and the game link now both have a button that puts the game view itself into fullscreen, not just the browser window. - The Game tab header has a fullscreen button on the right. It hides toolbar, panels, tab headers and console and keeps the zoom from the picker. A running game keeps running. - A corner button at the top right, or Esc, leaves fullscreen and puts the view back into the tab where it was. - The game link shows the same corner button. Over a running game it hides after 2 s without mouse movement and returns on the next move. - A click on the corner button never reaches the game. - Stop ends fullscreen. Stop switches back to the Scene tab, and any switch away from the Game tab ends fullscreen, so no empty screen is left behind. - src/viewport/fullscreen.ts wraps the Fullscreen API and holds the rule for when the button shows. FullscreenCorner.ts is the button used by both pages. Two SVG icons are new. - Editor.showGameFullscreen moves the GameViewport into the root while fullscreen is on. Tests: - ./check.sh: 195 files, 2815 tests, exit 0 (3 new unit tests). - e2e 60: 2 new checks for the corner button on the game link. - e2e 61 (new): 4 checks for fullscreen in the editor. - Full e2e run (local): ALL PASSED, 1580 checks. - Counter-checks fail as expected: rule without "no game running", no stopPropagation (60h), no end on tab switch (61c), no reparenting of the view (61a). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
24d360aeec |
Add zoom steps and a larger start caption to the game link (#266)
The released game only ever zoomed in whole steps up to 400%, so a small camera on a large screen stayed far smaller than it could be. The "Click to play" caption was drawn in the 11 px panel title size and was easy to miss. - GAME_SCALE_STEPS gains 300%, 500% and 600%. The game tab picker, Auto in the editor and the game link all read the same list. - The start caption takes its size from the new Theme.captionSize (16) instead of a constant in GameScreen. - The player page keeps the default arrow cursor at all times. It no longer switches to the hand over the caption. Tests: - view.test: Auto for 320x180 in 2000x2000 is now 6, with cases for 3 and 5. play-settings.test takes 2.5 and 8 as invalid steps. - e2e 60 checks the caption against the panel title size and the arrow cursor before and after the start. Module 03 follows the new step list. - ./check.sh 2808 tests, CHECK EXIT 0 (before the last Theme tweak, which changes no test). Full e2e run ALL PASSED, 1574 checks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
4194f3e150 |
Share a link that plays the game without the editor (#266)
A project could only be played inside the editor, behind the login.
To show a game to someone else there was no way short of an account.
A release freezes the saved project under a random token and serves
it on a page that holds nothing but the game view.
- Menu -> Release... opens a dialog with the state of the release,
the link with Copy link and Open, Publish or Update release, and
Take down behind a confirmation.
- Publish saves first and refuses while anything is unsaved, like
snapshots. Updating keeps the link. Take down kills it, and a new
Publish hands out a new token.
- The link is play.html?game=<32 hex>. The page needs no session,
paints Theme.colors.background, zooms with Auto and waits for
"Click to play" so that sound and keyboard focus are allowed.
- The game always starts in the start scene. Persistent variables
are stored per link in the browser. Plugins are not loaded, and the
dialog warns about them and about a project without a scene.
- GET ?r=releases/{token} is the only route besides auth/* that works
without a login. The public answer carries the token as project.id,
so the project id stays hidden. Release files are written with 0640
and nginx does not serve them directly (measured 403).
- Starting, the frame and Load scene moved into runtime/GameRun.ts.
Play in the editor and the play page run through the same code.
- WorldViewport moved to src/viewport/ and no longer needs the
EditorState. Pointer forwarding (gamePointer.ts) and setting up
textures, fonts and sounds (projectFiles.ts) exist only once.
- Vite builds two pages. The play page loads 4 kB of its own code
plus the shared chunk (Pixi, runtime, drawing, 653 kB) and Rapier,
without the editor.
Found on the way, not fixed here: data/projects/<id>.json answers 200
without a login because the files are 0644.
Tests:
- ./check.sh: 194 files, 2808 tests, exit 0.
- tests/php/releases.test.php: 20 checks, also added to CI.
- New e2e module 60 with 10 checks, played in a browser without a
session. Module 46 counts the menu entries and was updated.
- Full e2e run: 1572 checks passed.
- Counter-checks: 3 unit, 2 PHP and 3 browser mutations all fail.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|