Shader assets: graph-built shaders, the Shader tab and sprite rows from the graph (#62)
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>
This commit is contained in:
rootandClaude Fable 5.1 committed 2026-09-12 22:41:14 +02:00
1 parent 6141d07d4f
commit 42cafa7543
62 files changed
+1682 -144

No files matched your search

+2 -2
View File
@@ -42,8 +42,8 @@ export class ApplyOverrideToAssetCommand extends ObjectEditCommand {
protected override apply(ref: ObjectRef, project: Project): void {
const asset = requireAsset(project, ref.object.assetId);
requirePropDef(asset, this.key);
const value = resolveSceneObjectProps(asset, ref.object)[this.key];
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!;
+7 -5
View File
@@ -36,6 +36,8 @@ export function buildAsset(type: AssetType, name: string, id = newId()): Asset {
return { ...base, type: 'animation', frames: [] };
case 'statemachine':
return { ...base, type: 'statemachine', graph: createEmptyGraph() };
case 'shader':
return { ...base, type: 'shader', graph: createEmptyGraph() };
}
}
@@ -57,7 +59,7 @@ export class CreateAssetCommand implements Command {
execute(project: Project): void {
for (const [key, value] of Object.entries(this.props))
this.asset.props[key] = coerceValue(requirePropDef(this.asset, key), value);
this.asset.props[key] = coerceValue(requirePropDef(project, this.asset, key), value);
project.assets.push(this.asset);
this.previousActiveScene = project.activeSceneId;
if (this.asset.type === 'scene' && !project.activeSceneId) project.activeSceneId = this.asset.id;
@@ -195,8 +197,8 @@ export class SetAssetPropertyCommand extends AssetPropertyCommand {
super(assetId, key, `Set ${key}`);
}
protected override computeNext(asset: Asset): PropertyValue {
return coerceValue(requirePropDef(asset, this.key), this.value);
protected override computeNext(asset: Asset, project: Project): PropertyValue {
return coerceValue(requirePropDef(project, asset, this.key), this.value);
}
}
@@ -205,8 +207,8 @@ export class ResetAssetPropertyCommand extends AssetPropertyCommand {
super(assetId, key, `Reset ${key}`);
}
protected override computeNext(asset: Asset): PropertyValue {
return requirePropDef(asset, this.key).default;
protected override computeNext(asset: Asset, project: Project): PropertyValue {
return requirePropDef(project, asset, this.key).default;
}
}
+1 -1
View File
@@ -37,7 +37,7 @@ export class AddColliderCommand implements Command {
this.assetId,
);
if (targetAsset.type === 'sprite') {
const props = resolveSceneObjectProps(targetAsset, target);
const props = resolveSceneObjectProps(project, targetAsset, target);
asset.props.width = Number(props.width);
asset.props.height = Number(props.height);
}
+10 -3
View File
@@ -13,7 +13,7 @@ import {
type Project,
} from '../model/Project';
import type { SceneObject } from '../model/SceneObject';
import { propDef, type PropertyDef } from '../properties/PropertySchema';
import { type PropertyDef, schemaForAsset, schemaForObject } from '../properties/PropertySchema';
import { CommandError } from './Command';
export function requireAsset(project: Project, assetId: string): Asset {
@@ -91,8 +91,15 @@ export function requireNodeDef(type: string): NodeDef {
return def;
}
export function requirePropDef(asset: Asset, key: string): PropertyDef {
const def = propDef(asset, key);
/** The row `key` names on the asset – or, given the placed object, on that object (its shader's rows). */
export function requirePropDef(
project: Project,
asset: Asset,
key: string,
object?: SceneObject,
): PropertyDef {
const schema = object ? schemaForObject(project, asset, object) : schemaForAsset(project, asset);
const def = schema.find((d) => d.key === key);
if (!def) throw new CommandError(`Property "${key}" is not defined for ${asset.type}`);
return def;
}
+4 -1
View File
@@ -189,7 +189,10 @@ export class SetOverrideCommand extends OverrideCommand {
}
protected override nextValue(object: SceneObject, project: Project): PropertyValue {
return coerceValue(requirePropDef(requireAsset(project, object.assetId), this.key), this.value);
return coerceValue(
requirePropDef(project, requireAsset(project, object.assetId), this.key, object),
this.value,
);
}
}