Add a node that names the render layer of an object (#305)
The issue asked for Set render layer, Set sees and Render layer of. As with #211, a probe showed that writing already works. A Properties node of the object's type writes Render layer during a run, Inherit included, and one of type Camera writes Sees. The next picture follows both. As agreed, only Render layer of is built, and writing stays with Properties. - Render layer of is a data node with the input object and the outputs name and number. It answers the layer that holds, so an object without a choice of its own names its parent's. - NodeContext.renderLayerOf answers the number with the same rule the drawing uses (renderLayerAlong). The new field renderLayers on GraphLookup brings the names to the node. - hasRenderLayer in PropertySchema says which types are drawn. - Decided here: for an object that is not drawn (a camera, a behavior, a collider) the name is empty and the number -1, as Layer of does without a collider. Changing the render layer or Sees during a run had no test so far. Three unit tests now hold the proof from the issue. Tests: - Gate: 231 files, 3534 tests (3526 + 8), exit 0 - e2e 6: 149 checks passed, e2e 63: 13 checks passed (+1) - Unit: the coin moves into the overlay with Set and back with Inherit, a HUD container takes its sprite along, the main camera sees a second layer once its Sees is set, and Render layer of names the inherited layer, and none for a behavior - Node catalog: four cases for Render layer of - 63c3 moves a sprite onto UI in a Play run: the main camera no longer draws it, and the log reads "UI" and "1" - Three counter-probes fail 1, 1 and 2 tests: without inheriting in the node, without the check for a drawn type, and without inheriting in the drawing - "layer of" in the palette still finds Layer of first (6z-x) - The e2e sum is 1731 (1730 plus 1) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
70f3608733
commit
d9c6bbfa4d
12 files changed
+250
-8
No files matched your search
@@ -205,6 +205,11 @@ export interface NodeContext {
|
||||
* other object's that of its first collider child; null for an object without a collider.
|
||||
*/
|
||||
collisionLayerOf(target: EntityHandle): number | null;
|
||||
/**
|
||||
* The number of the render layer `target` is drawn on (#305): its own choice, else its parent's,
|
||||
* at the top 0; null for an object that is not drawn.
|
||||
*/
|
||||
renderLayerOf(target: EntityHandle): number | null;
|
||||
/** Lets `target` fall through one-way colliders and tiles for `seconds` (#155). */
|
||||
ignoreOneWay(target: EntityHandle, seconds: number): void;
|
||||
/** Whether `target` is a placed tilemap – what the tile nodes act on (#135). */
|
||||
|
||||
@@ -45,8 +45,8 @@ export interface StateMachine {
|
||||
* What a node may look up in the project around its graph: the behaviors it can run, the state
|
||||
* machines it can read, the curves a `Lerp` eases along (#247) and the struct types `Add item`
|
||||
* checks (#254), what a variable written by a graph is held to (#271), how long an animation is
|
||||
* (#233) and what the collision layers are called (#211). One handle, so no caller can resolve a
|
||||
* node with half of it.
|
||||
* (#233) and what the collision and render layers are called (#211, #305). One handle, so no caller
|
||||
* can resolve a node with half of it.
|
||||
*/
|
||||
export interface GraphLookup {
|
||||
behavior(assetId: string): BehaviorProgram | null;
|
||||
@@ -62,6 +62,8 @@ export interface GraphLookup {
|
||||
animation(assetId: string): AnimationClip | null;
|
||||
/** The project's collision layers by number, as `Layer of` names them (#211). */
|
||||
collisionLayers(): readonly string[];
|
||||
/** The project's render layers by number, as `Render layer of` names them (#305). */
|
||||
renderLayers(): readonly string[];
|
||||
/** The asset whose graph this is, `''` for none. */
|
||||
ownerOf(graph: Graph): string;
|
||||
/** What a variable of that asset declares about the structs it holds (#271); none for nothing typed. */
|
||||
@@ -94,6 +96,7 @@ export const NO_LOOKUP: GraphLookup = {
|
||||
textureSize: () => null,
|
||||
animation: () => null,
|
||||
collisionLayers: () => [],
|
||||
renderLayers: () => [],
|
||||
ownerOf: () => '',
|
||||
variableTyping: () => undefined,
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
import { layerName } from '../../../model/collisionLayers';
|
||||
import type { NodeDef } from '../../NodeRegistry';
|
||||
import { asEntity } from '../../values';
|
||||
import { num, obj, str } from '../shared';
|
||||
|
||||
export const RenderLayerOf: NodeDef = {
|
||||
type: 'RenderLayerOf',
|
||||
label: 'Render layer of',
|
||||
category: 'data',
|
||||
description:
|
||||
'The render layer an object is drawn on, by name and by number: its own, else the one it ' +
|
||||
'inherits from its parent. For an object that is not drawn the name is empty and the number -1.',
|
||||
inputs: [obj('object')],
|
||||
outputs: [str('name'), num('number')],
|
||||
params: [],
|
||||
read: (ctx, _params, input, _def, lookup) => {
|
||||
const target = asEntity(input('object'));
|
||||
const layer = target ? ctx.renderLayerOf(target) : null;
|
||||
if (layer === null) return { name: '', number: -1 };
|
||||
return { name: layerName(lookup.renderLayers(), layer), number: layer };
|
||||
},
|
||||
};
|
||||
@@ -88,6 +88,7 @@ import { PackStruct } from './data/PackStruct';
|
||||
import { Properties } from './data/Properties';
|
||||
import { Parent } from './data/Parent';
|
||||
import { Random } from './data/Random';
|
||||
import { RenderLayerOf } from './data/RenderLayerOf';
|
||||
import { Round } from './data/Round';
|
||||
import { Select } from './data/Select';
|
||||
import { Self } from './data/Self';
|
||||
@@ -261,6 +262,7 @@ export const BUILT_IN_NODES: readonly NodeDef[] = [
|
||||
IsHovered,
|
||||
IsTouching,
|
||||
LayerOf,
|
||||
RenderLayerOf,
|
||||
Move,
|
||||
MoveToward,
|
||||
SetPosition,
|
||||
|
||||
@@ -60,6 +60,7 @@ export function graphLookup(project: Project): GraphLookup {
|
||||
return asset ? clipOf(asset) : null;
|
||||
},
|
||||
collisionLayers: () => project.collisionLayers,
|
||||
renderLayers: () => project.renderLayers,
|
||||
ownerOf: (graph) => project.assets.find((a) => isGraphAsset(a) && a.graph === graph)?.id ?? '',
|
||||
variableTyping: (assetId, key) => {
|
||||
const owner = findAsset(project, assetId);
|
||||
|
||||
@@ -402,8 +402,13 @@ const RENDER_LAYER_TYPES: ReadonlySet<AssetType> = new Set([
|
||||
'tilemap',
|
||||
]);
|
||||
|
||||
/** Whether objects of that type are drawn and so sit on a render layer. */
|
||||
export function hasRenderLayer(type: AssetType): boolean {
|
||||
return RENDER_LAYER_TYPES.has(type);
|
||||
}
|
||||
|
||||
function renderLayerPropsOf(type: AssetType): PropertySchema {
|
||||
return RENDER_LAYER_TYPES.has(type) ? [RENDER_LAYER_ROW] : [];
|
||||
return hasRenderLayer(type) ? [RENDER_LAYER_ROW] : [];
|
||||
}
|
||||
|
||||
const COMMON_KEYS = new Set(
|
||||
|
||||
@@ -10,7 +10,9 @@ import type { Vec2 } from '../../core/model/Properties';
|
||||
import { JOINT_CONNECTED_KEY } from '../../core/model/joint';
|
||||
import { objectReference } from '../../core/model/objectRefs';
|
||||
import { findAsset, findAssetOf, findScene, type Project } from '../../core/model/Project';
|
||||
import { renderLayerAlong } from '../../core/model/renderLayers';
|
||||
import { soundFileOf } from '../../core/model/sound';
|
||||
import { hasRenderLayer } from '../../core/properties/PropertySchema';
|
||||
import { resolveAssetProps } from '../../core/properties/resolve';
|
||||
import { loopsOf, volumeOf } from '../../core/properties/sounds';
|
||||
import type { Entity } from '../Entity';
|
||||
@@ -128,6 +130,15 @@ export function behaviorContext(
|
||||
const holder = found && layerHolderOf(world, found);
|
||||
return holder ? collisionFilterOf(holder.props).layer : null;
|
||||
},
|
||||
renderLayerOf: (target) => {
|
||||
const found = resolve(target);
|
||||
if (!found || !hasRenderLayer(found.type)) return null;
|
||||
return renderLayerAlong(
|
||||
found,
|
||||
(at) => at.props,
|
||||
(at) => at.parent ?? null,
|
||||
);
|
||||
},
|
||||
ignoreOneWay: (target, seconds) => {
|
||||
const found = resolve(target);
|
||||
if (found) world.ignoreOneWay(found, seconds);
|
||||
|
||||
Reference in new issue
Block a user