Add a four-direction walk demo that seeds its own sprite sheet
tools/seed-actor.mjs builds the project Actor demo: it uploads tools/assets/Actor1.png, slices the 576x384 sheet into the 96 cells of a 12x8 grid (48x48 each, no remainder) and wears the character in the top left corner. Walking is four ping-pong animations of three frames, which is the original RPG-Maker walk cycle; standing is the middle frame of the row, not an animation. The graph pastes walk- or idle- in front of the direction faced and wears the result, so eight cases need no branch tree - the List node only holds numbers. Which way it looks is the key pressed last, corrected whenever only one axis is still held, or letting go of one key of a diagonal would walk backwards. Axis input already normalizes its vector. tools/lib/projects.mjs gains uploadTexture, since data/textures/ is git-ignored and a seed that ships its picture has to send it. The harness gains w/a/s/d and keyDown/keyUp halves, because holdKey cannot express two presses that overlap - which is what a diagonal is. tools/smoke-actor.mjs plays it in 19 checks and reads the drawn pixels at the camera edge, where the bounds hold him walking on the spot: three distinct frames walking, one standing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
1135bf4562
commit
e147dd6d21
7 files changed
+534
No files matched your search
@@ -6,14 +6,19 @@
|
||||
* fixture rewritten by every e2e run (tools/seed-e2e.mjs + tools/e2e.mjs).
|
||||
* jumpRun the example game (tools/seed-jumprun.mjs, played by tools/smoke-jumprun.mjs).
|
||||
* tetris the second example game (tools/seed-tetris.mjs, played by tools/smoke-tetris.mjs).
|
||||
* actor the four-direction walk demo (tools/seed-actor.mjs).
|
||||
*
|
||||
* All ids exist on the server (created via POST ?r=projects); PUT does not create projects.
|
||||
*/
|
||||
import { readFile } from 'node:fs/promises';
|
||||
import { extname } from 'node:path';
|
||||
|
||||
export const PROJECTS = {
|
||||
playground: '649188c15414',
|
||||
fixture: '3caa9b6a0c92',
|
||||
jumpRun: '3d271f3bb58b',
|
||||
tetris: '2072b56583af',
|
||||
actor: '3ccb5c67220b',
|
||||
};
|
||||
|
||||
export const DEFAULT_BASE_URL = 'https://nockal.com/game-maker/';
|
||||
@@ -32,6 +37,40 @@ export function cliArgs(script, defaultId) {
|
||||
return { projectId, baseUrl };
|
||||
}
|
||||
|
||||
const MIME = {
|
||||
png: 'image/png',
|
||||
jpg: 'image/jpeg',
|
||||
jpeg: 'image/jpeg',
|
||||
webp: 'image/webp',
|
||||
gif: 'image/gif',
|
||||
};
|
||||
|
||||
/**
|
||||
* Uploads an image and returns `{file, width, height}` for a texture asset. Storage is
|
||||
* content-addressed, so re-seeding the same file is a no-op rather than a second copy – which is
|
||||
* what makes a seed that ships its own image reproducible (`data/textures/` is git-ignored).
|
||||
*/
|
||||
export async function uploadTexture(baseUrl, filePath) {
|
||||
const bytes = await readFile(filePath);
|
||||
const type = MIME[extname(filePath).slice(1).toLowerCase()];
|
||||
if (!type) {
|
||||
console.error(`unsupported image type: ${filePath}`);
|
||||
process.exit(1);
|
||||
}
|
||||
const res = await fetch(`${baseUrl}api/index.php?r=textures`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': type },
|
||||
body: bytes,
|
||||
signal: AbortSignal.timeout(20_000),
|
||||
});
|
||||
const result = await res.json();
|
||||
if (!res.ok) {
|
||||
console.error('texture upload failed', res.status, result);
|
||||
process.exit(1);
|
||||
}
|
||||
return { file: result.file, width: result.width, height: result.height };
|
||||
}
|
||||
|
||||
/** Overwrites a project, keeping the name it already has on the server. */
|
||||
export async function writeProject(projectId, baseUrl, project) {
|
||||
const url = `${baseUrl}api/index.php?r=projects/${projectId}`;
|
||||
|
||||
Reference in new issue
Block a user