Let the example-game smokes read texts in the game again
Since 89aa92d (#169) a text in the game is drawn as a picture of its
glyphs on a sprite mesh, not as a Pixi `Text`. The test hook
`__gm.locate` only looked for `Text` nodes, so every game text was
invisible to it. The smokes have been red in CI since then (tasks 385
to 395): 5 checks in jumprun, 1 in tetris. The e2e part stayed green,
because it reads editor labels only.
- `textPicture.ts` remembers, per mesh, the text a `TextPicture`
shows, and hands it out through `pictureText(node)`.
- `textProbe.eachText` reads both kinds and passes the text as a
string instead of the Pixi node. `locate`, `locateAll` and
`overlayTexts` use that string.
Tests:
- ./check.sh: 195 files, 2815 tests, CHECK EXIT 0
- smoke-jumprun: ALL PASSED, 24 checks
- smoke-tetris: ALL PASSED, 19 checks
- full e2e run (local): ALL PASSED, 1580 checks, the sum is unchanged
although `locate` now also sees texts in the scene
- counter-check: CI task 395 with the old code, same checks, 6 red
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
cab1c0177f
commit
dded62b9f9
3 files changed
+26
-8
No files matched your search
@@ -64,8 +64,8 @@ export function uiHooks({ app, state, editor }: HookContext) {
|
||||
*/
|
||||
const overlayTexts = (): string[] => {
|
||||
const out: string[] = [];
|
||||
eachText(ui().overlay, (node) => {
|
||||
if (node.text) out.push(node.text);
|
||||
eachText(ui().overlay, (text) => {
|
||||
if (text) out.push(text);
|
||||
});
|
||||
return out;
|
||||
};
|
||||
|
||||
+12
-6
@@ -1,5 +1,6 @@
|
||||
import { type Application, type Container, Graphics, Rectangle, Text } from 'pixi.js';
|
||||
import { Button, Widget } from '../ui';
|
||||
import { pictureText } from '../viewport/textPicture';
|
||||
|
||||
/** The global box of a widget: where it starts, and how large it is. */
|
||||
export function rectOf(widget: { getGlobalPosition(): { x: number; y: number }; w: number; h: number }): {
|
||||
@@ -36,7 +37,7 @@ export interface IconHit {
|
||||
* it the sign-in screen needs, since it exists before there is an editor to ask.
|
||||
*/
|
||||
export function textProbe(app: Application): {
|
||||
eachText: (root: Container, visit: (node: Text, point: { x: number; y: number }) => void) => void;
|
||||
eachText: (root: Container, visit: (text: string, point: { x: number; y: number }) => void) => void;
|
||||
locateAll: (text: string) => { x: number; y: number }[];
|
||||
locate: (text: string, last?: boolean) => { x: number; y: number } | null;
|
||||
locateIcons: (name: string) => IconHit[];
|
||||
@@ -76,14 +77,19 @@ export function textProbe(app: Application): {
|
||||
};
|
||||
walk(root, null, null);
|
||||
};
|
||||
const eachText = (root: Container, visit: (node: Text, point: { x: number; y: number }) => void): void => {
|
||||
const eachText = (
|
||||
root: Container,
|
||||
visit: (text: string, point: { x: number; y: number }) => void,
|
||||
): void => {
|
||||
eachShown(root, (node, clip) => {
|
||||
if (!(node instanceof Text)) return false;
|
||||
// A text object in the game is a picture of its glyphs (#169), not a Pixi `Text`.
|
||||
const text = node instanceof Text ? node.text : pictureText(node);
|
||||
if (text === undefined) return false;
|
||||
// Global bounds, not `width`/`height`: those are unscaled, and inside the graph canvas
|
||||
// (which is scaled by the zoom) that put the point right of the text it was meant to hit.
|
||||
const box = node.getBounds();
|
||||
const point = { x: box.x + box.width / 2, y: box.y + box.height / 2 };
|
||||
if (!clip || clip.contains(point.x, point.y)) visit(node, point);
|
||||
if (!clip || clip.contains(point.x, point.y)) visit(text, point);
|
||||
return true;
|
||||
});
|
||||
};
|
||||
@@ -105,8 +111,8 @@ export function textProbe(app: Application): {
|
||||
/** Centres of every visible text equal to `text`, in drawing order (the topmost last). */
|
||||
const locateAll = (text: string): { x: number; y: number }[] => {
|
||||
const hits: { x: number; y: number }[] = [];
|
||||
eachText(app.stage, (node, point) => {
|
||||
if (node.text === text) hits.push(point);
|
||||
eachText(app.stage, (shown, point) => {
|
||||
if (shown === text) hits.push(point);
|
||||
});
|
||||
return hits;
|
||||
};
|
||||
|
||||
@@ -70,6 +70,17 @@ export function sharpCoverage(
|
||||
return { data, width: w, height: h };
|
||||
}
|
||||
|
||||
/**
|
||||
* The text each picture shows, by its mesh. The mesh is a sprite quad and carries no text, so the test
|
||||
* hook reads a game text through here – otherwise `locate` finds only the editor's own labels.
|
||||
*/
|
||||
const shownTexts = new WeakMap<Mesh, string>();
|
||||
|
||||
/** The text a node on screen draws as a `TextPicture`, or undefined for any other node. */
|
||||
export function pictureText(node: object): string | undefined {
|
||||
return shownTexts.get(node as Mesh);
|
||||
}
|
||||
|
||||
export class TextPicture {
|
||||
readonly mesh: Mesh = newSpriteMesh();
|
||||
private readonly style = new TextStyle({ fontFamily: Theme.font, fontSize: 32, fill: 0xffffff });
|
||||
@@ -88,6 +99,7 @@ export class TextPicture {
|
||||
const color = Number(item.props.color ?? 0xffffff);
|
||||
const resolution = textResolution(scale, item.fontSmooth);
|
||||
const key = JSON.stringify([text, family, size, color, resolution]);
|
||||
shownTexts.set(this.mesh, text);
|
||||
const stale = key !== this.rasterKey;
|
||||
const previous = stale ? this.texture : null;
|
||||
if (stale) {
|
||||
|
||||
Reference in new issue
Block a user