game-maker / suite (push) Failing after 3m59s
A shader is an asset carrying a node graph (Texture color, Output, Property, Color, Add/Subtract/Multiply/Divide, Mix, Luminance) that compiles to the GLSL fragment of a Pixi filter. The Shader tab reuses the Behavior canvas; a sprite wears a shader through its Shader picker, and the rows below it are the graph's Property nodes. Property schema, resolution and deviation take the project because a sprite's rows now live in another asset. The built-in registry of the prototype is gone; "Adjust colors" ships as a graph in the fixture and the three example games. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
156 lines
5.4 KiB
TypeScript
156 lines
5.4 KiB
TypeScript
/**
|
||
* "Apply" is the opposite direction of an override: a value changed on a placed object is written
|
||
* back to the asset, or to the template of the container it belongs to. Reverting drops the
|
||
* instance's deviation again, which is all it takes for the value to follow the template – the
|
||
* content of a placed container is resolved from it, never copied (see `model/instance.ts`).
|
||
*/
|
||
import type { Asset } from '../model/Asset';
|
||
import type { InstanceTarget, ObjectRef } from '../model/instance';
|
||
import { type Axis, type Position, type PropertyValue, setBagValue } from '../model/Properties';
|
||
import type { Project } from '../model/Project';
|
||
import { resolveSceneObjectProps } from '../properties/resolve';
|
||
import { CommandError } from './Command';
|
||
import { requireAsset, requirePropDef } from './require';
|
||
import { ObjectEditCommand } from './sceneObjectCommands';
|
||
|
||
const ALL_AXES: readonly Axis[] = ['x', 'y', 'z'];
|
||
|
||
function requireTarget(ref: ObjectRef): InstanceTarget {
|
||
if (!ref.target) throw new CommandError('That object is not part of a placed container');
|
||
return ref.target;
|
||
}
|
||
|
||
/**
|
||
* Writes the object's value for `key` into its asset and drops the override. For content of a
|
||
* placed container the template's override goes too, so every instance follows the asset again.
|
||
*/
|
||
export class ApplyOverrideToAssetCommand extends ObjectEditCommand {
|
||
readonly label: string;
|
||
private asset: Asset | undefined;
|
||
private previousAssetValue: PropertyValue | undefined;
|
||
private template: InstanceTarget | undefined;
|
||
private previousTemplateValue: PropertyValue | undefined;
|
||
|
||
constructor(
|
||
holderId: string,
|
||
objectId: string,
|
||
private readonly key: string,
|
||
) {
|
||
super(holderId, objectId);
|
||
this.label = `Apply ${key} to asset`;
|
||
}
|
||
|
||
protected override apply(ref: ObjectRef, project: Project): void {
|
||
const asset = requireAsset(project, ref.object.assetId);
|
||
requirePropDef(project, asset, this.key, ref.object);
|
||
const value = resolveSceneObjectProps(project, asset, ref.object)[this.key];
|
||
this.asset = asset;
|
||
this.previousAssetValue = asset.props[this.key];
|
||
asset.props[this.key] = value!;
|
||
ref.setOverride(this.key, undefined);
|
||
this.template = ref.target;
|
||
if (!this.template) return;
|
||
this.previousTemplateValue = this.template.template.overrides[this.key];
|
||
delete this.template.template.overrides[this.key];
|
||
}
|
||
|
||
protected override revert(): void {
|
||
if (this.template) setBagValue(this.template.template.overrides, this.key, this.previousTemplateValue);
|
||
if (this.asset) setBagValue(this.asset.props, this.key, this.previousAssetValue);
|
||
}
|
||
}
|
||
|
||
export class ApplyOverrideToContainerCommand extends ObjectEditCommand {
|
||
readonly label: string;
|
||
private previous: PropertyValue | undefined;
|
||
private target: InstanceTarget | undefined;
|
||
|
||
constructor(
|
||
holderId: string,
|
||
objectId: string,
|
||
private readonly key: string,
|
||
) {
|
||
super(holderId, objectId);
|
||
this.label = `Apply ${key} to container`;
|
||
}
|
||
|
||
protected override apply(ref: ObjectRef): void {
|
||
const target = requireTarget(ref);
|
||
this.target = target;
|
||
this.previous = target.template.overrides[this.key];
|
||
setBagValue(target.template.overrides, this.key, ref.object.overrides[this.key]);
|
||
// The deviation is gone the moment the template carries it, so the edit must go with it.
|
||
ref.setOverride(this.key, target.template.overrides[this.key]);
|
||
}
|
||
|
||
protected override revert(): void {
|
||
if (this.target) setBagValue(this.target.template.overrides, this.key, this.previous);
|
||
}
|
||
}
|
||
|
||
export class RevertOverrideToTemplateCommand extends ObjectEditCommand {
|
||
readonly label: string;
|
||
|
||
constructor(
|
||
holderId: string,
|
||
objectId: string,
|
||
private readonly key: string,
|
||
) {
|
||
super(holderId, objectId);
|
||
this.label = `Revert ${key}`;
|
||
}
|
||
|
||
protected override apply(ref: ObjectRef): void {
|
||
ref.setOverride(this.key, requireTarget(ref).base.overrides[this.key]);
|
||
}
|
||
}
|
||
|
||
/** Writes the object's local position – one axis, or all – into its container template. */
|
||
export class ApplyPositionToContainerCommand extends ObjectEditCommand {
|
||
readonly label: string;
|
||
private previous: Position = { x: 0, y: 0, z: 0 };
|
||
private target: InstanceTarget | undefined;
|
||
|
||
constructor(
|
||
holderId: string,
|
||
objectId: string,
|
||
private readonly axis?: Axis,
|
||
) {
|
||
super(holderId, objectId);
|
||
this.label = axis ? `Apply ${axis} to container` : 'Apply position to container';
|
||
}
|
||
|
||
protected override apply(ref: ObjectRef): void {
|
||
const target = requireTarget(ref);
|
||
this.target = target;
|
||
this.previous = { ...target.template.position };
|
||
for (const axis of this.axis ? [this.axis] : ALL_AXES)
|
||
target.template.position[axis] = ref.object.position[axis];
|
||
ref.setPosition(ref.object.position);
|
||
}
|
||
|
||
protected override revert(): void {
|
||
if (this.target) this.target.template.position = { ...this.previous };
|
||
}
|
||
}
|
||
|
||
export class RevertPositionToTemplateCommand extends ObjectEditCommand {
|
||
readonly label: string;
|
||
|
||
constructor(
|
||
holderId: string,
|
||
objectId: string,
|
||
private readonly axis?: Axis,
|
||
) {
|
||
super(holderId, objectId);
|
||
this.label = axis ? `Revert ${axis}` : 'Revert position';
|
||
}
|
||
|
||
protected override apply(ref: ObjectRef): void {
|
||
const base = requireTarget(ref).base;
|
||
const position = { ...ref.object.position };
|
||
for (const axis of this.axis ? [this.axis] : ALL_AXES) position[axis] = base.position[axis];
|
||
ref.setPosition(position);
|
||
}
|
||
}
|