Collider asset type, CollisionSystem and collision nodes
- Asset type 'collider' (box/circle) placed as child of a body object - 'Add collider' shortcut (hierarchy context menu + inspector) - CollisionSystem with enter/stay/exit pairs, sibling colliders excluded - Nodes: On collision enter/stay/exit, Is touching - Green outline in edit mode, hidden in play Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
26d5edc496
commit
0a59513b74
21 files changed
+525
-16
No files matched your search
@@ -39,6 +39,12 @@ export const ASSET_SCHEMAS: Record<AssetType, PropertySchema> = {
|
||||
],
|
||||
// Behaviors declare their own properties (variables) per asset – see `schemaForAsset`.
|
||||
behavior: [],
|
||||
collider: [
|
||||
{ key: 'shape', label: 'Shape', kind: 'enum', default: 'box', options: [{ value: 'box', label: 'Box' }, { value: 'circle', label: 'Circle' }] },
|
||||
{ key: 'width', label: 'Width', kind: 'number', default: 100, min: 1, step: 1 },
|
||||
{ key: 'height', label: 'Height', kind: 'number', default: 100, min: 1, step: 1 },
|
||||
{ key: 'radius', label: 'Radius', kind: 'number', default: 50, min: 1, step: 1 },
|
||||
],
|
||||
};
|
||||
|
||||
/** Properties every scene object has regardless of its asset type. */
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import type { PropertyBag, Vec2 } from '../model/Properties';
|
||||
import { defaultsFor } from './PropertySchema';
|
||||
|
||||
/**
|
||||
* Collider geometry in world units. Pure functions shared by the editor (drawing, gizmos)
|
||||
* and the runtime (CollisionSystem), so both agree on what "overlaps" means.
|
||||
*/
|
||||
export type ColliderShape =
|
||||
| { kind: 'box'; x: number; y: number; w: number; h: number }
|
||||
| { kind: 'circle'; x: number; y: number; r: number };
|
||||
|
||||
/** The shape described by collider properties, centred on `centre` (a world position). */
|
||||
export function colliderShape(props: PropertyBag, centre: Vec2): ColliderShape {
|
||||
const d = defaultsFor('collider');
|
||||
if (props.shape === 'circle') return { kind: 'circle', x: centre.x, y: centre.y, r: Math.max(0, Number(props.radius ?? d.radius)) };
|
||||
return { kind: 'box', x: centre.x, y: centre.y, w: Math.max(0, Number(props.width ?? d.width)), h: Math.max(0, Number(props.height ?? d.height)) };
|
||||
}
|
||||
|
||||
/** Footprint of a collider shape (used for culling and the selection gizmo). */
|
||||
export function colliderSize(props: PropertyBag): { w: number; h: number } {
|
||||
const shape = colliderShape(props, { x: 0, y: 0 });
|
||||
return shape.kind === 'circle' ? { w: shape.r * 2, h: shape.r * 2 } : { w: shape.w, h: shape.h };
|
||||
}
|
||||
|
||||
/** Whether two shapes overlap. Shapes that merely touch along an edge do not overlap. */
|
||||
export function shapesOverlap(a: ColliderShape, b: ColliderShape): boolean {
|
||||
if (a.kind === 'box' && b.kind === 'box') {
|
||||
return Math.abs(a.x - b.x) * 2 < a.w + b.w && Math.abs(a.y - b.y) * 2 < a.h + b.h;
|
||||
}
|
||||
if (a.kind === 'circle' && b.kind === 'circle') {
|
||||
const r = a.r + b.r;
|
||||
return (a.x - b.x) ** 2 + (a.y - b.y) ** 2 < r * r;
|
||||
}
|
||||
const box = a.kind === 'box' ? a : (b as Extract<ColliderShape, { kind: 'box' }>);
|
||||
const circle = a.kind === 'circle' ? a : (b as Extract<ColliderShape, { kind: 'circle' }>);
|
||||
// Closest point of the box to the circle centre, then a plain distance check.
|
||||
const cx = Math.max(box.x - box.w / 2, Math.min(circle.x, box.x + box.w / 2));
|
||||
const cy = Math.max(box.y - box.h / 2, Math.min(circle.y, box.y + box.h / 2));
|
||||
return (circle.x - cx) ** 2 + (circle.y - cy) ** 2 < circle.r * circle.r;
|
||||
}
|
||||
Reference in new issue
Block a user