A new Texture tab next to Scene/Game/Graph shows a texture sheet and lets frames be drawn on it: drag to create, move, resize via eight handles, edit name and rect in the inspector, Delete to remove. Every change is a command, so one gesture is one undo step. "Slice..." cuts a grid from columns and rows with optional offset and spacing, previewing the resulting frame count and cell size live. Sprites pick a frame instead of the whole texture through the same `texture` property: it now holds either a texture id or a frame id. Frame ids come from the same generator and are document-wide unique, so one picker, one property and one set of instance overrides cover both cases, and every sprite written before this still means the same thing. A Graphics fill ignores a Pixi sub-texture's frame and draws the whole image, because it derives its uvs from the shape bounds and the inverted style matrix. The cutout is therefore a matrix (`viewport/textureFill.ts`), which keeps a sheet at a single GPU upload no matter how many of its frames are on screen at once. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
128 lines
4.2 KiB
TypeScript
128 lines
4.2 KiB
TypeScript
import type { Graph } from '../graph/Graph';
|
||
import type { PropertyDef } from '../properties/PropertySchema';
|
||
import type { AssetType } from './AssetTypes';
|
||
import type { PropertyBag } from './Properties';
|
||
import type { SceneObject } from './SceneObject';
|
||
|
||
/** Every asset has an id, a name, a type and a bag of typed properties (see PropertySchema). */
|
||
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';
|
||
}
|
||
|
||
/**
|
||
* A line of text drawn in the scene (titles, HUD readouts, buttons). Its content is the
|
||
* ordinary property `text`, so behaviors change it with `Set text` (or `Set property`).
|
||
*/
|
||
export interface TextAsset extends AssetBase {
|
||
type: 'text';
|
||
}
|
||
|
||
/** A scene is an asset that additionally owns the objects placed into it. */
|
||
export interface SceneAsset extends AssetBase {
|
||
type: 'scene';
|
||
objects: SceneObject[];
|
||
}
|
||
|
||
/**
|
||
* A behavior is logic: a node graph plus the variables it declares. Variables are ordinary
|
||
* properties – `variables` is the asset's own property schema, `props` holds their default
|
||
* values, and placed instances override them like any other property.
|
||
*/
|
||
export interface BehaviorAsset extends AssetBase {
|
||
type: 'behavior';
|
||
variables: PropertyDef[];
|
||
graph: Graph;
|
||
}
|
||
|
||
/**
|
||
* A collider is a shape (box or circle) used for collision detection. Placed as a child of
|
||
* the object it belongs to (its "body"); its position is the offset from that body.
|
||
*/
|
||
export interface ColliderAsset extends AssetBase {
|
||
type: 'collider';
|
||
}
|
||
|
||
/**
|
||
* A container is a reusable group of objects (a "prefab"): its `objects` are a template that
|
||
* is instantiated below the container whenever it is placed. Template positions are local
|
||
* to the container's origin – the centre of the group when it was created from a selection.
|
||
* Template ids are private to the asset; every placed copy gets fresh ids and remembers the
|
||
* template object it came from (`SceneObject.templateId`).
|
||
*/
|
||
export interface ContainerAsset extends AssetBase {
|
||
type: 'container';
|
||
objects: SceneObject[];
|
||
}
|
||
|
||
/**
|
||
* One named rectangle of a texture, in image pixels – a frame of a sprite sheet.
|
||
*
|
||
* Its `id` is drawn from the same generator as asset ids and is unique across the document,
|
||
* which is what lets a sprite's `texture` property name either a whole texture or one frame
|
||
* of it (see `model/texture.ts`).
|
||
*/
|
||
export interface TextureFrame {
|
||
id: string;
|
||
name: string;
|
||
x: number;
|
||
y: number;
|
||
w: number;
|
||
h: number;
|
||
}
|
||
|
||
/**
|
||
* An uploaded image. `file` names it in `data/textures/` and is content-addressed
|
||
* (`{sha256}.{ext}`), so the same picture uploaded twice is one file and a stored file can be
|
||
* cached forever – its name cannot ever come to mean different bytes.
|
||
*
|
||
* Like a scene's objects and a behavior's graph, this is intrinsic data rather than a
|
||
* property: nothing about it is editable or overridable per instance. What *is* a property
|
||
* is the reference to it – sprites carry `texture` (see PropertySchema), which is how
|
||
* instance overrides, "Apply to container" and `Set property` work on it for free.
|
||
*
|
||
* `frames` cuts the picture into named rectangles (the Texture tab draws them); a texture
|
||
* with no frames is simply used whole, which is what every single-image texture wants and
|
||
* what every document written before frames existed already says.
|
||
*
|
||
* `width`/`height` are the pixel size as uploaded, kept so a sprite can be sized to its
|
||
* picture without waiting for the image to load – and so frames can be validated and sliced
|
||
* before it arrives. An empty `file` means the upload failed or has not happened yet; such a
|
||
* texture draws as nothing.
|
||
*/
|
||
export interface TextureAsset extends AssetBase {
|
||
type: 'texture';
|
||
file: string;
|
||
width: number;
|
||
height: number;
|
||
frames: TextureFrame[];
|
||
}
|
||
|
||
export type Asset =
|
||
| SpriteAsset
|
||
| CameraAsset
|
||
| TextAsset
|
||
| SceneAsset
|
||
| BehaviorAsset
|
||
| ColliderAsset
|
||
| ContainerAsset
|
||
| TextureAsset;
|
||
|
||
export function isSceneAsset(asset: Asset): asset is SceneAsset {
|
||
return asset.type === 'scene';
|
||
}
|
||
|
||
export function isTextureAsset(asset: Asset): asset is TextureAsset {
|
||
return asset.type === 'texture';
|
||
}
|