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)
game-maker / gate (push) Successful in 1m50s
game-maker / suite (push) Successful in 5m59s

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
adminandClaude Opus 5 committed 2026-09-17 21:35:29 +02:00
1 parent c17b2b5426
commit 1a9f07c4ac
24 files changed
+1797 -18

No files matched your search

+2
View File
@@ -39,6 +39,8 @@ export function buildAsset(type: AssetType, name: string, id = newId()): Asset {
return { ...base, type: 'container', objects: [] };
case 'animation':
return { ...base, type: 'animation', root: '', tracks: [] };
case 'autotile':
return { ...base, type: 'autotile', pictures: [] };
case 'statemachine':
return { ...base, type: 'statemachine', graph: createEmptyGraph() };
case 'shader':
+91
View File
@@ -0,0 +1,91 @@
/**
* The picture list of an autotile (#118): which blocks it is drawn from, and in which order – the
* order is the animation, so moving a picture is an edit of its own rather than a cosmetic sort.
* The kind, the rate and the playback are ordinary properties and go through `SetAssetPropertyCommand`.
*/
import type { Project } from '../model/Project';
import { resolvePicture } from '../model/texture';
import { type Command, CommandError } from './Command';
import { requireAutotile } from './require';
/** Appends a picture; `at` puts it before the picture with that index instead. */
export class AddAutotilePictureCommand implements Command {
readonly label = 'Add autotile picture';
private index = -1;
constructor(
private readonly assetId: string,
private readonly pictureId: string,
private readonly at?: number,
) {}
execute(project: Project): void {
const asset = requireAutotile(project, this.assetId);
// A texture or one of its frames – anything else names no block, and a list of dead ids is
// the one thing the tab could not explain to whoever built it.
if (!resolvePicture(project, this.pictureId))
throw new CommandError(`${this.pictureId} is not a texture or a frame`);
this.index = this.at === undefined ? asset.pictures.length : clampIndex(this.at, asset.pictures.length);
asset.pictures.splice(this.index, 0, this.pictureId);
}
undo(project: Project): void {
requireAutotile(project, this.assetId).pictures.splice(this.index, 1);
}
}
export class RemoveAutotilePictureCommand implements Command {
readonly label = 'Remove autotile picture';
private removed = '';
constructor(
private readonly assetId: string,
private readonly index: number,
) {}
execute(project: Project): void {
const asset = requireAutotile(project, this.assetId);
const picture = asset.pictures[this.index];
if (picture === undefined) throw new CommandError(`${asset.name} has no picture ${this.index + 1}`);
this.removed = picture;
asset.pictures.splice(this.index, 1);
}
undo(project: Project): void {
requireAutotile(project, this.assetId).pictures.splice(this.index, 0, this.removed);
}
}
/** Moves one picture to another place in the list; the step in between is what changes the animation. */
export class MoveAutotilePictureCommand implements Command {
readonly label = 'Reorder autotile pictures';
constructor(
private readonly assetId: string,
private readonly from: number,
private readonly to: number,
) {}
execute(project: Project): void {
this.move(project, this.from, this.to);
}
undo(project: Project): void {
this.move(project, this.to, this.from);
}
private move(project: Project, from: number, to: number): void {
const asset = requireAutotile(project, this.assetId);
const picture = asset.pictures[from];
if (picture === undefined) throw new CommandError(`${asset.name} has no picture ${from + 1}`);
if (to < 0 || to >= asset.pictures.length)
throw new CommandError(`${asset.name} has no place ${to + 1} to move it to`);
asset.pictures.splice(from, 1);
asset.pictures.splice(to, 0, picture);
}
}
function clampIndex(index: number, length: number): number {
if (!Number.isFinite(index)) return length;
return Math.max(0, Math.min(length, Math.floor(index)));
}
+7 -1
View File
@@ -1,7 +1,7 @@
/** Lookups that throw a `CommandError` when the target does not exist. */
import { findNode, type Graph, type GraphEdge, type GraphNode } from '../graph/Graph';
import { type NodeDef, nodeDef } from '../graph/NodeRegistry';
import type { Asset, BehaviorAsset, StateMachineAsset, VariablesAsset } from '../model/Asset';
import type { Asset, AutotileAsset, BehaviorAsset, StateMachineAsset, VariablesAsset } from '../model/Asset';
import { findObject, type ObjectTree } from '../model/hierarchy';
import { isTemplateContent, objectRef, type ObjectRef } from '../model/instance';
import {
@@ -79,6 +79,12 @@ export function requireVariableOwner(project: Project, assetId: string): Behavio
return asset;
}
export function requireAutotile(project: Project, assetId: string): AutotileAsset {
const asset = findAssetOf(project, assetId, 'autotile');
if (!asset) throw new CommandError(`Autotile ${assetId} not found`);
return asset;
}
export function requireStateMachine(project: Project, assetId: string): StateMachineAsset {
const asset = findAssetOf(project, assetId, 'statemachine');
if (!asset) throw new CommandError(`State machine ${assetId} not found`);