Spawn node: place assets at runtime into the play copy, Spawner demo behavior

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
rootandClaude Fable 5.1 committed 2026-09-07 16:57:19 +02:00
1 parent c3a533c4e5
commit 91ee2feadd
13 files changed
+219 -37

No files matched your search

+1 -1
View File
@@ -131,7 +131,7 @@ export class GraphEditor extends Widget {
this.views.set(node.id, view);
this.nodeLayer.addChild(view);
}
view.sync(node, asset.graph, asset);
view.sync(node, asset.graph, asset, this.state.project);
}
for (const [id, view] of this.views) {
if (!seen.has(id)) {
+13 -7
View File
@@ -1,6 +1,8 @@
import { CanvasTextMetrics, Graphics, Rectangle, Text } from 'pixi.js';
import { edgesInto, type Graph, type GraphNode, inlineInputs, inlineValue, type NodeCategory, type NodeDef, type PortDef, type PortType } from '../../core/graph';
import type { BehaviorAsset } from '../../core/model/Asset';
import { findAsset, type Project } from '../../core/model/Project';
import type { PropertyDef } from '../../core/properties/PropertySchema';
import { Theme, textStyle, Widget } from '../../ui';
import { keyLabel } from '../inspector/fieldFactory';
@@ -59,7 +61,7 @@ export class NodeView extends Widget {
super();
this.cursor = 'grab';
this.addChild(this.box, this.ports);
this.sync(node, { nodes: [node], edges: [] }, null);
this.sync(node, { nodes: [node], edges: [] }, null, null);
}
get titleText(): string {
@@ -72,9 +74,9 @@ export class NodeView extends Widget {
this.hitArea = new Rectangle(-PORT_HIT_RADIUS, 0, w + PORT_HIT_RADIUS * 2, h);
}
/** Re-reads params and connections; rebuilds the text lines. */
sync(node: GraphNode, graph: Graph, asset: BehaviorAsset | null): void {
this.paramLines = this.def.params.map((p) => `${p.label}: ${formatParam(p.kind, node.params[p.key] ?? p.default, asset)}`);
/** Re-reads params and connections; rebuilds the text lines. `project` resolves asset references (Spawn). */
sync(node: GraphNode, graph: Graph, asset: BehaviorAsset | null, project: Project | null): void {
this.paramLines = this.def.params.map((p) => `${p.label}: ${formatParam(p, node.params[p.key] ?? p.default, asset, project)}`);
this.connected = new Set(inlineInputs(this.def).filter((p) => edgesInto(graph, node.id, p.key).length > 0).map((p) => p.key));
const rows = this.rowCount();
const height = TITLE_HEIGHT + PAD + this.paramLines.length * ROW_HEIGHT + rows * ROW_HEIGHT + PAD;
@@ -169,10 +171,14 @@ export class NodeView extends Widget {
}
}
function formatParam(kind: string, value: unknown, asset: BehaviorAsset | null): string {
function formatParam(param: PropertyDef, value: unknown, asset: BehaviorAsset | null, project: Project | null): string {
if (value === undefined || value === '') return '—';
if (kind === 'key') return keyLabel(String(value));
if (kind === 'enum' && asset && !asset.variables.some((v) => v.key === value) && asset.variables.length > 0 && String(value).length > 3) return `${value}?`;
if (param.kind === 'key') return keyLabel(String(value));
if (param.kind === 'enum' && param.key === 'asset') {
const target = project ? findAsset(project, String(value)) : undefined;
return target ? target.name : `${value}?`;
}
if (param.kind === 'enum' && param.key === 'variable' && asset && !asset.variables.some((v) => v.key === value) && asset.variables.length > 0 && String(value).length > 3) return `${value}?`;
return formatValue(value);
}
+9 -1
View File
@@ -12,6 +12,7 @@ import {
import { AddVariableCommand, nextVariableName, RemoveVariableCommand, RenameVariableCommand } from '../../core/commands/variableCommands';
import { edgesInto, inlineInputDef, inlineInputs, nodeDef } from '../../core/graph';
import type { Asset, BehaviorAsset, SceneAsset } from '../../core/model/Asset';
import { isPlaceableType } from '../../core/model/AssetTypes';
import { isSelfOrAncestor, treeOrder } from '../../core/model/hierarchy';
import type { Position, PropertyValue } from '../../core/model/Properties';
import { findAsset, findScene, findSceneObject } from '../../core/model/Project';
@@ -395,11 +396,18 @@ export class Inspector extends Panel {
this.body.add(new Button('Delete node', { variant: 'danger', onClick: () => this.state.execute(new RemoveNodeCommand(assetId, nodeId)) }));
}
/** Variable params pick from the asset's variables; everything else is used as declared. */
/**
* Variable params pick from the asset's variables, asset params (Spawn) from the project's
* placeable assets; everything else is used as declared.
*/
private resolveParamDef(param: PropertyDef, asset: Asset): PropertyDef {
if (param.kind === 'enum' && param.key === 'variable' && asset.type === 'behavior') {
return { ...param, options: asset.variables.map((v) => ({ value: v.key, label: v.key })) };
}
if (param.kind === 'enum' && param.key === 'asset') {
const options = this.state.project.assets.filter((a) => isPlaceableType(a.type)).map((a) => ({ value: a.id, label: `${a.name} (${a.type})` }));
return { ...param, options };
}
return param;
}