Let a tile report a touch instead of blocking it (#160)
A tile could be solid, one-way and scaled, but not report without blocking. Water, lava, spikes or a goal zone needed a trigger object of their own laid over the map. Now a tile has a switch Trigger, and the collision events name the tile and the cell they met. - A tile has the switch "Trigger (reports, never blocks)" under Solid. Its cells hold nothing up: no Move, no falling body, no ray. They raise On collision enter, stay and exit with the tilemap as other. - The three events have the outputs tile, cell x and cell y. Without a tile, tile is empty and the cell is -1, -1. - The three events have the input per cell. Off, the default, gives one event per area: cells of the same tile that touch side by side. On gives one event per cell. - The scene view outlines trigger cells in yellow like a trigger collider, merged to rectangles but never with solid cells. - Is touching with the map counts trigger tiles, and Set tile works while the game runs. - Both systems find the touches with the same overlap check in runtime/systems/tileTriggers.ts, not through the physics engine. Decided here: - Trigger wins over Solid. A tile with both does not block, as with a collider. - Water beside lava is two areas, and so are two ponds that touch only at a corner. - An area names the cell nearest to the centre of the collider, and on exit the cell it named last. - The solid cells of a map still do not name their tile. - Normal, impulse and point are 0, as with a trigger collider. - Collision scale and outlines shape a trigger cell like a solid one. One-way, friction and bounciness have no effect. Tests: - Gate on stein2: 205 files, 3104 tests (3067 + 37), exit 0 - e2e 39,40: 106 checks. e2e 1-8,39-43: 490 checks. Both on melektaus-atx. No full run, the sum of 1648 (1645 + 3) is reckoned and CI measures it. - 37 unit tests in tests/core/tile-triggers.test.ts, nearly all against both systems - Three e2e checks with a real mouse in block 40l-iv - 24 unit counter-probes fail tests. Two did not at first, tests were added for them. - Two browser counter-probes fail their checks - Two screenshots looked at (yellow outline, the node with per cell) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
2ff3a78607
commit
275e50af01
20 files changed
+1161
-57
No files matched your search
@@ -23,7 +23,9 @@ function sameSolids(a: TilemapSolids, b: TilemapSolids): boolean {
|
||||
sameRects(a.solids, b.solids) &&
|
||||
samePolygons(a.outlines, b.outlines) &&
|
||||
sameRects(a.oneWay.solids, b.oneWay.solids) &&
|
||||
samePolygons(a.oneWay.outlines, b.oneWay.outlines)
|
||||
samePolygons(a.oneWay.outlines, b.oneWay.outlines) &&
|
||||
sameRects(a.triggers.solids, b.triggers.solids) &&
|
||||
samePolygons(a.triggers.outlines, b.triggers.outlines)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -39,7 +41,7 @@ export class TilemapSolidOutlines {
|
||||
*/
|
||||
private readonly boundaries = new Map<
|
||||
string,
|
||||
{ from: TilemapSolids; solid: BoundaryLine[]; oneWay: BoundaryLine[] }
|
||||
{ from: TilemapSolids; solid: BoundaryLine[]; oneWay: BoundaryLine[]; triggers: BoundaryLine[] }
|
||||
>();
|
||||
/** What each map's last draw tinted, or nothing: the painted layer's part, while it is selected. */
|
||||
private readonly tinted = new Map<string, TilemapSolids>();
|
||||
@@ -64,7 +66,9 @@ export class TilemapSolidOutlines {
|
||||
oneWay: CellRect[];
|
||||
oneWayOutlines: Vec2[][];
|
||||
boundary: BoundaryLine[];
|
||||
filled: { rects: CellRect[]; outlines: number; oneWay: CellRect[] } | null;
|
||||
triggers: CellRect[];
|
||||
triggerOutlines: Vec2[][];
|
||||
filled: { rects: CellRect[]; outlines: number; oneWay: CellRect[]; triggers: CellRect[] } | null;
|
||||
} | null {
|
||||
const node = item && this.nodes.get(item.id);
|
||||
if (!node || !item.tilemap) return null;
|
||||
@@ -75,6 +79,7 @@ export class TilemapSolidOutlines {
|
||||
rects: tinted.solids.map((rect) => ({ ...rect })),
|
||||
outlines: tinted.outlines.length,
|
||||
oneWay: tinted.oneWay.solids.map((rect) => ({ ...rect })),
|
||||
triggers: tinted.triggers.solids.map((rect) => ({ ...rect })),
|
||||
}
|
||||
: null,
|
||||
visible: node.visible,
|
||||
@@ -82,6 +87,10 @@ export class TilemapSolidOutlines {
|
||||
outlines: item.tilemap.outlines.map((polygon) => polygon.outline.map((p) => ({ ...p }))),
|
||||
oneWay: item.tilemap.oneWay.solids.map((rect) => ({ ...rect })),
|
||||
oneWayOutlines: item.tilemap.oneWay.outlines.map((polygon) => polygon.outline.map((p) => ({ ...p }))),
|
||||
triggers: item.tilemap.triggers.solids.map((rect) => ({ ...rect })),
|
||||
triggerOutlines: item.tilemap.triggers.outlines.map((polygon) =>
|
||||
polygon.outline.map((p) => ({ ...p })),
|
||||
),
|
||||
boundary: this.boundaryOf(item).solid.map((line) => ({
|
||||
points: line.points.map((p) => ({ ...p })),
|
||||
closed: line.closed,
|
||||
@@ -89,7 +98,11 @@ export class TilemapSolidOutlines {
|
||||
};
|
||||
}
|
||||
|
||||
private boundaryOf(item: DrawItem): { solid: BoundaryLine[]; oneWay: BoundaryLine[] } {
|
||||
private boundaryOf(item: DrawItem): {
|
||||
solid: BoundaryLine[];
|
||||
oneWay: BoundaryLine[];
|
||||
triggers: BoundaryLine[];
|
||||
} {
|
||||
const from: TilemapSolids = item.tilemap!;
|
||||
let boundary = this.boundaries.get(item.id);
|
||||
if (!boundary || !sameSolids(boundary.from, from)) {
|
||||
@@ -97,6 +110,7 @@ export class TilemapSolidOutlines {
|
||||
from,
|
||||
solid: solidBoundary(from.solids, from.outlines),
|
||||
oneWay: solidBoundary(from.oneWay.solids, from.oneWay.outlines),
|
||||
triggers: solidBoundary(from.triggers.solids, from.triggers.outlines),
|
||||
};
|
||||
this.boundaries.set(item.id, boundary);
|
||||
}
|
||||
@@ -113,8 +127,10 @@ export class TilemapSolidOutlines {
|
||||
const rects = item.tilemap?.solids ?? [];
|
||||
const outlines = item.tilemap?.outlines ?? [];
|
||||
const oneWay = item.tilemap?.oneWay ?? { solids: [], outlines: [] };
|
||||
const triggers = item.tilemap?.triggers ?? { solids: [], outlines: [] };
|
||||
this.tinted.delete(item.id);
|
||||
if (!show || rects.length + outlines.length + oneWay.solids.length + oneWay.outlines.length === 0) {
|
||||
const shapes = [rects, outlines, oneWay.solids, oneWay.outlines, triggers.solids, triggers.outlines];
|
||||
if (!show || shapes.every((list) => list.length === 0)) {
|
||||
if (node) node.visible = false;
|
||||
return;
|
||||
}
|
||||
@@ -132,7 +148,12 @@ export class TilemapSolidOutlines {
|
||||
const color = Theme.colors.collider;
|
||||
const boundary = this.boundaryOf(item);
|
||||
// Filled piece by piece, but stroked only along the outer edges, so no seam shows (#173).
|
||||
const fill = (rects: readonly CellRect[], outlines: readonly CellPolygon[], alpha: number) => {
|
||||
const fill = (
|
||||
rects: readonly CellRect[],
|
||||
outlines: readonly CellPolygon[],
|
||||
alpha: number,
|
||||
color: number = Theme.colors.collider,
|
||||
) => {
|
||||
if (rects.length + outlines.length === 0) return;
|
||||
for (const rect of rects) {
|
||||
const corner = cellOrigin(item.props, rect.x, rect.y);
|
||||
@@ -145,7 +166,11 @@ export class TilemapSolidOutlines {
|
||||
);
|
||||
node.fill({ color, alpha });
|
||||
};
|
||||
const stroke = (lines: readonly BoundaryLine[], lineWidth: number) => {
|
||||
const stroke = (
|
||||
lines: readonly BoundaryLine[],
|
||||
lineWidth: number,
|
||||
color: number = Theme.colors.collider,
|
||||
) => {
|
||||
if (lines.length === 0) return;
|
||||
for (const line of lines)
|
||||
node.poly(
|
||||
@@ -158,9 +183,12 @@ export class TilemapSolidOutlines {
|
||||
this.tinted.set(item.id, filled);
|
||||
fill(filled.solids, filled.outlines, COLLIDER_FILL_ALPHA);
|
||||
fill(filled.oneWay.solids, filled.oneWay.outlines, COLLIDER_FILL_ALPHA / 2);
|
||||
fill(filled.triggers.solids, filled.triggers.outlines, COLLIDER_FILL_ALPHA, Theme.colors.trigger);
|
||||
}
|
||||
stroke(boundary.solid, width);
|
||||
stroke(boundary.oneWay, width / 2);
|
||||
// Trigger tiles in the trigger collider's colour (#160): they report, the others block.
|
||||
stroke(boundary.triggers, width, Theme.colors.trigger);
|
||||
// A platform's top is what blocks, so that is what stands out, with the cone it lands within.
|
||||
const length = CONE_LENGTH / (scale * scaleOf(item.m));
|
||||
for (const rect of oneWay.solids) {
|
||||
|
||||
Reference in new issue
Block a user