From 74a77fa2814a6577e1b3a816f17b6604383abdb2 Mon Sep 17 00:00:00 2001 From: kolaflash Date: Sat, 17 Jan 2026 14:52:42 +0100 Subject: [PATCH] Improve animation handling --- dev/main.tsx | 22 ++++++++++++++++------ {lib => engine}/Animation.ts | 19 ++++++++++++++++--- engine/Scene.ts | 16 ++++++++++------ entities/barEntity/entity.ts | 7 +++---- entities/fooEntity/entity.ts | 7 +++---- entities/portal/entity.ts | 19 +++++++++++++++++++ entities/testEntity/entity.ts | 30 +++++++++++++++--------------- scenes/testScene/data.json | 4 ++-- 8 files changed, 84 insertions(+), 40 deletions(-) rename {lib => engine}/Animation.ts (64%) create mode 100644 entities/portal/entity.ts diff --git a/dev/main.tsx b/dev/main.tsx index dfe9569..cd918f3 100644 --- a/dev/main.tsx +++ b/dev/main.tsx @@ -16,6 +16,7 @@ import { FileIcon } from "#/dev/icons/File.tsx"; import { LetterXIcon } from "#/dev/icons/LetterX.tsx"; import { LetterYIcon } from "#/dev/icons/LetterY.tsx"; import { ZoomScanIcon } from "#/dev/icons/ZoomScan.tsx"; +import { type Animation, spriteMap } from "#/engine/Animation.ts"; import type { Entity } from "#/engine/Entity.ts"; import { currentScene, @@ -106,6 +107,7 @@ const DevTools: Component<{ gameContainer: HTMLElement }> = ({ instances[entityRefs.findIndex((ref) => ref.item.isActive)]; setActiveEntity(instance); + const sprite = spriteMap.get(instance.animation.spriteSrc)!; const processProxy = sceneProcessMap.get(currentScene.data.name)!; const ctx = viewport.ctx; @@ -114,12 +116,20 @@ const DevTools: Component<{ gameContainer: HTMLElement }> = ({ ctx.save(); ctx.lineWidth = 1; - ctx.strokeStyle = "#ee459e"; + ctx.strokeStyle = "#6572f5"; viewport.ctx.strokeRect( - instance.position.x + 0.5, - instance.position.y + 0.5, - 16 - 1, - 16 - 1, + instance.position.x - 0.5, + instance.position.y - 0.5, + sprite.image.width / sprite.xFrames + 1, + sprite.image.height / sprite.yFrames + 1, + ); + + ctx.fillStyle = "#ee459e"; + viewport.ctx.fillRect( + instance.position.x - 1, + instance.position.y - 1, + 2, + 2, ); ctx.restore(); }; @@ -182,7 +192,7 @@ const DevTools: Component<{ gameContainer: HTMLElement }> = ({ - + diff --git a/lib/Animation.ts b/engine/Animation.ts similarity index 64% rename from lib/Animation.ts rename to engine/Animation.ts index 39d35d4..a614192 100644 --- a/lib/Animation.ts +++ b/engine/Animation.ts @@ -1,14 +1,25 @@ import { drawSprite, type Sprite } from "#/lib/Sprite.ts"; +export const spriteMap = new Map(); + export type Animation = { + spriteSrc: string; xIndex: number; yIndex: number; frameTime: number; frameDelta: number; }; -export function createAnimation(yIndex: number, frameTime: number): Animation { +export function createAnimation( + sprite: Sprite, + frameTime: number, + yIndex: number, +): Animation { + const spriteSrc = sprite.image.src; + spriteMap.set(spriteSrc, sprite); + return { + spriteSrc, xIndex: 0, yIndex, frameTime, @@ -16,19 +27,21 @@ export function createAnimation(yIndex: number, frameTime: number): Animation { }; } -export function animateSprite( +export function playAnimation( ctx: CanvasRenderingContext2D, - sprite: Sprite, animation: Animation, x: number, y: number, delta: number, ): void { + const sprite = spriteMap.get(animation.spriteSrc)!; + if ((animation.frameDelta += delta) > animation.frameTime) { animation.frameDelta = 0; if ((animation.xIndex += 1) >= sprite.xFrames) { animation.xIndex = 0; } } + drawSprite(ctx, sprite, animation.xIndex, animation.yIndex, x, y); } diff --git a/engine/Scene.ts b/engine/Scene.ts index 9a43f62..f2cec9f 100644 --- a/engine/Scene.ts +++ b/engine/Scene.ts @@ -66,12 +66,16 @@ export const [loadScene, sceneCache] = useWithAsyncCache( }); } - const instance: Entity = structuredClone({ - ...entityMap.get(entity.name)!, - ...entity, - }); - - entityInstanceMap.get(entity.name)!.instances.push(instance); + try { + entityInstanceMap.get(entity.name)!.instances.push( + structuredClone({ + ...entityMap.get(entity.name)!, + ...entity, + }), + ); + } catch (error) { + console.error(entity.name, error); + } }), ); }, diff --git a/entities/barEntity/entity.ts b/entities/barEntity/entity.ts index ce4cfb2..0fdede8 100644 --- a/entities/barEntity/entity.ts +++ b/entities/barEntity/entity.ts @@ -1,18 +1,17 @@ +import { createAnimation, playAnimation } from "#/engine/Animation.ts"; import { useEntity } from "#/engine/Entity.ts"; -import { animateSprite, createAnimation } from "#/lib/Animation.ts"; import { fromSrc, type Sprite } from "#/lib/Sprite.ts"; const idle: Sprite = await fromSrc("/assets/hero/idle.png", 5, 4); const { animate, process } = useEntity(import.meta, { - animation: createAnimation(0, 200), + animation: createAnimation(idle, 200, 0), stuff: 1, }); animate((entity, ctx, delta) => { - animateSprite( + playAnimation( ctx, - idle, entity.animation, entity.position.x, entity.position.y, diff --git a/entities/fooEntity/entity.ts b/entities/fooEntity/entity.ts index 269aa27..2c11f9b 100644 --- a/entities/fooEntity/entity.ts +++ b/entities/fooEntity/entity.ts @@ -1,18 +1,17 @@ import charIdle from "#/assets/char-idle.png"; +import { createAnimation, playAnimation } from "#/engine/Animation.ts"; import { useEntity } from "#/engine/Entity.ts"; -import { animateSprite, createAnimation } from "#/lib/Animation.ts"; import { fromSrc, type Sprite } from "#/lib/Sprite.ts"; const idle: Sprite = await fromSrc(charIdle, 2, 4); const { animate, process } = useEntity(import.meta, { - animation: createAnimation(2, 500), + animation: createAnimation(idle, 500, 2), }); animate((entity, ctx, delta) => { - animateSprite( + playAnimation( ctx, - idle, entity.animation, entity.position.x, entity.position.y, diff --git a/entities/portal/entity.ts b/entities/portal/entity.ts new file mode 100644 index 0000000..f862c3b --- /dev/null +++ b/entities/portal/entity.ts @@ -0,0 +1,19 @@ +import { createAnimation, playAnimation } from "#/engine/Animation.ts"; +import { useEntity } from "#/engine/Entity.ts"; +import { fromSrc, type Sprite } from "#/lib/Sprite.ts"; + +const sprite: Sprite = await fromSrc("./assets/portal.png", 3, 1); + +const { animate } = useEntity(import.meta, { + animation: createAnimation(sprite, 400, 0), +}); + +animate((entity, ctx, delta) => { + playAnimation( + ctx, + entity.animation, + entity.position.x, + entity.position.y, + delta, + ); +}); diff --git a/entities/testEntity/entity.ts b/entities/testEntity/entity.ts index c5a3730..f5d2a37 100644 --- a/entities/testEntity/entity.ts +++ b/entities/testEntity/entity.ts @@ -1,12 +1,12 @@ import charIdle from "#/assets/char-idle.png"; import charWalk from "#/assets/char-walk.png"; -import { useEntity } from "#/engine/Entity.ts"; -import { viewport } from "#/engine/Viewport.ts"; import { type Animation, - animateSprite, createAnimation, -} from "#/lib/Animation.ts"; + playAnimation, +} from "#/engine/Animation.ts"; +import { useEntity } from "#/engine/Entity.ts"; +import { viewport } from "#/engine/Viewport.ts"; import { loadImage } from "#/lib/loadImage.ts"; import { createSprite } from "#/lib/Sprite.ts"; @@ -14,28 +14,28 @@ const sprites = { idle: createSprite(await loadImage(charIdle), 2, 4), walk: createSprite(await loadImage(charWalk), 4, 4), }; +const animations = { + idle: createAnimation(sprites.idle, 500, 2), + walk: createAnimation(sprites.walk, 250, 2), +} as const; const { animate, process } = useEntity<{ + animation: Animation; animations: { idle: Animation; walk: Animation; }; - currentAnimation: "idle" | "walk"; stuff: number; }>(import.meta, { - animations: { - idle: createAnimation(2, 500), - walk: createAnimation(2, 250), - }, - currentAnimation: "idle", + animations, + animation: animations.idle, stuff: 1, }); animate((entity, ctx, delta) => { - animateSprite( + playAnimation( ctx, - sprites[entity.currentAnimation], - entity.animations[entity.currentAnimation], + entity.animation, entity.position.x, entity.position.y, delta, @@ -46,9 +46,9 @@ process((entity, delta) => { entity.position.x += entity.stuff * 0.5; if (entity.position.x > viewport.canvas.width - 16) { entity.stuff = -1; - entity.currentAnimation = "walk"; + entity.animation = entity.animations.walk; } else if (entity.position.x < 0) { entity.stuff = 1; - entity.currentAnimation = "idle"; + entity.animation = entity.animations.idle; } }); diff --git a/scenes/testScene/data.json b/scenes/testScene/data.json index b93e95a..fce5cdf 100644 --- a/scenes/testScene/data.json +++ b/scenes/testScene/data.json @@ -4,14 +4,14 @@ "height": 48, "entities": [ { - "name": "testEntity", + "name": "fooEntity", "position": { "x": 0, "y": 0 } }, { - "name": "fooEntity", + "name": "portal", "position": { "x": 16, "y": 16