Files
feuerwerk/src/core/model/Asset.ts
T
adminandClaude Opus 5 1a9f07c4ac
game-maker / gate (push) Successful in 1m50s
game-maker / suite (push) Successful in 5m59s
Autotiles are a new asset type with a tab that shows every tile of the floor (2x3) or wall (2x2) block and an animated sample pattern (#118)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 21:35:29 +02:00

238 lines
7.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { Graph } from '../graph/Graph';
import type { PropertyDef } from '../properties/PropertySchema';
import type { AssetType } from './AssetTypes';
import type { PropertyBag, PropertyValue } from './Properties';
import type { Scope } from './Scope';
import type { SceneObject } from './SceneObject';
export interface AssetBase {
id: string;
type: AssetType;
name: string;
props: PropertyBag;
}
export interface SpriteAsset extends AssetBase {
type: 'sprite';
}
export interface CameraAsset extends AssetBase {
type: 'camera';
}
export interface TextAsset extends AssetBase {
type: 'text';
}
export interface SceneAsset extends AssetBase {
type: 'scene';
objects: SceneObject[];
}
/** `variables` is this asset's own property schema; `props` holds their default values. */
export interface BehaviorAsset extends AssetBase {
type: 'behavior';
variables: PropertyDef[];
graph: Graph;
}
/**
* Declarations shared by the whole run: a `Variables` node naming this asset reads and writes one
* value per key, however many graphs point at it. Nothing is placed, so nothing overrides it –
* `props` holds the starting values, as a behavior's does for its own variables.
*/
export interface VariablesAsset extends AssetBase {
type: 'variables';
variables: PropertyDef[];
}
/** Placed as a child of the object it belongs to; its position is the offset from that body. */
export interface ColliderAsset extends AssetBase {
type: 'collider';
}
/**
* `objects` is a template, not content: a placed container shows it without copying it, so an
* edit here reaches every instance (see `model/instance.ts`). Positions are local to the
* container's origin.
*/
export interface ContainerAsset extends AssetBase {
type: 'container';
objects: SceneObject[];
}
/** How the parts between the corners of a nine-slice frame fill the space they are given (#41). */
export type FrameFill = 'stretch' | 'repeat';
/** Distances from the frame's own edges, in image pixels; all zero means the frame is not sliced. */
export interface FrameBorders {
left: number;
top: number;
right: number;
bottom: number;
}
/**
* `id` shares the generator with asset ids and is unique across the document (see `model/texture.ts`).
* `border` and `fill` are absent on a frame nobody sliced, which is what every document written
* before #41 says – read them through `frameBorders`/`frameFill`, never off the object.
*/
export interface TextureFrame {
id: string;
name: string;
x: number;
y: number;
w: number;
h: number;
border?: FrameBorders;
fill?: FrameFill;
}
/**
* `file` names the image in `data/textures/` and is content-addressed (`{sha256}.{ext}`), so it
* can be cached forever; empty means the upload failed or has not happened yet. `width`/`height`
* are the size as uploaded, so frames can be sliced and validated before the image loads.
*/
export interface TextureAsset extends AssetBase {
type: 'texture';
file: string;
width: number;
height: number;
frames: TextureFrame[];
}
/** How a key blends into the next one (#101); absent is `linear`. `step` holds until the next key. */
export type KeyInterpolation = 'linear' | 'step' | 'ease-in' | 'ease-out' | 'ease-in-out';
/**
* A value on a whole frame. Its interpolation rules the way to the next key. A value that cannot
* blend (a texture, a switch) holds until the next key whatever it says.
*/
export interface AnimationKey {
frame: number;
value: PropertyValue;
interpolation?: KeyInterpolation;
}
/**
* One row of the timeline: a property of the root (`target` empty) or of an object inside it
* (`target` = the template object's id). `keys` are sorted by frame, at most one per frame.
* `relative` adds a number to the value the target had when the animation started.
*/
export interface AnimationTrack {
id: string;
target: string;
property: string;
relative?: boolean;
keys: AnimationKey[];
}
/**
* A timeline over one root asset – a sprite, text, container or scene. It is played on an object,
* and its rows reach that object and the objects inside it, so every instance plays on its own.
* The last key decides how long it runs (see `model/animation.ts`).
*/
export interface AnimationAsset extends AssetBase {
type: 'animation';
root: string;
tracks: AnimationTrack[];
}
/**
* The graph holds one `State` node per state and an edge per transition; a behavior reads the
* machine's current state through `State branch` and moves it through `Set state` or `State transition`.
* A `global` machine has one current state for the whole run instead of one per behavior instance.
*
* `start` is the id of the state every instance begins in, empty when the machine has no start –
* then it is in no state until `Set state` runs. It lives on the asset rather than on the node
* because only one state may be the start, and that is structural here instead of a rule someone
* has to keep. A deleted state leaves a dead id behind, which reads as no start.
*/
export interface StateMachineAsset extends AssetBase {
type: 'statemachine';
graph: Graph;
scope?: Scope;
start?: string;
}
/**
* The graph is GLSL in nodes: `Property` nodes declare the sprite rows, the `Output` node ends
* the fragment (see `core/shader/compile.ts`). A sprite wears it through its `shader` property.
*/
export interface ShaderAsset extends AssetBase {
type: 'shader';
graph: Graph;
}
/**
* `file` names the typeface in `data/fonts/` the way a texture names its image: content-addressed
* `{sha256}.{ext}`, empty when the upload failed. A text object wears it through its `font`
* property; the family the browser knows it under is derived from the file name, see `model/font`.
*/
export interface FontAsset extends AssetBase {
type: 'font';
file: string;
}
/**
* `file` names the sound in `data/sounds/` the way a font names its typeface: content-addressed
* `{sha256}.{ext}`, empty when the upload failed. Nothing wears a sound – a graph plays it with
* `Play sound`, and `props` says how loud and whether it repeats (see `properties/sounds.ts`).
*/
export interface SoundAsset extends AssetBase {
type: 'sound';
file: string;
}
/**
* A tile that draws its own edges: `pictures` names one block per animation step (a texture id for
* a sheet that holds nothing else, a frame id for one block cut out of a sheet), and `props` say
* which kind of block it is, how fast it runs and whether it loops or bounces. What a block is cut
* into and which quarter goes where is `model/autotile.ts`; placing one is the tilemap of #128.
*/
export interface AutotileAsset extends AssetBase {
type: 'autotile';
pictures: string[];
}
/**
* The graph is the motion of one particle: every output of its `Output` node is a function of the
* particle's age and its own random number (see `core/particles/compile.ts`). A placed object is
* the source that emits them; how many and for how long are its properties.
*/
export interface ParticlesAsset extends AssetBase {
type: 'particles';
graph: Graph;
}
export type Asset =
| SpriteAsset
| CameraAsset
| TextAsset
| SceneAsset
| BehaviorAsset
| VariablesAsset
| ColliderAsset
| ContainerAsset
| TextureAsset
| AnimationAsset
| AutotileAsset
| StateMachineAsset
| ShaderAsset
| FontAsset
| SoundAsset
| ParticlesAsset;
/** The assets edited as a node graph – what the graph canvas and the graph commands work on. */
export type GraphAsset = BehaviorAsset | StateMachineAsset | ShaderAsset | ParticlesAsset;
const GRAPH_TYPES: ReadonlySet<AssetType> = new Set(['behavior', 'statemachine', 'shader', 'particles']);
export function isGraphAsset(asset: Asset): asset is GraphAsset {
return GRAPH_TYPES.has(asset.type);
}
export function isSceneAsset(asset: Asset): asset is SceneAsset {
return asset.type === 'scene';
}