Add basic controls

This commit is contained in:
kolaflash
2026-01-23 14:21:21 +01:00
parent 589af23e8c
commit f9a2409dad
19 changed files with 225 additions and 47 deletions
+9 -9
View File
@@ -5,15 +5,15 @@
"": {
"name": "nage",
"devDependencies": {
"@biomejs/biome": "2.3.11",
"@types/bun": "1.3.5",
"lz-string": "1.5.0",
"solid-js": "1.9.10",
"vite": "7.3.1",
"vite-plugin-solid": "2.11.10",
"@biomejs/biome": "latest",
"@types/bun": "latest",
"lz-string": "latest",
"solid-js": "latest",
"vite": "latest",
"vite-plugin-solid": "latest",
},
"peerDependencies": {
"typescript": "5.9.3",
"typescript": "latest",
},
},
},
@@ -192,7 +192,7 @@
"@types/babel__traverse": ["@types/babel__traverse@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="],
"@types/bun": ["@types/bun@1.3.5", "", { "dependencies": { "bun-types": "1.3.5" } }, "sha512-RnygCqNrd3srIPEWBd5LFeUYG7plCoH2Yw9WaZGyNmdTEei+gWaHqydbaIRkIkcbXwhBT94q78QljxN0Sk838w=="],
"@types/bun": ["@types/bun@1.3.6", "", { "dependencies": { "bun-types": "1.3.6" } }, "sha512-uWCv6FO/8LcpREhenN1d1b6fcspAB+cefwD7uti8C8VffIv0Um08TKMn98FynpTiU38+y2dUO55T11NgDt8VAA=="],
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
@@ -206,7 +206,7 @@
"browserslist": ["browserslist@4.28.1", "", { "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", "electron-to-chromium": "^1.5.263", "node-releases": "^2.0.27", "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" } }, "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA=="],
"bun-types": ["bun-types@1.3.5", "", { "dependencies": { "@types/node": "*" } }, "sha512-inmAYe2PFLs0SUbFOWSVD24sg1jFlMPxOjOSSCYqUgn4Hsc3rDc7dFvfVYjFPNHtov6kgUeulV4SxbuIV/stPw=="],
"bun-types": ["bun-types@1.3.6", "", { "dependencies": { "@types/node": "*" } }, "sha512-OlFwHcnNV99r//9v5IIOgQ9Uk37gZqrNMCcqEaExdkVq3Avwqok1bJFmvGMCkCE0FqzdY8VMOZpfpR3lwI+CsQ=="],
"caniuse-lite": ["caniuse-lite@1.0.30001763", "", {}, "sha512-mh/dGtq56uN98LlNX9qdbKnzINhX0QzhiWBFEkFfsFO4QyCvL8YegrJAazCwXIeqkIob8BlZPGM3xdnY+sgmvQ=="],
+1 -6
View File
@@ -40,11 +40,6 @@
--rgb-black: 30, 31, 34;
--c-black: rgb(var(--rgb-black));
--c-primary: var(--c-blurple);
--c-secondary: var(--c-fuchsia);
--c-alert: var(--c-orange);
--c-error: var(--c-red);
--c-bg-0: rgb(30, 31, 34);
--c-bg-1: rgb(43, 45, 49);
--c-bg-2: rgb(49, 51, 56);
@@ -81,7 +76,7 @@
*:focus {
outline-width: 2px;
outline-style: solid;
outline-color: var(--c-primary);
outline-color: var(--c-blurple);
}
}
+2 -3
View File
@@ -21,14 +21,13 @@ export const entityMap = new Map<string, Entity<unknown>>();
export const entityAnimateMap = new Map<string, Animate<Indirect>>();
export const entityProcessMap = new Map<string, Process<Indirect>>();
export function useEntity<T extends { animation: Animation }>(
meta: { url: string },
export function defineEntity<T extends { animation: Animation }>(
name: string,
state: T,
): {
animate: (fn: Animate<T>) => void;
process: (fn: Process<T>) => void;
} {
const name = meta.url.match(/entities\/(.+)\/entity\.ts$/)![1];
entityMap.set(name, {
name,
position: createVector(),
+73
View File
@@ -0,0 +1,73 @@
export type KeyboardInput = {
arrowUp: boolean;
arrowDown: boolean;
arrowLeft: boolean;
arrowRight: boolean;
enter: boolean;
escape: boolean;
};
export const keyboardInput: KeyboardInput = {
arrowUp: false,
arrowDown: false,
arrowLeft: false,
arrowRight: false,
enter: false,
escape: false,
};
function onKeyDown(event: KeyboardEvent) {
switch (event.key) {
case "ArrowUp":
keyboardInput.arrowUp = true;
break;
case "ArrowDown":
keyboardInput.arrowDown = true;
break;
case "ArrowLeft":
keyboardInput.arrowLeft = true;
break;
case "ArrowRight":
keyboardInput.arrowRight = true;
break;
case "Enter":
keyboardInput.enter = true;
break;
case "Escape":
keyboardInput.escape = true;
break;
}
}
function onKeyUp(event: KeyboardEvent) {
switch (event.key) {
case "ArrowUp":
keyboardInput.arrowUp = false;
break;
case "ArrowDown":
keyboardInput.arrowDown = false;
break;
case "ArrowLeft":
keyboardInput.arrowLeft = false;
break;
case "ArrowRight":
keyboardInput.arrowRight = false;
break;
case "Enter":
keyboardInput.enter = false;
break;
case "Escape":
keyboardInput.escape = false;
break;
}
}
export function listenKeyboard(): () => void {
document.addEventListener("keydown", onKeyDown);
document.addEventListener("keyup", onKeyUp);
return () => {
document.removeEventListener("keydown", onKeyDown);
document.removeEventListener("keyup", onKeyUp);
};
}
+84
View File
@@ -0,0 +1,84 @@
import type { Vector } from "#/lib/Vector.ts";
import { viewport } from "./Viewport.ts";
export type Pointer = {
isDown: boolean;
position: Vector;
};
export const pointer: Pointer = {
isDown: false,
position: { x: 0, y: 0 },
};
function onMouseDown(event: MouseEvent): void {
adjustPointerPosition(pointer, event.clientX, event.clientY);
pointer.isDown = event.button === 0 ? true : pointer.isDown;
}
function onTouchStart(event: TouchEvent): void {
adjustPointerPosition(
pointer,
event.touches[0].clientX,
event.touches[0].clientY,
);
pointer.isDown = true;
}
function onMouseMove(event: MouseEvent): void {
adjustPointerPosition(pointer, event.clientX, event.clientY);
}
function onTouchMove(event: TouchEvent): void {
adjustPointerPosition(
pointer,
event.touches[0].clientX,
event.touches[0].clientY,
);
}
function onMouseUp(event: MouseEvent): void {
pointer.isDown = event.button === 0 ? false : pointer.isDown;
}
function onTouchEnd(event: TouchEvent): void {
pointer.isDown = event.touches.length === 0 ? false : pointer.isDown;
}
function onContextMenu(event: MouseEvent): void {
event.preventDefault();
}
function adjustPointerPosition(
pointer: Pointer,
clientX: number,
clientY: number,
): void {
const { ctx, translation: position, initialScale } = viewport;
const clientRect = ctx.canvas.getBoundingClientRect();
pointer.position.x = (clientX - clientRect.x + position.x) / initialScale;
pointer.position.y = (clientY - clientRect.y + position.y) / initialScale;
}
export function listenPointer(): () => void {
const canvas = viewport.ctx.canvas;
canvas.addEventListener("mousedown", onMouseDown);
canvas.addEventListener("touchstart", onTouchStart);
canvas.addEventListener("mousemove", onMouseMove);
canvas.addEventListener("touchmove", onTouchMove);
canvas.addEventListener("mouseup", onMouseUp);
canvas.addEventListener("touchend", onTouchEnd);
canvas.addEventListener("contextmenu", onContextMenu);
return () => {
canvas.removeEventListener("mousedown", onMouseDown);
canvas.removeEventListener("touchstart", onTouchStart);
canvas.removeEventListener("mousemove", onMouseMove);
canvas.removeEventListener("touchmove", onTouchMove);
canvas.removeEventListener("mouseup", onMouseUp);
canvas.removeEventListener("touchend", onTouchEnd);
canvas.removeEventListener("contextmenu", onContextMenu);
};
}
+4 -4
View File
@@ -1,5 +1,3 @@
import { useWithAsyncCache } from "#/lib/cache.ts";
import { type Graph, getNeighbours } from "#/lib/Graph.ts";
import {
type Animate as AnimateEntity,
type Entity,
@@ -8,7 +6,9 @@ import {
entityProcessMap,
type Indirect,
type Process as ProcessEntity,
} from "./Entity.ts";
} from "#/engine/Entity.ts";
import { useWithAsyncCache } from "#/lib/cache.ts";
import { type Graph, getNeighbours } from "#/lib/Graph.ts";
export type SceneData = {
name: string;
@@ -121,7 +121,7 @@ function linkScenes(a: string, b: string): void {
}
}
export function useScene(data: SceneData): {
export function defineScene(data: SceneData): {
linkScenes: <T extends readonly string[]>(
names: T,
) => (name: T[number]) => Promise<void>;
+2 -2
View File
@@ -1,10 +1,10 @@
import { createAnimation, playAnimation } from "#/engine/Animation.ts";
import { useEntity } from "#/engine/Entity.ts";
import { defineEntity } from "#/engine/Entity.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, {
const { animate, process } = defineEntity("barEntity", {
animation: createAnimation(idle, 200, 0),
stuff: 1,
});
@@ -1,11 +1,12 @@
import charIdle from "#/assets/char-idle.png";
import { createAnimation, playAnimation } from "#/engine/Animation.ts";
import { useEntity } from "#/engine/Entity.ts";
import { defineEntity } from "#/engine/Entity.ts";
import { pointer } from "#/engine/Pointer.ts";
import { fromSrc, type Sprite } from "#/lib/Sprite.ts";
const idle: Sprite = await fromSrc(charIdle, 2, 4);
const { animate, process } = useEntity(import.meta, {
const { animate, process } = defineEntity("hero", {
animation: createAnimation(idle, 500, 2),
});
@@ -19,4 +20,9 @@ animate((entity, ctx, delta) => {
);
});
process((entity, delta) => {});
process((entity, delta) => {
if (pointer.isDown) {
entity.position.x = pointer.position.x;
entity.position.y = pointer.position.y;
}
});
+2 -2
View File
@@ -1,10 +1,10 @@
import { createAnimation, playAnimation } from "#/engine/Animation.ts";
import { useEntity } from "#/engine/Entity.ts";
import { defineEntity } 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, {
const { animate } = defineEntity("portal", {
animation: createAnimation(sprite, 400, 0),
});
+5 -4
View File
@@ -5,7 +5,7 @@ import {
createAnimation,
playAnimation,
} from "#/engine/Animation.ts";
import { useEntity } from "#/engine/Entity.ts";
import { defineEntity } from "#/engine/Entity.ts";
import { viewport } from "#/engine/Viewport.ts";
import { loadImage } from "#/lib/loadImage.ts";
import { createSprite } from "#/lib/Sprite.ts";
@@ -14,19 +14,20 @@ 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<{
const { animate, process } = defineEntity<{
animation: Animation;
animations: {
idle: Animation;
walk: Animation;
};
stuff: number;
}>(import.meta, {
}>("testEntity", {
animations,
animation: animations.idle,
stuff: 1,
+3
View File
@@ -1,3 +1,4 @@
import { listenPointer } from "#/engine/Pointer.ts";
import { currentScene, setScene } from "#/engine/Scene.ts";
import {
initViewport,
@@ -30,5 +31,7 @@ function animate(timestamp: number): void {
resizeCanvas(currentScene.data.width, currentScene.data.height);
});
listenPointer();
animate(delta);
})();
+1 -1
View File
@@ -7,7 +7,7 @@
},
"devDependencies": {
"@biomejs/biome": "2.3.11",
"@types/bun": "1.3.5",
"@types/bun": "1.3.6",
"lz-string": "1.5.0",
"solid-js": "1.9.10",
"vite": "7.3.1",
+1 -1
View File
@@ -4,7 +4,7 @@
"height": 160,
"entities": [
{
"name": "fooEntity",
"name": "hero",
"position": {
"x": 0,
"y": 0
+2 -2
View File
@@ -1,7 +1,7 @@
import { useScene } from "#/engine/Scene.ts";
import { defineScene } from "#/engine/Scene.ts";
import data from "./data.json";
const { process, linkScenes } = useScene(data);
const { process, linkScenes } = defineScene(data);
linkScenes(["testScene"] as const);
+2 -2
View File
@@ -4,14 +4,14 @@
"height": 48,
"entities": [
{
"name": "fooEntity",
"name": "hero",
"position": {
"x": 0,
"y": 0
}
},
{
"name": "fooEntity",
"name": "hero",
"position": {
"x": 24,
"y": 16
+2 -2
View File
@@ -1,7 +1,7 @@
import { useScene } from "#/engine/Scene.ts";
import { defineScene } from "#/engine/Scene.ts";
import data from "./data.json";
const { process, linkScenes } = useScene(data);
const { process, linkScenes } = defineScene(data);
linkScenes(["barScene"] as const);
+2 -2
View File
@@ -1,7 +1,7 @@
import { useScene } from "#/engine/Scene.ts";
import { defineScene } from "#/engine/Scene.ts";
import data from "./data.json";
const { process, linkScenes } = useScene(data);
const { process, linkScenes } = defineScene(data);
linkScenes(["testScene"] as const);
+1 -1
View File
@@ -4,7 +4,7 @@
"height": 48,
"entities": [
{
"name": "fooEntity",
"name": "hero",
"position": {
"x": 0,
"y": 0
+20 -3
View File
@@ -1,7 +1,9 @@
import { useScene } from "#/engine/Scene.ts";
import { pointer } from "#/engine/Pointer.ts";
import { defineScene } from "#/engine/Scene.ts";
import type { Vector } from "#/lib/Vector.ts";
import data from "./data.json";
const { process, preProcess, linkScenes } = useScene(data);
const { process, preProcess, linkScenes } = defineScene(data);
linkScenes(["fooScene", "fuzzScene"] as const);
@@ -13,4 +15,19 @@ preProcess((entityInstanceMap) => {
instance.position.y = data.height - 16;
});
process((_ctx, _delta) => {});
function drawPoint(
ctx: CanvasRenderingContext2D,
position: Vector,
radius: number = 4,
): void {
ctx.beginPath();
ctx.arc(position.x, position.y, radius, 0, 2 * Math.PI);
ctx.strokeStyle = "#ff9000";
ctx.stroke();
}
process((ctx, delta) => {
if (!pointer.isDown) {
drawPoint(ctx, pointer.position);
}
});