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 = 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'; }