An animated or set Z of -1 on a weapon put it behind everything on Z 0, not just behind the player holding it. A child can now opt in to being drawn at its parent's place, so its own Z only orders it against the parent and its siblings. - New property "Sort with parent" on sprites, texts and particle emitters, shown in the inspector right under Z. It is a normal property, set on the asset and overridable per object. - With it on, the rest of the scene sees only the parent's Z and, where the layer sorts by Y, the parent's feet. Below 0 the child is drawn behind the parent, from 0 on in front. - It works across levels: a grandchild with the switch hangs on the top object of the chain. - It has no effect under a parent that is not drawn itself (container, behavior, camera, collider) or under a tilemap. - New assets start with it on, saved ones read as off, so old projects draw unchanged. For that a schema row now has "initial" next to "default". The gold "changed" mark and reset follow "initial", so a fresh sprite is not marked as changed. No load migration is needed. - parentGroups() in drawOrder.ts gives a slot only to the top of a chain and inserts the children there. Editor, play mode and picking all use it, so they agree. Tests: - 10 new unit tests in tests/viewport/sort-with-parent.test.ts. - 8 existing tests list the sprite rows or defaults and got the row. - New e2e check 5b-ii clicks the switch and checks order and undo. - Counter-checks, all originals restored by cmp: switch ignored fails 7, children not sorted by Z fails 6, no chain fails 1, "initial" ignored fails 9, row missing in the inspector fails e2e 5. - ./check.sh: 166 files, 2426 tests, CHECK EXIT 0. - e2e 5: ALL PASSED, 13 checks. Check total 1398, computed only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
67 lines
2.7 KiB
TypeScript
67 lines
2.7 KiB
TypeScript
import type { Asset } from '../model/Asset';
|
||
import { type PropertyBag, type PropertyValue, sameValue } from '../model/Properties';
|
||
import type { Project } from '../model/Project';
|
||
import type { SceneObject } from '../model/SceneObject';
|
||
import { initialOf, propDef, schemaForAsset, schemaForObject } from './PropertySchema';
|
||
|
||
/**
|
||
* Schema defaults filled with the asset's own values. Keys unknown to the schema are ignored, so
|
||
* stale data never leaks into the editor.
|
||
*/
|
||
export function resolveAssetProps(project: Project, asset: Asset): PropertyBag {
|
||
return valuesOver(schemaForAsset(project, asset), asset.props);
|
||
}
|
||
|
||
/**
|
||
* The only place where overrides are merged onto asset values. The rows are the object's: a
|
||
* sprite overriding its shader has that shader's parameters, not the asset's.
|
||
*/
|
||
export function resolveSceneObjectProps(project: Project, asset: Asset, object: SceneObject): PropertyBag {
|
||
const schema = schemaForObject(project, asset, object);
|
||
const bag = valuesOver(schema, asset.props);
|
||
for (const def of schema) {
|
||
const override = object.overrides[def.key];
|
||
if (override !== undefined) bag[def.key] = override;
|
||
}
|
||
return bag;
|
||
}
|
||
|
||
function valuesOver(
|
||
schema: readonly { key: string; default: PropertyValue }[],
|
||
values: PropertyBag,
|
||
): PropertyBag {
|
||
const bag: PropertyBag = {};
|
||
for (const def of schema) {
|
||
const own = values[def.key];
|
||
bag[def.key] = own !== undefined ? own : def.default;
|
||
}
|
||
return bag;
|
||
}
|
||
|
||
/** Whether the asset's own value differs from what a new asset starts with – the asset-level override. */
|
||
export function isModified(project: Project, asset: Asset, key: string): boolean {
|
||
const def = propDef(project, asset, key);
|
||
if (!def) return false;
|
||
const own = asset.props[key];
|
||
return own !== undefined && !sameValue(own, initialOf(def));
|
||
}
|
||
|
||
/** The value the object would have without its override for `key`. */
|
||
export function inheritedValue(project: Project, asset: Asset, key: string): PropertyValue | undefined {
|
||
return resolveAssetProps(project, asset)[key];
|
||
}
|
||
|
||
/** An override that happens to carry the asset's own value does not count as a deviation. */
|
||
export function deviatesFromAsset(project: Project, object: SceneObject, asset: Asset, key: string): boolean {
|
||
const own = object.overrides[key];
|
||
return own !== undefined && !sameValue(own, inheritedValue(project, asset, key));
|
||
}
|
||
|
||
/**
|
||
* Compared as overrides, not as effective values: both sides resolve against the same asset, so
|
||
* "neither overrides" means equal even when the asset value changed since.
|
||
*/
|
||
export function deviatesFromTemplate(object: SceneObject, base: SceneObject, key: string): boolean {
|
||
return !sameValue(object.overrides[key], base.overrides[key]);
|
||
}
|