Seed the saved demo scene (crate colliders) and make collider symbols grabbable only at their outline

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
rootandClaude Fable 5.1 committed 2026-09-07 16:14:01 +02:00
1 parent b0860ca07f
commit cb8885359f
3 files changed
+49 -17

No files matched your search

+21 -2
View File
@@ -5,7 +5,7 @@ import { type Position, type PropertyBag, sortByZ, type Vec2 } from '../core/mod
import { treeOrder, worldPosition } from '../core/model/hierarchy';
import { findAsset } from '../core/model/Project';
import { cameraFrame, rectAround, rectsIntersect, type WorldRect } from '../core/properties/camera';
import { colliderShape, colliderSize, isTrigger } from '../core/properties/collider';
import { type ColliderShape, colliderShape, colliderSize, isTrigger } from '../core/properties/collider';
import { resolveSceneObjectProps } from '../core/properties/resolve';
import type { World } from '../runtime/World';
import { Theme, textStyle, Widget, type Size } from '../ui';
@@ -27,6 +27,22 @@ interface DrawItem {
/** World-unit size of the behavior symbol (a diamond). */
const BEHAVIOR_SYMBOL = 28;
/** Screen-pixel half-width of the band along a collider's outline that grabs the collider. */
const COLLIDER_GRAB_BAND = 6;
/** Hit area covering only the outline band of a collider shape (local coordinates, world units). */
function colliderOutlineHitArea(shape: ColliderShape, band: number): { contains(x: number, y: number): boolean } {
if (shape.kind === 'circle') return { contains: (x, y) => Math.abs(Math.hypot(x, y) - shape.r) <= band };
const hw = shape.w / 2;
const hh = shape.h / 2;
return {
contains: (x, y) => {
const ax = Math.abs(x);
const ay = Math.abs(y);
return ax <= hw + band && ay <= hh + band && (ax >= hw - band || ay >= hh - band);
},
};
}
/**
* How world coordinates map onto the viewport: the world point (`x`, `y`) sits at the
@@ -301,12 +317,15 @@ export class Viewport extends Widget {
node.circle(-6, -3, 2).fill(Theme.colors.accentText);
node.circle(6, 3, 2).fill(Theme.colors.accentText);
} else if (item.type === 'collider') {
// Outline plus a faint fill: the fill keeps the shape hit-testable and reads as "area".
// Outline plus a faint fill so the shape reads as "area". Colliders usually sit right on
// top of their body, so only the outline band is hit-testable: clicks inside still reach
// the sprite underneath, the edge grabs the collider.
const shape = colliderShape(item.props, { x: 0, y: 0 });
if (shape.kind === 'circle') node.circle(0, 0, shape.r);
else node.rect(-shape.w / 2, -shape.h / 2, shape.w, shape.h);
const color = isTrigger(item.props) ? Theme.colors.trigger : Theme.colors.collider;
node.fill({ color, alpha: 0.12 }).stroke({ width: 2 / view.scale, color });
node.hitArea = colliderOutlineHitArea(shape, COLLIDER_GRAB_BAND / view.scale);
}
}
this.drawParentLinks(items, editing, view.scale);