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
+5
-1
@@ -6,6 +6,7 @@ import { listScenes } from '../core/model/Project';
|
||||
import { HttpProjectStore } from '../core/store/HttpProjectStore';
|
||||
import type { ProjectStore } from '../core/store/ProjectStore';
|
||||
import { BehaviorSystem } from '../runtime/systems/BehaviorSystem';
|
||||
import { CollisionSystem } from '../runtime/systems/CollisionSystem';
|
||||
import { Input } from '../runtime/systems/Input';
|
||||
import { World } from '../runtime/World';
|
||||
import { Button, Dialog, HStack, Menu, type MenuItem, Splitter, Theme, ui, VStack, type Widget } from '../ui';
|
||||
@@ -262,7 +263,10 @@ class Editor {
|
||||
ui().focus.blur();
|
||||
this.world = World.instantiate(this.state.project, scene.id);
|
||||
this.input.attach();
|
||||
this.behaviors = new BehaviorSystem(this.state.project, this.input);
|
||||
// Collisions are detected first so behaviors see this frame's enter/stay/exit events.
|
||||
const collisions = new CollisionSystem();
|
||||
this.behaviors = new BehaviorSystem(this.state.project, this.input, collisions);
|
||||
this.world.addSystem(collisions);
|
||||
this.world.addSystem(this.behaviors);
|
||||
this.viewport.setWorld(this.world);
|
||||
this.state.setMode('play');
|
||||
|
||||
@@ -7,7 +7,7 @@ import { findAsset, findUsages } from '../../core/model/Project';
|
||||
import { Button, Dialog, ListView, Menu, type MenuItem, Panel, ScrollContainer } from '../../ui';
|
||||
import type { EditorState } from '../state';
|
||||
|
||||
const TYPE_LABELS: Record<AssetType, string> = { scene: 'Scene', camera: 'Camera', sprite: 'Sprite', behavior: 'Behavior' };
|
||||
const TYPE_LABELS: Record<AssetType, string> = { scene: 'Scene', camera: 'Camera', sprite: 'Sprite', behavior: 'Behavior', collider: 'Collider' };
|
||||
|
||||
/** Lists all assets of the project; creates, places and deletes them. */
|
||||
export class AssetPanel extends Panel {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { AddColliderCommand } from '../../core/commands/colliderCommands';
|
||||
import { RemoveSceneObjectCommand, SetParentCommand } from '../../core/commands/sceneObjectCommands';
|
||||
import { treeOrder } from '../../core/model/hierarchy';
|
||||
import { findAsset } from '../../core/model/Project';
|
||||
@@ -33,8 +34,17 @@ export class HierarchyPanel extends Panel {
|
||||
const scene = state.activeScene;
|
||||
if (!scene) return;
|
||||
const object = scene.objects.find((o) => o.id === id);
|
||||
const type = object && findAsset(state.project, object.assetId)?.type;
|
||||
new Menu([
|
||||
{ label: 'Select asset', onSelect: () => object && state.select({ kind: 'asset', assetId: object.assetId }) },
|
||||
{
|
||||
label: 'Add collider',
|
||||
disabled: !type || type === 'collider',
|
||||
onSelect: () => {
|
||||
const command = new AddColliderCommand(scene.id, id);
|
||||
if (state.execute(command)) state.select({ kind: 'object', sceneId: scene.id, objectId: command.objectId });
|
||||
},
|
||||
},
|
||||
{ label: 'Unparent (move to top level)', disabled: !object?.parentId, onSelect: () => state.execute(new SetParentCommand(scene.id, id, '')) },
|
||||
{ label: 'Remove from scene', danger: true, onSelect: () => state.execute(new RemoveSceneObjectCommand(scene.id, id)) },
|
||||
]).open(x, y);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { RenameAssetCommand, ResetAssetPropertyCommand, SetAssetPropertyCommand } from '../../core/commands/assetCommands';
|
||||
import { AddColliderCommand } from '../../core/commands/colliderCommands';
|
||||
import { RemoveNodeCommand, SetNodeParamCommand } from '../../core/commands/graphCommands';
|
||||
import {
|
||||
ClearOverrideCommand,
|
||||
@@ -275,6 +276,20 @@ export class Inspector extends Panel {
|
||||
this.fields.set(def.key, row);
|
||||
this.body.add(row.row);
|
||||
}
|
||||
|
||||
// Sprites (and other bodies) get their collider with one click: a child sized like the object.
|
||||
if (asset.type !== 'collider') {
|
||||
this.body.add(new Label(''));
|
||||
this.body.add(
|
||||
new Button('+ Add collider', {
|
||||
variant: 'ghost',
|
||||
onClick: () => {
|
||||
const command = new AddColliderCommand(sceneId, objectId);
|
||||
if (this.state.execute(command)) this.state.select({ kind: 'object', sceneId, objectId: command.objectId });
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in new issue
Block a user