diff --git a/engine/Animation.ts b/engine/Animation.ts deleted file mode 100644 index 84b9e0d..0000000 --- a/engine/Animation.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { drawSprite, type Sprite } from "./Sprite.ts"; - -export const spriteMap = new Map(); - -export type Animation = { - spriteSrc: string; - xIndex: number; - yIndex: number; - frameTime: number; - frameDelta: number; -}; - -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, - frameDelta: 0, - }; -} - -export function playAnimation( - ctx: CanvasRenderingContext2D, - 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/Collision.ts b/engine/Collision.ts index 32e5b5b..080c32b 100644 --- a/engine/Collision.ts +++ b/engine/Collision.ts @@ -1,13 +1,5 @@ import type { Vector } from "#/engine/Vector.ts"; -export type Body = { - shape: T; - offset: Vector; -}; - -export function moveAndCollide( - body: Body, - velocity: Vector, -): void { +export function moveAndCollide(body: T, velocity: Vector): void { return; } diff --git a/engine/Entity.ts b/engine/Entity.ts index aa0128e..9694210 100644 --- a/engine/Entity.ts +++ b/engine/Entity.ts @@ -1,45 +1,20 @@ -import type { Animation } from "./Animation.ts"; -import { createVector, type Vector } from "./Vector.ts"; - -export type Indirect = never; - -export type Entity = { +export type Entity = { name: string; - position: Vector; - animation: Animation; -} & T; + state: T; +}; -export type Animate = ( - entity: Entity, - ctx: CanvasRenderingContext2D, - delta: number, -) => void; +export type Process = (instance: T, delta: number) => void; -export type Process = (entity: Entity, delta: number) => void; +export const entityMap = new Map(); +export const entityProcessMap = new Map(); -export const entityMap = new Map>(); -export const entityAnimateMap = new Map>(); -export const entityProcessMap = new Map>(); - -export function defineEntity( +export function defineEntity( name: string, state: T, -): { - animate: (fn: Animate) => void; - process: (fn: Process) => void; -} { - entityMap.set(name, { - name, - position: createVector(), - ...state, - }); +): (fn: Process) => void { + entityMap.set(name, state); - return { - animate(fn) { - entityAnimateMap.set(name, fn); - }, - process(fn) { - entityProcessMap.set(name, fn); - }, + return (fn) => { + entityProcessMap.set(name, fn); }; } diff --git a/engine/Scene.ts b/engine/Scene.ts index d6199d0..b136bbe 100644 --- a/engine/Scene.ts +++ b/engine/Scene.ts @@ -1,28 +1,29 @@ import { - type Animate as AnimateEntity, type Entity, - entityAnimateMap, entityMap, entityProcessMap, - type Indirect, type Process as ProcessEntity, -} from "./Entity.ts"; -import { type Graph, getNeighbours } from "./Graph.ts"; -import { useWithAsyncCache } from "./lib/cache.ts"; +} from "#/engine/Entity.ts"; +import { type Graph, getNeighbours } from "#/engine/Graph.ts"; +import { useWithAsyncCache } from "#/engine/lib/cache.ts"; + +export type EntityData = { + name: string; + init: object; +}; export type SceneData = { name: string; width: number; height: number; - entities: Array>; + entities: Array; }; export type EntityInstanceMap = Map< string, { - animate: AnimateEntity; - process: ProcessEntity; - instances: Array>; + process: ProcessEntity; + instances: Array; } >; @@ -41,42 +42,43 @@ export type SceneChangeHandler = (to: SceneData, from: SceneData) => void; export const sceneProcessMap = new Map(); export const scenePreProcessMap = new Map(); export const scenePostProcessMap = new Map(); -export const sceneDataMap = new Map(); -export const sceneEntiyMap = new Map(); + export const sceneChangedHandlers = new Set(); + +export const sceneDataMap = new Map(); +export const sceneEntiyInstanceMap = new Map(); export const sceneGraph: Graph = new Map(); -const sceneInstancesMap = new Map>>(); +const sceneInstancesMap = new Map>(); export const [loadScene, sceneCache] = useWithAsyncCache( async (name: string) => { await import(`#/game/scenes/${name}/scene.ts`); const data = sceneDataMap.get(name)!; - const entityInstanceMap = sceneEntiyMap.get(name)!; + const entityInstanceMap = sceneEntiyInstanceMap.get(name)!; entityInstanceMap.clear(); await Promise.all( - data.entities.map(async (entity: Entity) => { - await import(`#/game/entities/${entity.name}/entity.ts`); + data.entities.map(async (entityData: EntityData) => { + await import(`#/game/entities/${entityData.name}/entity.ts`); - if (!entityInstanceMap.has(entity.name)) { - entityInstanceMap.set(entity.name, { - animate: entityAnimateMap.get(entity.name) ?? (() => {}), - process: entityProcessMap.get(entity.name) ?? (() => {}), + if (!entityInstanceMap.has(entityData.name)) { + entityInstanceMap.set(entityData.name, { + process: entityProcessMap.get(entityData.name) ?? (() => {}), instances: [], }); } try { - entityInstanceMap.get(entity.name)!.instances.push( + entityInstanceMap.get(entityData.name)!.instances.push( structuredClone({ - ...entityMap.get(entity.name)!, - ...entity, + ...entityMap.get(entityData.name)!, + ...entityData, }), ); } catch (error) { - console.error(entity.name, error); + console.error(entityData.name, error); } }), ); @@ -94,21 +96,21 @@ export const currentScene: Scene = { entityInstanceMap: new Map(), }; -function animateEntities(ctx: CanvasRenderingContext2D, delta: number): void { - const instances = sceneInstancesMap.get(currentScene.data.name)!; - instances.sort((a, b) => a.position.y - b.position.y); +// function animateEntities(ctx: CanvasRenderingContext2D, delta: number): void { +// const instances = sceneInstancesMap.get(currentScene.data.name)!; +// instances.sort((a, b) => a.position.y - b.position.y); - for (const instance of instances) { - currentScene.entityInstanceMap - .get(instance.name)! - .animate(instance as Entity, ctx, delta); - } -} +// for (const instance of instances) { +// currentScene.entityInstanceMap +// .get(instance.name)! +// .animate(instance as Entity, ctx, delta); +// } +// } function processEntities(delta: number): void { for (const entity of currentScene.entityInstanceMap.values()) { for (const instance of entity.instances) { - entity.process(instance as Entity, delta); + entity.process(instance, delta); } } } @@ -130,7 +132,7 @@ export function defineScene(data: SceneData): { postProcess: (fn: PostProcess) => void; } { sceneDataMap.set(data.name, data); - sceneEntiyMap.set(data.name, new Map()); + sceneEntiyInstanceMap.set(data.name, new Map()); return { linkScenes(ids) { @@ -146,7 +148,7 @@ export function defineScene(data: SceneData): { sceneProcessMap.set(data.name, (ctx, delta) => { processEntities(delta); fn(ctx, delta); - animateEntities(ctx, delta); + // animateEntities(ctx, delta); }); }, preProcess(fn): void { @@ -168,7 +170,7 @@ export async function setScene(name: string): Promise { const data = sceneDataMap.get(name)!; const process = sceneProcessMap.get(name)!; - const entityInstanceMap = sceneEntiyMap.get(name)!; + const entityInstanceMap = sceneEntiyInstanceMap.get(name)!; sceneInstancesMap.set( data.name, entityInstanceMap.values().reduce( @@ -178,18 +180,18 @@ export async function setScene(name: string): Promise { } return acc; }, - [] as Array>, + [] as Array, ), ); const preProcess = scenePreProcessMap.get(name); if (preProcess) { - preProcess(sceneEntiyMap.get(name)!); + preProcess(sceneEntiyInstanceMap.get(name)!); } const postProcess = scenePostProcessMap.get(currentScene.data.name); if (postProcess) { - postProcess(sceneEntiyMap.get(name)!); + postProcess(sceneEntiyInstanceMap.get(name)!); } currentScene.data = data; diff --git a/engine/Sprite.ts b/engine/Sprite.ts deleted file mode 100644 index 92521ea..0000000 --- a/engine/Sprite.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { loadImage } from "./lib/loadImage.ts"; - -export type Sprite = { - image: HTMLImageElement; - xFrames: number; - yFrames: number; - frameWidth: number; - frameHeight: number; -}; - -export function createSprite( - image: HTMLImageElement, - xFrames: number, - yFrames: number, -): Sprite { - return { - image, - xFrames, - yFrames, - frameWidth: image.width / xFrames, - frameHeight: image.height / yFrames, - }; -} - -export async function fromSrc( - src: string, - xFrames: number, - yFrames: number, -): Promise { - return createSprite(await loadImage(src), xFrames, yFrames); -} - -export function drawSprite( - ctx: CanvasRenderingContext2D, - sprite: Sprite, - xIndex: number, - yIndex: number, - x: number, - y: number, - width?: number, - height?: number, -): void { - ctx.drawImage( - sprite.image, - xIndex * sprite.frameWidth, - yIndex * sprite.frameHeight, - sprite.frameWidth, - sprite.frameHeight, - x, - y, - width || sprite.frameWidth, - height || sprite.frameHeight, - ); -} diff --git a/engine/dev/main.tsx b/engine/dev/main.tsx index a6c06a0..eecfac3 100644 --- a/engine/dev/main.tsx +++ b/engine/dev/main.tsx @@ -7,18 +7,15 @@ import { Show, } from "solid-js"; import { render } from "solid-js/web"; -import { spriteMap } from "#/engine/Animation.ts"; import type { Entity } from "#/engine/Entity.ts"; import { currentScene, type SceneData, sceneChangedHandlers, sceneGraph, - sceneProcessMap, setScene, } from "#/engine/Scene.ts"; import { setScale, viewport } from "#/engine/Viewport.ts"; -import { strokeRect } from "../Rect.ts"; import { InputField } from "./controls/InputField.tsx"; import { RangeSlider } from "./controls/RangeSlider.tsx"; import { GraphBrowser } from "./GraphBrowser.tsx"; @@ -76,7 +73,7 @@ const DevTools: Component<{ gameContainer: HTMLElement }> = ({ const [entities, setEntities] = createSignal( mapSceneEntities(currentScene.data), ); - const [activeEntity, setActiveEntity] = createSignal>( + const [activeEntity, setActiveEntity] = createSignal>( null, ); @@ -96,47 +93,48 @@ const DevTools: Component<{ gameContainer: HTMLElement }> = ({ }); const selectEntity = (items: ItemsRef): void => { - const activeRef = items.find((ref) => ref.item.isActive)!; - const instances = currentScene.entityInstanceMap.get( - activeRef.item.label, - )!.instances; + console.log(items); + // const activeRef = items.find((ref) => ref.item.isActive)!; + // const instances = currentScene.entityInstanceMap.get( + // activeRef.item.label, + // )!.instances; - const entityRefs = items.filter( - (ref) => ref.item.label === activeRef.item.label, - ); - const instance = - instances[entityRefs.findIndex((ref) => ref.item.isActive)]; + // const entityRefs = items.filter( + // (ref) => ref.item.label === activeRef.item.label, + // ); + // const instance = + // instances[entityRefs.findIndex((ref) => ref.item.isActive)]; - setActiveEntity(instance); - const sprite = spriteMap.get(instance.animation.spriteSrc)!; + // setActiveEntity(instance); + // const sprite = spritesSheetsMap.get(instance.animation.spriteSrc)!; - const processProxy = sceneProcessMap.get(currentScene.data.name)!; - const ctx = viewport.ctx; - currentScene.process = (...args) => { - processProxy(...args); + // const processProxy = sceneProcessMap.get(currentScene.data.name)!; + // const ctx = viewport.ctx; + // currentScene.process = (...args) => { + // processProxy(...args); - ctx.save(); - ctx.globalAlpha = 0.75; - ctx.lineWidth = 1; - ctx.strokeStyle = "#6572f5"; - viewport.ctx.strokeRect( - instance.position.x - 0.5, - instance.position.y - 0.5, - sprite.image.width / sprite.xFrames + 1, - sprite.image.height / sprite.yFrames + 1, - ); + // ctx.save(); + // ctx.globalAlpha = 0.75; + // ctx.lineWidth = 1; + // ctx.strokeStyle = "#6572f5"; + // viewport.ctx.strokeRect( + // instance.position.x - 0.5, + // instance.position.y - 0.5, + // sprite.sheet.width / sprite.xFrames + 1, + // sprite.sheet.height / sprite.yFrames + 1, + // ); - ctx.fillStyle = "#ee459e"; - viewport.ctx.fillRect( - instance.position.x - 1, - instance.position.y - 1, - 2, - 2, - ); - ctx.beginPath(); - ctx.fill(); - ctx.restore(); - }; + // ctx.fillStyle = "#ee459e"; + // viewport.ctx.fillRect( + // instance.position.x - 1, + // instance.position.y - 1, + // 2, + // 2, + // ); + // ctx.beginPath(); + // ctx.fill(); + // ctx.restore(); + // }; }; onMount(() => { @@ -205,19 +203,19 @@ const DevTools: Component<{ gameContainer: HTMLElement }> = ({ { - activeEntity()!.position.x = parseInt(value); + // activeEntity()!.position.x = parseInt(value); }} > { - activeEntity()!.position.y = parseInt(value); + // activeEntity()!.position.y = parseInt(value); }} > diff --git a/engine/sprites.ts b/engine/sprites.ts new file mode 100644 index 0000000..e893202 --- /dev/null +++ b/engine/sprites.ts @@ -0,0 +1,83 @@ +export type SpritesSheet = { + image: HTMLImageElement; + xFrames: number; + yFrames: number; + frameWidth: number; + frameHeight: number; +}; + +export type SpriteAnimation = { + sheet: SpritesSheet; + xIndex: number; + yIndex: number; + frameTime: number; + frameDelta: number; +}; + +export function createSpritesSheet( + image: HTMLImageElement, + xFrames: number, + yFrames: number, +): SpritesSheet { + return { + image, + xFrames, + yFrames, + frameWidth: image.width / xFrames, + frameHeight: image.height / yFrames, + }; +} + +export function createSpriteAnimation( + sheet: SpritesSheet, + frameTime: number, + yIndex: number, +): SpriteAnimation { + return { + sheet, + xIndex: 0, + yIndex, + frameTime, + frameDelta: 0, + }; +} + +export function drawSprite( + ctx: CanvasRenderingContext2D, + sheet: SpritesSheet, + xIndex: number, + yIndex: number, + x: number, + y: number, + width?: number, + height?: number, +): void { + ctx.drawImage( + sheet.image, + xIndex * sheet.frameWidth, + yIndex * sheet.frameHeight, + sheet.frameWidth, + sheet.frameHeight, + x, + y, + width || sheet.frameWidth, + height || sheet.frameHeight, + ); +} + +export function playAnimation( + ctx: CanvasRenderingContext2D, + animation: SpriteAnimation, + x: number, + y: number, + delta: number, +): void { + if ((animation.frameDelta += delta) > animation.frameTime) { + animation.frameDelta = 0; + if ((animation.xIndex += 1) >= animation.sheet.xFrames) { + animation.xIndex = 0; + } + } + + drawSprite(ctx, animation.sheet, animation.xIndex, animation.yIndex, x, y); +} diff --git a/game/entities/hero/entity.ts b/game/entities/hero/entity.ts index a38d2c0..6ffe656 100644 --- a/game/entities/hero/entity.ts +++ b/game/entities/hero/entity.ts @@ -1,34 +1,35 @@ -import { createAnimation, playAnimation } from "#/engine/Animation.ts"; -import { createCircle } from "#/engine/Circle"; +import { type Circle, createCircle } from "#/engine/Circle.ts"; import { moveAndCollide } from "#/engine/Collision.ts"; import { defineEntity } from "#/engine/Entity.ts"; import { keyboardInput } from "#/engine/Keyboard.ts"; +import { loadImage } from "#/engine/lib/loadImage"; import { pointer } from "#/engine/Pointer.ts"; -import { fromSrc, type Sprite } from "#/engine/Sprite.ts"; -import { createVector } from "#/engine/Vector.ts"; +import { + createSpriteAnimation, + createSpritesSheet, + type SpriteAnimation, +} from "#/engine/sprites.ts"; +import { createVector, type Vector } from "#/engine/Vector.ts"; import charIdle from "#/game/assets/char-idle.png"; -const idle: Sprite = await fromSrc(charIdle, 2, 4); +export type Hero = { + position: Vector; + velocity: Vector; + body: Circle; + animation: SpriteAnimation; +}; + const speed = 0.05; +const idleSheet = createSpritesSheet(await loadImage(charIdle), 2, 4); -const { animate, process } = defineEntity("hero", { - animation: createAnimation(idle, 500, 2), +const state: Hero = { + position: createVector(), velocity: createVector(), - body: { - shape: createCircle(createVector(), 4), - offset: createVector(8, 10), - }, -}); + body: createCircle(createVector(), 4), + animation: createSpriteAnimation(idleSheet, 500, 2), +}; -animate((entity, ctx, delta) => { - playAnimation( - ctx, - entity.animation, - entity.position.x, - entity.position.y, - delta, - ); -}); +const process = defineEntity("hero", state); process((entity, delta) => { if (pointer.isDown) { @@ -49,6 +50,6 @@ process((entity, delta) => { moveAndCollide(entity.body, entity.velocity); - entity.body.shape.position.x = entity.body.offset.x + entity.position.x; - entity.body.shape.position.y = entity.body.offset.y + entity.position.y; + // entity.body.position.x = entity.body.offset.x + entity.position.x; + // entity.body.position.y = entity.body.offset.y + entity.position.y; }); diff --git a/game/entities/portal/entity.ts b/game/entities/portal/entity.ts index 3fe4ff8..457e548 100644 --- a/game/entities/portal/entity.ts +++ b/game/entities/portal/entity.ts @@ -1,20 +1,10 @@ -import { createAnimation, playAnimation } from "#/engine/Animation.ts"; import { defineEntity } from "#/engine/Entity.ts"; -import { fromSrc, type Sprite } from "#/engine/Sprite.ts"; +import { loadImage } from "#/engine/lib/loadImage.ts"; +import { createSpriteAnimation, createSpritesSheet } from "#/engine/sprites.ts"; import portal from "#/game/assets/portal.png"; -const sprite: Sprite = await fromSrc(portal, 3, 1); +const portalSheet = createSpritesSheet(await loadImage(portal), 3, 1); -const { animate } = defineEntity("portal", { - animation: createAnimation(sprite, 400, 0), -}); - -animate((entity, ctx, delta) => { - playAnimation( - ctx, - entity.animation, - entity.position.x, - entity.position.y, - delta, - ); +defineEntity("portal", { + animation: createSpriteAnimation(portalSheet, 400, 0), }); diff --git a/game/entities/testEntity/entity.ts b/game/entities/testEntity/entity.ts index 0333739..632b259 100644 --- a/game/entities/testEntity/entity.ts +++ b/game/entities/testEntity/entity.ts @@ -1,49 +1,41 @@ -import { - type Animation, - createAnimation, - playAnimation, -} from "#/engine/Animation.ts"; import { defineEntity } from "#/engine/Entity.ts"; import { loadImage } from "#/engine/lib/loadImage.ts"; -import { createSprite } from "#/engine/Sprite.ts"; +import { + createSpriteAnimation, + createSpritesSheet, + type SpriteAnimation, +} from "#/engine/sprites.ts"; +import { createVector, type Vector } from "#/engine/Vector.ts"; import { viewport } from "#/engine/Viewport.ts"; import charIdle from "#/game/assets/char-idle.png"; import charWalk from "#/game/assets/char-walk.png"; const sprites = { - idle: createSprite(await loadImage(charIdle), 2, 4), - walk: createSprite(await loadImage(charWalk), 4, 4), + idle: createSpritesSheet(await loadImage(charIdle), 2, 4), + walk: createSpritesSheet(await loadImage(charWalk), 4, 4), }; const animations = { - idle: createAnimation(sprites.idle, 500, 2), - walk: createAnimation(sprites.walk, 250, 2), + idle: createSpriteAnimation(sprites.idle, 500, 2), + walk: createSpriteAnimation(sprites.walk, 250, 2), }; -const { animate, process } = defineEntity<{ - animation: Animation; +const process = defineEntity<{ + position: Vector; + animation: SpriteAnimation; animations: { - idle: Animation; - walk: Animation; + idle: SpriteAnimation; + walk: SpriteAnimation; }; stuff: number; }>("testEntity", { + position: createVector(), animations, animation: animations.idle, stuff: 1, }); -animate((entity, ctx, delta) => { - playAnimation( - ctx, - entity.animation, - entity.position.x, - entity.position.y, - delta, - ); -}); - -process((entity, delta) => { +process((entity, _delta) => { entity.position.x += entity.stuff * 0.5; if (entity.position.x > viewport.canvas.width - 16) { entity.stuff = -1; diff --git a/game/scenes/barScene/data.json b/game/scenes/barScene/data.json index 675f68a..cf9d1fe 100644 --- a/game/scenes/barScene/data.json +++ b/game/scenes/barScene/data.json @@ -5,9 +5,11 @@ "entities": [ { "name": "hero", - "position": { - "x": 0, - "y": 0 + "init": { + "position": { + "x": 0, + "y": 0 + } } } ] diff --git a/game/scenes/fooScene/data.json b/game/scenes/fooScene/data.json index 6b49044..5433d44 100644 --- a/game/scenes/fooScene/data.json +++ b/game/scenes/fooScene/data.json @@ -5,16 +5,20 @@ "entities": [ { "name": "hero", - "position": { - "x": 0, - "y": 0 + "init": { + "position": { + "x": 0, + "y": 0 + } } }, { "name": "testEntity", - "position": { - "x": 24, - "y": 16 + "init": { + "position": { + "x": 24, + "y": 16 + } } } ] diff --git a/game/scenes/testScene/data.json b/game/scenes/testScene/data.json index 01757ca..4bcd694 100644 --- a/game/scenes/testScene/data.json +++ b/game/scenes/testScene/data.json @@ -1,27 +1,33 @@ { "name": "testScene", - "width": 80, - "height": 48, + "width": 240, + "height": 160, "entities": [ { "name": "hero", - "position": { - "x": 0, - "y": 0 + "init": { + "position": { + "x": 0, + "y": 0 + } } }, { "name": "portal", - "position": { - "x": 16, - "y": 16 + "init": { + "position": { + "x": 16, + "y": 16 + } } }, { "name": "testEntity", - "position": { - "x": 32, - "y": 32 + "init": { + "position": { + "x": 32, + "y": 32 + } } } ] diff --git a/game/scenes/testScene/scene.ts b/game/scenes/testScene/scene.ts index 975a743..d58e682 100644 --- a/game/scenes/testScene/scene.ts +++ b/game/scenes/testScene/scene.ts @@ -1,6 +1,7 @@ -import { strokeCircle } from "#/engine/Circle"; +import { strokeCircle } from "#/engine/Circle.ts"; import { pointer } from "#/engine/Pointer.ts"; import { defineScene } from "#/engine/Scene.ts"; +import type { Hero } from "#/game/entities/hero/entity.ts"; import data from "./data.json"; const { process, preProcess, linkScenes } = defineScene(data); @@ -8,14 +9,14 @@ const { process, preProcess, linkScenes } = defineScene(data); linkScenes(["fooScene"] as const); preProcess((entityInstanceMap) => { - const entity = entityInstanceMap.get(data.entities[0].name); - const instance = entity!.instances[0]; + const entry = entityInstanceMap.get("hero")!; + const instance = entry.instances[0] as Hero; - instance.position.x = data.width - 16; - instance.position.y = data.height - 16; + instance.position.x = data.width - instance.animation.sheet.frameHeight; + instance.position.y = data.height - instance.animation.sheet.frameWidth; }); -process((ctx, delta) => { +process((ctx, _delta) => { if (!pointer.isDown) { strokeCircle(ctx, { position: pointer.position,