@shadowdara/webgameengine 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/README.md +127 -0
  2. package/dist/build/buildconfig.d.ts +8 -0
  3. package/dist/build/buildconfig.js +10 -0
  4. package/dist/build/buildhelper.d.ts +3 -0
  5. package/dist/build/buildhelper.js +41 -0
  6. package/dist/build/exporthtml.d.ts +2 -0
  7. package/dist/build/exporthtml.js +58 -0
  8. package/dist/build/index.d.ts +2 -0
  9. package/dist/build/index.js +2 -0
  10. package/dist/build/tools/devserver/cli.d.ts +2 -0
  11. package/dist/build/tools/devserver/cli.js +105 -0
  12. package/dist/build/tools/devserver/config.d.ts +3 -0
  13. package/dist/build/tools/devserver/config.js +18 -0
  14. package/dist/core.d.ts +3 -0
  15. package/dist/core.js +13 -0
  16. package/dist/html.d.ts +16 -0
  17. package/dist/html.js +64 -0
  18. package/dist/index.d.ts +9 -0
  19. package/dist/index.js +16 -0
  20. package/dist/input.d.ts +15 -0
  21. package/dist/input.js +104 -0
  22. package/dist/keys.d.ts +52 -0
  23. package/dist/keys.js +55 -0
  24. package/dist/logger.d.ts +1 -0
  25. package/dist/logger.js +16 -0
  26. package/dist/renderer.d.ts +5 -0
  27. package/dist/renderer.js +17 -0
  28. package/dist/save.d.ts +5 -0
  29. package/dist/save.js +27 -0
  30. package/dist/texture.d.ts +4 -0
  31. package/dist/texture.js +46 -0
  32. package/dist/types/color.d.ts +8 -0
  33. package/dist/types/color.js +18 -0
  34. package/dist/types/index.d.ts +5 -0
  35. package/dist/types/index.js +4 -0
  36. package/dist/types/math-utils.d.ts +3 -0
  37. package/dist/types/math-utils.js +12 -0
  38. package/dist/types/rectangle.d.ts +14 -0
  39. package/dist/types/rectangle.js +31 -0
  40. package/dist/types/vector2d.d.ts +7 -0
  41. package/dist/types/vector2d.js +29 -0
  42. package/dist/types/vector3d.d.ts +8 -0
  43. package/dist/types/vector3d.js +32 -0
  44. package/package.json +40 -0
package/README.md ADDED
@@ -0,0 +1,127 @@
1
+ # WebGameEngine 🎮
2
+
3
+ **(Currently trying to make this into a lib)**
4
+
5
+ **Use `bun tools/build.ts`**
6
+
7
+ A lightweight, TypeScript-first web game engine framework for building 2D games.
8
+
9
+ ## Features
10
+
11
+ - 🎯 Simple game loop management
12
+ - 🎨 Rendering system with text and sprite support
13
+ - ⌨️ Input handling (keyboard & mouse)
14
+ - 📦 TypeScript support out of the box
15
+ - 🛠️ Build tools included
16
+ - 📝 Logging utilities
17
+ - 💾 Save/Load system
18
+
19
+ ## Installation
20
+
21
+ ```bash
22
+ npm install @yourusername/webgameengine
23
+ ```
24
+
25
+ Or use directly from source:
26
+
27
+ ```bash
28
+ bun install
29
+ bun run dev
30
+ ```
31
+
32
+ ## Quick Start
33
+
34
+ ### Basic Game Loop
35
+
36
+ ```typescript
37
+ import { startEngine, setupInput, dlog, renderText } from '@yourusername/webgameengine';
38
+
39
+ const canvas = document.getElementById('gameCanvas') as HTMLCanvasElement;
40
+ const ctx = canvas.getContext('2d')!;
41
+
42
+ setupInput(canvas, 800, 600);
43
+
44
+ function init() {
45
+ dlog('🎮 Game initialized!');
46
+ }
47
+
48
+ function gameLoop(dt: number) {
49
+ // Clear canvas
50
+ ctx.fillStyle = 'black';
51
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
52
+
53
+ // Your game logic here
54
+ renderText(ctx, `FPS: ${(1 / dt).toFixed(0)}`, 10, 20);
55
+ }
56
+
57
+ startEngine(init, gameLoop);
58
+ ```
59
+
60
+ ## Development & Building
61
+
62
+ ### Using Bun (local development)
63
+
64
+ ```bash
65
+ bun tools/build.ts # Build main game
66
+ bun tools/build.ts --entry main2 # Build specific entry
67
+ bun tools/build.ts --release # Production build
68
+
69
+
70
+ ```
71
+
72
+ ### Configuration
73
+
74
+ Edit `project.ts` to configure your game:
75
+
76
+ ```typescript
77
+ import { defineConfig } from './project';
78
+
79
+ export function defineConfig() {
80
+ return {
81
+ entryname: 'main.ts',
82
+ outdir: 'dist',
83
+ // ... other config
84
+ };
85
+ }
86
+ ```
87
+
88
+ or
89
+
90
+ ```typescript
91
+ // Project File for the Game
92
+
93
+ import { type buildconfig, new_buildconfig } from "./engine/build/buildconfig";
94
+
95
+ export function defineConfig(): buildconfig {
96
+ let config: buildconfig = new_buildconfig();
97
+ return config;
98
+ }
99
+ ```
100
+
101
+ ## API Reference
102
+
103
+ ### Core Engine
104
+ - `startEngine(init, gameLoop)` - Initialize game loop
105
+
106
+ ### Rendering
107
+ - `renderText(ctx, text, x, y, color?, font?)` - Render text
108
+ - `renderBitmapText()` - Render bitmap font text
109
+
110
+ ### Input System
111
+ - `setupInput(canvas, width?, height?)` - Initialize input
112
+ - `getKeyState(key)` - Check key state
113
+ - Mouse state available via input module
114
+
115
+ ### Types
116
+ - `Vector2D` / `Vector3D` - Vector mathematics
117
+ - `Color` - Color management
118
+ - `Rect` - Rectangle collision
119
+ - Math utilities for game logic
120
+
121
+ ### Utilities
122
+ - `dlog()` - Development logging
123
+ - `startEngine()` - Manage game loop
124
+
125
+ ## License
126
+
127
+ MIT
@@ -0,0 +1,8 @@
1
+ export interface buildconfig {
2
+ htmlhead: string;
3
+ title: string;
4
+ show_fullscreen_button: boolean;
5
+ entryname: string;
6
+ outdir: string;
7
+ }
8
+ export declare function new_buildconfig(): buildconfig;
@@ -0,0 +1,10 @@
1
+ // Generate the HTML File
2
+ export function new_buildconfig() {
3
+ return {
4
+ htmlhead: "",
5
+ title: "My new Game",
6
+ show_fullscreen_button: true,
7
+ entryname: "main",
8
+ outdir: "dist",
9
+ };
10
+ }
@@ -0,0 +1,3 @@
1
+ export declare const flog: (...args: any[]) => void;
2
+ export declare function copyFolder(src: string, dest: string): Promise<void>;
3
+ export declare function getContentType(path: string): "application/javascript" | "application/typescript" | "text/html" | "text/css" | "image/png" | "text/plain";
@@ -0,0 +1,41 @@
1
+ import { readdirSync, mkdirSync, promises as fsPromises } from "fs";
2
+ import { join } from "path";
3
+ export const flog = (...args) => {
4
+ const now = new Date();
5
+ const time = `[${now.getHours().toString().padStart(2, "0")}:` +
6
+ `${now.getMinutes().toString().padStart(2, "0")}:` +
7
+ `${now.getSeconds().toString().padStart(2, "0")}.` +
8
+ `${now.getMilliseconds().toString().padStart(3, "0")}]`;
9
+ console.log(time, ...args);
10
+ };
11
+ export async function copyFolder(src, dest) {
12
+ // Zielordner erstellen
13
+ mkdirSync(dest, { recursive: true });
14
+ const entries = readdirSync(src, { withFileTypes: true });
15
+ for (const entry of entries) {
16
+ const srcPath = join(src, entry.name);
17
+ const destPath = join(dest, entry.name);
18
+ if (entry.isDirectory()) {
19
+ await copyFolder(srcPath, destPath);
20
+ }
21
+ else if (entry.isFile()) {
22
+ // Datei mit Node.js schreiben
23
+ const data = await fsPromises.readFile(srcPath);
24
+ await fsPromises.writeFile(destPath, data);
25
+ }
26
+ }
27
+ }
28
+ // === Helper ===
29
+ export function getContentType(path) {
30
+ if (path.endsWith(".js"))
31
+ return "application/javascript";
32
+ if (path.endsWith(".ts"))
33
+ return "application/typescript";
34
+ if (path.endsWith(".html"))
35
+ return "text/html";
36
+ if (path.endsWith(".css"))
37
+ return "text/css";
38
+ if (path.endsWith(".png"))
39
+ return "image/png";
40
+ return "text/plain";
41
+ }
@@ -0,0 +1,2 @@
1
+ import type { buildconfig } from "./buildconfig.js";
2
+ export declare function GetDefaultHTML(config: buildconfig): string;
@@ -0,0 +1,58 @@
1
+ // Function to create the Export HTML for the Build
2
+ export function GetDefaultHTML(config) {
3
+ let fullscreenbutton = "";
4
+ let fullscreenBtn = "";
5
+ if (config.show_fullscreen_button) {
6
+ fullscreenbutton = `#fullscreenBtn {
7
+ position: fixed;
8
+ top: 10px;
9
+ right: 10px;
10
+
11
+ padding: 10px 15px;
12
+ font-size: 16px;
13
+
14
+ background: rgba(0, 0, 0, 0.6);
15
+ color: white;
16
+ border: none;
17
+ border-radius: 6px;
18
+
19
+ cursor: pointer;
20
+ z-index: 1000;
21
+ }
22
+
23
+ #fullscreenBtn:hover {
24
+ background: rgba(0, 0, 0, 0.8);
25
+ }`;
26
+ fullscreenBtn = `<!-- Button to make it fullscreen -->
27
+ <button id="fullscreenBtn">⛶ Fullscreen</button>`;
28
+ }
29
+ const defaulthtml = `<!-- HTML Web Game made with webgameengine by Shadowdara -->
30
+ <!-- DO NOT REMOVE THIS NOTE ! -->
31
+ <!DOCTYPE html>
32
+ <html>
33
+ <head>
34
+ <meta charset="UTF-8" />
35
+ <title>${config.title}</title>
36
+ <!-- Für Mobile Viewports -->
37
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
38
+ <style>
39
+ * {
40
+ margin: 0;
41
+ padding: 0;
42
+ box-sizing: border-box;
43
+ }
44
+ body {
45
+ overflow: hidden; /* 🔥 verhindert Scrollbars */
46
+ }
47
+ ${fullscreenbutton}
48
+ </style>
49
+ </style>
50
+ </head>
51
+ <body>
52
+ ${fullscreenBtn}
53
+ <script type="module" src="${config.entryname}.js"></script>
54
+ </body>
55
+ </html>
56
+ `;
57
+ return defaulthtml;
58
+ }
@@ -0,0 +1,2 @@
1
+ export type { buildconfig } from "./buildconfig.js";
2
+ export { new_buildconfig } from "./buildconfig.js";
@@ -0,0 +1,2 @@
1
+ // Buildconfig Export
2
+ export { new_buildconfig } from "./buildconfig.js";
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,105 @@
1
+ #!/usr/bin/env node
2
+ // Shebang
3
+ import { build as esbuild } from "esbuild";
4
+ import { copyFolder, flog, getContentType } from "../../buildhelper.js";
5
+ import { GetDefaultHTML } from "../../exporthtml.js";
6
+ import { createServer } from "http";
7
+ import { readFile, writeFile } from "fs/promises";
8
+ import { watch } from "fs";
9
+ import path from "path";
10
+ import { WebSocketServer } from "ws";
11
+ import { loadUserConfig } from "./config.js";
12
+ const config = await loadUserConfig();
13
+ // console.log("CONFIG:", config);
14
+ let isBuilding = false;
15
+ const isRelease = process.argv.includes("--release");
16
+ const isDev = !isRelease;
17
+ // ================= BUILD =================
18
+ async function build(config) {
19
+ if (isBuilding)
20
+ return;
21
+ isBuilding = true;
22
+ flog("🔄 Baue neu...");
23
+ await esbuild({
24
+ entryPoints: ["./game/" + config.entryname],
25
+ outdir: "./" + config.outdir,
26
+ bundle: true,
27
+ platform: "browser",
28
+ minify: isRelease,
29
+ sourcemap: isDev,
30
+ define: {
31
+ "import.meta.env.DEV": JSON.stringify(isDev),
32
+ },
33
+ });
34
+ const html = GetDefaultHTML(config);
35
+ await writeFile("./dist/index.html", html);
36
+ await copyFolder("./resources", "./dist/resources");
37
+ flog("✅ Build fertig!");
38
+ isBuilding = false;
39
+ }
40
+ // ================= SERVER =================
41
+ let sockets = new Set();
42
+ function startServer() {
43
+ const server = createServer(async (req, res) => {
44
+ let url = req.url || "/";
45
+ let filePath = url === "/" ? "/index.html" : url;
46
+ try {
47
+ const fullPath = path.join(process.cwd(), "dist", filePath);
48
+ const file = await readFile(fullPath);
49
+ res.writeHead(200, {
50
+ "Content-Type": getContentType(filePath),
51
+ });
52
+ res.end(file);
53
+ }
54
+ catch {
55
+ res.writeHead(404);
56
+ res.end("Not Found");
57
+ }
58
+ });
59
+ const wss = new WebSocketServer({ server });
60
+ wss.on("connection", (ws) => {
61
+ sockets.add(ws);
62
+ ws.on("close", () => sockets.delete(ws));
63
+ });
64
+ server.listen(3000, () => {
65
+ flog("🚀 Dev Server läuft auf http://localhost:3000");
66
+ });
67
+ return server;
68
+ }
69
+ // ================= RELOAD =================
70
+ function reloadClients() {
71
+ flog("🔄 Browser reload...");
72
+ for (const ws of sockets) {
73
+ ws.send("reload");
74
+ }
75
+ }
76
+ // ================= WATCH =================
77
+ let restarting = false;
78
+ let pendingRestart = false;
79
+ async function restart() {
80
+ if (restarting) {
81
+ pendingRestart = true;
82
+ return;
83
+ }
84
+ restarting = true;
85
+ do {
86
+ pendingRestart = false;
87
+ flog("♻️ Restart...");
88
+ await build(config);
89
+ reloadClients();
90
+ } while (pendingRestart);
91
+ restarting = false;
92
+ }
93
+ // ================= START =================
94
+ await build(config);
95
+ if (isDev) {
96
+ startServer();
97
+ ["resources", "game"].forEach((dir) => {
98
+ watch(dir, { recursive: true }, async () => {
99
+ flog(`📁 Änderung erkannt in ${dir}`);
100
+ await restart();
101
+ });
102
+ });
103
+ flog("👀 Watcher aktiv...");
104
+ }
105
+ flog(`Build fertig! Modus: ${isRelease ? "Release" : "Dev"}`);
@@ -0,0 +1,3 @@
1
+ import "esbuild-register";
2
+ import { buildconfig } from "../../buildconfig.js";
3
+ export declare function loadUserConfig(): Promise<buildconfig>;
@@ -0,0 +1,18 @@
1
+ import "esbuild-register";
2
+ import path from "path";
3
+ import { pathToFileURL } from "url";
4
+ export async function loadUserConfig() {
5
+ const configPath = path.resolve(process.cwd(), "webgameengine.config.ts");
6
+ try {
7
+ const configUrl = pathToFileURL(configPath).href;
8
+ const mod = await import(configUrl);
9
+ const config = typeof mod.default === "function"
10
+ ? mod.default()
11
+ : mod.default;
12
+ return config;
13
+ }
14
+ catch (e) {
15
+ console.error(e);
16
+ throw new Error("❌ Konnte webgameengine.config.ts nicht laden\nPATH: " + configPath);
17
+ }
18
+ }
package/dist/core.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ type GameLoop = (dt: number) => void;
2
+ export declare function startEngine(start: () => void, gameLoop: GameLoop): void;
3
+ export {};
package/dist/core.js ADDED
@@ -0,0 +1,13 @@
1
+ let lastTime = 0;
2
+ let loop;
3
+ export function startEngine(start, gameLoop) {
4
+ loop = gameLoop;
5
+ start();
6
+ requestAnimationFrame(run);
7
+ }
8
+ function run(time) {
9
+ const dt = (time - lastTime) / 1000;
10
+ lastTime = time;
11
+ loop(dt);
12
+ requestAnimationFrame(run);
13
+ }
package/dist/html.d.ts ADDED
@@ -0,0 +1,16 @@
1
+ export type CanvasConfig = {
2
+ width?: number;
3
+ height?: number;
4
+ fullscreen?: boolean;
5
+ scaling?: "none" | "fit";
6
+ virtualWidth?: number;
7
+ virtualHeight?: number;
8
+ };
9
+ export declare function createCanvas(config?: CanvasConfig): {
10
+ canvas: HTMLCanvasElement;
11
+ ctx: CanvasRenderingContext2D;
12
+ applyScaling: () => void;
13
+ };
14
+ export declare function resizeCanvas(canvas: HTMLCanvasElement): void;
15
+ export declare function enableFullscreen(canvas: HTMLCanvasElement): void;
16
+ export declare function setupFullscreenButton(canvas: HTMLCanvasElement): void;
package/dist/html.js ADDED
@@ -0,0 +1,64 @@
1
+ export function createCanvas(config = {}) {
2
+ const canvas = document.createElement("canvas");
3
+ const ctx = canvas.getContext("2d");
4
+ document.body.appendChild(canvas);
5
+ const virtualWidth = config.virtualWidth ?? 800;
6
+ const virtualHeight = config.virtualHeight ?? 800;
7
+ function resize() {
8
+ if (config.fullscreen) {
9
+ canvas.width = window.innerWidth;
10
+ canvas.height = window.innerHeight;
11
+ }
12
+ else {
13
+ canvas.width = config.width ?? 800;
14
+ canvas.height = config.height ?? 800;
15
+ }
16
+ }
17
+ window.addEventListener("resize", resize);
18
+ resize();
19
+ function applyScaling() {
20
+ if (config.scaling === "fit") {
21
+ const scale = Math.min(canvas.width / virtualWidth, canvas.height / virtualHeight);
22
+ const offsetX = (canvas.width - virtualWidth * scale) / 2;
23
+ const offsetY = (canvas.height - virtualHeight * scale) / 2;
24
+ ctx.setTransform(scale, 0, 0, scale, offsetX, offsetY);
25
+ }
26
+ else {
27
+ ctx.setTransform(1, 0, 0, 1, 0, 0);
28
+ }
29
+ }
30
+ return {
31
+ canvas,
32
+ ctx,
33
+ applyScaling,
34
+ };
35
+ }
36
+ export function resizeCanvas(canvas) {
37
+ canvas.width = window.innerWidth;
38
+ canvas.height = window.innerHeight;
39
+ }
40
+ export function enableFullscreen(canvas) {
41
+ window.addEventListener("keydown", (e) => {
42
+ if (e.key === "f") {
43
+ if (!document.fullscreenElement) {
44
+ canvas.requestFullscreen();
45
+ }
46
+ else {
47
+ document.exitFullscreen();
48
+ }
49
+ }
50
+ });
51
+ }
52
+ export function setupFullscreenButton(canvas) {
53
+ const btn = document.getElementById("fullscreenBtn");
54
+ if (!btn)
55
+ return;
56
+ btn.addEventListener("click", () => {
57
+ if (!document.fullscreenElement) {
58
+ canvas.requestFullscreen();
59
+ }
60
+ else {
61
+ document.exitFullscreen();
62
+ }
63
+ });
64
+ }
@@ -0,0 +1,9 @@
1
+ export { startEngine } from "./core.js";
2
+ export { renderText, renderBitmapText } from "./renderer.js";
3
+ export { setupInput, isKeyJustPressed, resetInput, getMouse } from "./input.js";
4
+ export type { Mouse } from "./input.js";
5
+ export { dlog } from "./logger.js";
6
+ export { saveGame, loadGame, clearSave } from "./save.js";
7
+ export { drawTexture, getTexture, type Texture, loadTextureAsync } from "./texture.js";
8
+ export { createCanvas, enableFullscreen, setupFullscreenButton } from "./html.js";
9
+ export { Key } from "./keys.js";
package/dist/index.js ADDED
@@ -0,0 +1,16 @@
1
+ // Core Engine Exports
2
+ export { startEngine } from "./core.js";
3
+ // Rendering
4
+ export { renderText, renderBitmapText } from "./renderer.js";
5
+ // Input System
6
+ export { setupInput, isKeyJustPressed, resetInput, getMouse } from "./input.js";
7
+ // Logging
8
+ export { dlog } from "./logger.js";
9
+ // Save System
10
+ export { saveGame, loadGame, clearSave } from "./save.js";
11
+ // Texture Management
12
+ export { drawTexture, getTexture, loadTextureAsync } from "./texture.js";
13
+ // HTML Generation
14
+ export { createCanvas, enableFullscreen, setupFullscreenButton } from "./html.js";
15
+ // Keys Reference
16
+ export { Key } from "./keys.js";
@@ -0,0 +1,15 @@
1
+ export type Mouse = {
2
+ x: number;
3
+ y: number;
4
+ pressed: boolean;
5
+ justPressed: boolean;
6
+ justReleased: boolean;
7
+ rightPressed: boolean;
8
+ wheelDelta: number;
9
+ };
10
+ export declare function setupInput(canvas: HTMLCanvasElement, vWidth?: number, vHeight?: number): void;
11
+ export declare function isKeyPressed(code: string): boolean;
12
+ export declare function isKeyJustPressed(code: string): boolean;
13
+ export declare function isKeyJustReleased(code: string): boolean;
14
+ export declare function getMouse(): Readonly<Mouse>;
15
+ export declare function resetInput(): void;
package/dist/input.js ADDED
@@ -0,0 +1,104 @@
1
+ const keys = {};
2
+ const mouse = {
3
+ x: 0,
4
+ y: 0,
5
+ // for Left Buttons
6
+ pressed: false,
7
+ justPressed: false,
8
+ justReleased: false,
9
+ // TODO
10
+ // do the same for the right Buttons
11
+ rightPressed: false,
12
+ wheelDelta: 0,
13
+ };
14
+ let canvasRef;
15
+ // 👉 optional: für scaling (kannst du später aus deiner engine holen)
16
+ let virtualWidth = 800;
17
+ let virtualHeight = 800;
18
+ export function setupInput(canvas, vWidth = 800, vHeight = 800) {
19
+ canvasRef = canvas;
20
+ virtualWidth = vWidth;
21
+ virtualHeight = vHeight;
22
+ // ===== KEYBOARD =====
23
+ window.addEventListener("keydown", (e) => {
24
+ if (!keys[e.code]) {
25
+ keys[e.code] = { pressed: false, justPressed: false, justReleased: false };
26
+ }
27
+ const key = keys[e.code];
28
+ if (!key.pressed)
29
+ key.justPressed = true;
30
+ key.pressed = true;
31
+ });
32
+ window.addEventListener("keyup", (e) => {
33
+ if (!keys[e.code]) {
34
+ keys[e.code] = { pressed: false, justPressed: false, justReleased: false };
35
+ }
36
+ const key = keys[e.code];
37
+ key.pressed = false;
38
+ key.justReleased = true;
39
+ });
40
+ // ===== MOUSE =====
41
+ canvas.addEventListener("mousedown", (e) => {
42
+ if (e.button === 0) {
43
+ if (!mouse.pressed)
44
+ mouse.justPressed = true;
45
+ mouse.pressed = true;
46
+ }
47
+ if (e.button === 2) {
48
+ mouse.rightPressed = true;
49
+ }
50
+ });
51
+ canvas.addEventListener("mouseup", (e) => {
52
+ if (e.button === 0) {
53
+ mouse.pressed = false;
54
+ mouse.justReleased = true;
55
+ }
56
+ if (e.button === 2) {
57
+ mouse.rightPressed = false;
58
+ }
59
+ });
60
+ // 👉 wichtig: verhindert context menu bei right click
61
+ canvas.addEventListener("contextmenu", (e) => e.preventDefault());
62
+ // ===== MOUSE MOVE (mit scaling fix 🔥) =====
63
+ canvas.addEventListener("mousemove", (e) => {
64
+ const rect = canvas.getBoundingClientRect();
65
+ const scale = Math.min(canvas.width / virtualWidth, canvas.height / virtualHeight);
66
+ const offsetX = (canvas.width - virtualWidth * scale) / 2;
67
+ const offsetY = (canvas.height - virtualHeight * scale) / 2;
68
+ mouse.x = (e.clientX - rect.left - offsetX) / scale;
69
+ mouse.y = (e.clientY - rect.top - offsetY) / scale;
70
+ });
71
+ // ===== MOUSE WHEEL =====
72
+ canvas.addEventListener("wheel", (e) => {
73
+ mouse.wheelDelta = e.deltaY;
74
+ });
75
+ // 👉 Fix: wenn Maus Canvas verlässt
76
+ canvas.addEventListener("mouseleave", () => {
77
+ mouse.pressed = false;
78
+ mouse.rightPressed = false;
79
+ });
80
+ }
81
+ // ===== KEY HELPERS =====
82
+ export function isKeyPressed(code) {
83
+ return keys[code]?.pressed || false;
84
+ }
85
+ export function isKeyJustPressed(code) {
86
+ return keys[code]?.justPressed || false;
87
+ }
88
+ export function isKeyJustReleased(code) {
89
+ return keys[code]?.justReleased || false;
90
+ }
91
+ // ===== MOUSE =====
92
+ export function getMouse() {
93
+ return { ...mouse };
94
+ }
95
+ // ===== RESET =====
96
+ export function resetInput() {
97
+ for (const k in keys) {
98
+ keys[k].justPressed = false;
99
+ keys[k].justReleased = false;
100
+ }
101
+ mouse.justPressed = false;
102
+ mouse.justReleased = false;
103
+ mouse.wheelDelta = 0;
104
+ }
package/dist/keys.d.ts ADDED
@@ -0,0 +1,52 @@
1
+ export declare enum Key {
2
+ ArrowUp = "ArrowUp",
3
+ ArrowDown = "ArrowDown",
4
+ ArrowLeft = "ArrowLeft",
5
+ ArrowRight = "ArrowRight",
6
+ Space = "Space",
7
+ Enter = "Enter",
8
+ Escape = "Escape",
9
+ Tab = "Tab",
10
+ ShiftLeft = "ShiftLeft",
11
+ ShiftRight = "ShiftRight",
12
+ ControlLeft = "ControlLeft",
13
+ ControlRight = "ControlRight",
14
+ AltLeft = "AltLeft",
15
+ AltRight = "AltRight",
16
+ KeyA = "KeyA",
17
+ KeyB = "KeyB",
18
+ KeyC = "KeyC",
19
+ KeyD = "KeyD",
20
+ KeyE = "KeyE",
21
+ KeyF = "KeyF",
22
+ KeyG = "KeyG",
23
+ KeyH = "KeyH",
24
+ KeyI = "KeyI",
25
+ KeyJ = "KeyJ",
26
+ KeyK = "KeyK",
27
+ KeyL = "KeyL",
28
+ KeyM = "KeyM",
29
+ KeyN = "KeyN",
30
+ KeyO = "KeyO",
31
+ KeyP = "KeyP",
32
+ KeyQ = "KeyQ",
33
+ KeyR = "KeyR",
34
+ KeyS = "KeyS",
35
+ KeyT = "KeyT",
36
+ KeyU = "KeyU",
37
+ KeyV = "KeyV",
38
+ KeyW = "KeyW",
39
+ KeyX = "KeyX",
40
+ KeyY = "KeyY",
41
+ KeyZ = "KeyZ",
42
+ Digit0 = "Digit0",
43
+ Digit1 = "Digit1",
44
+ Digit2 = "Digit2",
45
+ Digit3 = "Digit3",
46
+ Digit4 = "Digit4",
47
+ Digit5 = "Digit5",
48
+ Digit6 = "Digit6",
49
+ Digit7 = "Digit7",
50
+ Digit8 = "Digit8",
51
+ Digit9 = "Digit9"
52
+ }
package/dist/keys.js ADDED
@@ -0,0 +1,55 @@
1
+ // keys.ts
2
+ // to view the Keys easily with the Autocomplete Feature
3
+ export var Key;
4
+ (function (Key) {
5
+ Key["ArrowUp"] = "ArrowUp";
6
+ Key["ArrowDown"] = "ArrowDown";
7
+ Key["ArrowLeft"] = "ArrowLeft";
8
+ Key["ArrowRight"] = "ArrowRight";
9
+ Key["Space"] = "Space";
10
+ Key["Enter"] = "Enter";
11
+ Key["Escape"] = "Escape";
12
+ Key["Tab"] = "Tab";
13
+ Key["ShiftLeft"] = "ShiftLeft";
14
+ Key["ShiftRight"] = "ShiftRight";
15
+ Key["ControlLeft"] = "ControlLeft";
16
+ Key["ControlRight"] = "ControlRight";
17
+ Key["AltLeft"] = "AltLeft";
18
+ Key["AltRight"] = "AltRight";
19
+ Key["KeyA"] = "KeyA";
20
+ Key["KeyB"] = "KeyB";
21
+ Key["KeyC"] = "KeyC";
22
+ Key["KeyD"] = "KeyD";
23
+ Key["KeyE"] = "KeyE";
24
+ Key["KeyF"] = "KeyF";
25
+ Key["KeyG"] = "KeyG";
26
+ Key["KeyH"] = "KeyH";
27
+ Key["KeyI"] = "KeyI";
28
+ Key["KeyJ"] = "KeyJ";
29
+ Key["KeyK"] = "KeyK";
30
+ Key["KeyL"] = "KeyL";
31
+ Key["KeyM"] = "KeyM";
32
+ Key["KeyN"] = "KeyN";
33
+ Key["KeyO"] = "KeyO";
34
+ Key["KeyP"] = "KeyP";
35
+ Key["KeyQ"] = "KeyQ";
36
+ Key["KeyR"] = "KeyR";
37
+ Key["KeyS"] = "KeyS";
38
+ Key["KeyT"] = "KeyT";
39
+ Key["KeyU"] = "KeyU";
40
+ Key["KeyV"] = "KeyV";
41
+ Key["KeyW"] = "KeyW";
42
+ Key["KeyX"] = "KeyX";
43
+ Key["KeyY"] = "KeyY";
44
+ Key["KeyZ"] = "KeyZ";
45
+ Key["Digit0"] = "Digit0";
46
+ Key["Digit1"] = "Digit1";
47
+ Key["Digit2"] = "Digit2";
48
+ Key["Digit3"] = "Digit3";
49
+ Key["Digit4"] = "Digit4";
50
+ Key["Digit5"] = "Digit5";
51
+ Key["Digit6"] = "Digit6";
52
+ Key["Digit7"] = "Digit7";
53
+ Key["Digit8"] = "Digit8";
54
+ Key["Digit9"] = "Digit9";
55
+ })(Key || (Key = {}));
@@ -0,0 +1 @@
1
+ export declare const dlog: (...args: any[]) => void;
package/dist/logger.js ADDED
@@ -0,0 +1,16 @@
1
+ // Debug Log Function
2
+ export const dlog =
3
+ // (import.meta.env?.DEV ?? true) // Default true, falls undefined
4
+ // ? (...args: any[]) => {
5
+ // const now = new Date();
6
+ // const time =
7
+ // `[${now.getHours().toString().padStart(2, "0")}:` +
8
+ // `${now.getMinutes().toString().padStart(2, "0")}:` +
9
+ // `${now.getSeconds().toString().padStart(2, "0")}.` +
10
+ // `${now.getMilliseconds().toString().padStart(3, "0")}]`;
11
+ // console.log(time, ...args);
12
+ // }
13
+ // :
14
+ (...args) => { }; // Release: nichts loggen
15
+ // Dlog in Release Mode:
16
+ // export const dlog = () => {};
@@ -0,0 +1,5 @@
1
+ import { type Rect } from "./types/rectangle";
2
+ export declare function renderText(ctx: CanvasRenderingContext2D, text: string, x: number, y: number, color?: string, font?: string): void;
3
+ type CharMap = Record<string, Rect>;
4
+ export declare function renderBitmapText(ctx: CanvasRenderingContext2D, text: string, x: number, y: number, sprite: HTMLImageElement, charMap: CharMap, scale?: number): void;
5
+ export {};
@@ -0,0 +1,17 @@
1
+ // Function to render Text
2
+ export function renderText(ctx, text, x, y, color = "white", font = "20px Arial") {
3
+ ctx.fillStyle = color;
4
+ ctx.font = font;
5
+ ctx.textBaseline = "top";
6
+ ctx.fillText(text, x, y);
7
+ }
8
+ export function renderBitmapText(ctx, text, x, y, sprite, charMap, scale = 1) {
9
+ let offsetX = 0;
10
+ for (const c of text) {
11
+ const rect = charMap[c];
12
+ if (!rect)
13
+ continue;
14
+ ctx.drawImage(sprite, rect.x, rect.y, rect.width, rect.height, x + offsetX, y, rect.width * scale, rect.height * scale);
15
+ offsetX += rect.width * scale;
16
+ }
17
+ }
package/dist/save.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ type SaveData = Record<string, any>;
2
+ export declare function saveGame(data: SaveData): void;
3
+ export declare function loadGame(): SaveData | null;
4
+ export declare function clearSave(): void;
5
+ export {};
package/dist/save.js ADDED
@@ -0,0 +1,27 @@
1
+ const SAVE_KEY = "my_game_save";
2
+ export function saveGame(data) {
3
+ try {
4
+ const json = JSON.stringify(data);
5
+ localStorage.setItem(SAVE_KEY, json);
6
+ console.log("Game saved!");
7
+ }
8
+ catch (err) {
9
+ console.error("Save failed:", err);
10
+ }
11
+ }
12
+ export function loadGame() {
13
+ try {
14
+ const json = localStorage.getItem(SAVE_KEY);
15
+ if (!json)
16
+ return null;
17
+ return JSON.parse(json);
18
+ }
19
+ catch (err) {
20
+ console.error("Load failed:", err);
21
+ return null;
22
+ }
23
+ }
24
+ export function clearSave() {
25
+ localStorage.removeItem(SAVE_KEY);
26
+ console.log("Save cleared!");
27
+ }
@@ -0,0 +1,4 @@
1
+ export type Texture = HTMLImageElement | undefined;
2
+ export declare function loadTextureAsync(src: string): Promise<HTMLImageElement>;
3
+ export declare function getTexture(src: string): Texture;
4
+ export declare function drawTexture(ctx: CanvasRenderingContext2D, texture: Texture, x: number, y: number, width?: number, height?: number): void;
@@ -0,0 +1,46 @@
1
+ import { dlog } from "./logger";
2
+ const textures = {};
3
+ function getresourcepath(path) {
4
+ return "resources/" + path;
5
+ }
6
+ // Function to load a Texture Async
7
+ export function loadTextureAsync(src) {
8
+ return new Promise((resolve, reject) => {
9
+ // Wenn schon geladen
10
+ const existing = getTexture(src);
11
+ if (existing)
12
+ return resolve(existing);
13
+ const img = new Image();
14
+ // 🔹 Hier Pfad modifizieren, z.B. Prefix hinzufügen
15
+ let finalSrc = getresourcepath(src);
16
+ img.onload = () => {
17
+ // Cache speichern
18
+ textures[finalSrc] = img; // optional: Original-Pfad oder finalSrc?
19
+ resolve(img);
20
+ };
21
+ img.onerror = () => {
22
+ const msg = `❌ Failed to load texture: ${finalSrc}`;
23
+ console.error(msg);
24
+ reject(new Error(msg));
25
+ };
26
+ // 🔹 Bild laden mit finalem Pfad
27
+ img.src = finalSrc;
28
+ });
29
+ }
30
+ export function getTexture(src) {
31
+ return textures[getresourcepath(src)];
32
+ }
33
+ export function drawTexture(ctx, texture, x, y, width, height) {
34
+ if (!texture) {
35
+ dlog("Texture not found");
36
+ ctx.fillStyle = "magenta";
37
+ ctx.fillRect(x, y, width ?? 32, height ?? 32);
38
+ return;
39
+ }
40
+ if (width && height) {
41
+ ctx.drawImage(texture, x, y, width, height);
42
+ }
43
+ else {
44
+ ctx.drawImage(texture, x, y);
45
+ }
46
+ }
@@ -0,0 +1,8 @@
1
+ export type Color = {
2
+ r: number;
3
+ g: number;
4
+ b: number;
5
+ a?: number;
6
+ };
7
+ export declare function invertcolor(color: Color): Color;
8
+ export declare function invertHexColor(hex: string): string;
@@ -0,0 +1,18 @@
1
+ export function invertcolor(color) {
2
+ return {
3
+ r: 255 - color.r,
4
+ g: 255 - color.g,
5
+ b: 255 - color.b,
6
+ ...(color.a !== undefined ? { a: color.a } : {}) // optional alpha behalten
7
+ };
8
+ }
9
+ export function invertHexColor(hex) {
10
+ // Entferne das führende #
11
+ const cleanHex = hex.replace('#', '');
12
+ // Wandeln in R, G, B
13
+ const r = 255 - parseInt(cleanHex.slice(0, 2), 16);
14
+ const g = 255 - parseInt(cleanHex.slice(2, 4), 16);
15
+ const b = 255 - parseInt(cleanHex.slice(4, 6), 16);
16
+ // Zurück in Hex-String
17
+ return '#' + [r, g, b].map(x => x.toString(16).padStart(2, '0')).join('');
18
+ }
@@ -0,0 +1,5 @@
1
+ export type { Vector2d } from "./vector2d.js";
2
+ export type { Vector3d } from "./vector3d.js";
3
+ export type { Color } from "./color.js";
4
+ export type { Rect } from "./rectangle.js";
5
+ export { isRectClicked } from "./rectangle.js";
@@ -0,0 +1,4 @@
1
+ // Types Export
2
+ export { isRectClicked } from "./rectangle.js";
3
+ // Math Utilities
4
+ // export * from "./math-utils.js";
@@ -0,0 +1,3 @@
1
+ export declare function clamp(input: number, min: number, max: number): number;
2
+ export declare function lerp(start: number, end: number, t: number): number;
3
+ export declare function map(value: number, inMin: number, inMax: number, outMin: number, outMax: number): number;
@@ -0,0 +1,12 @@
1
+ // Clamp
2
+ export function clamp(input, min, max) {
3
+ return Math.min(Math.max(input, min), max);
4
+ }
5
+ // Lerp
6
+ export function lerp(start, end, t) {
7
+ return (start + (end - start) * t);
8
+ }
9
+ // Map
10
+ export function map(value, inMin, inMax, outMin, outMax) {
11
+ return (outMax - outMin) * ((value - inMin) / (inMax - inMin)) + outMin;
12
+ }
@@ -0,0 +1,14 @@
1
+ import { Mouse } from "../input";
2
+ import { type Vector2d } from "./vector2d";
3
+ export type Rect = {
4
+ x: number;
5
+ y: number;
6
+ width: number;
7
+ height: number;
8
+ };
9
+ export declare function centerRectX(rect: Rect): number;
10
+ export declare function centerRectY(rect: Rect): number;
11
+ export declare function centerRect(rect: Rect): Vector2d;
12
+ export declare function isPointInRect(x: number, y: number, rect: Rect): boolean;
13
+ export declare function isMouseInRect(mouse: Mouse, rect: Rect): boolean;
14
+ export declare function isRectClicked(mouse: Mouse, rect: Rect): boolean;
@@ -0,0 +1,31 @@
1
+ // Rectangle Type for Hitboxes
2
+ // Function to get the Center of the Width of the Object
3
+ export function centerRectX(rect) {
4
+ return (rect.x + (rect.width / 2));
5
+ }
6
+ // Function to get the Center of the Height of the Object
7
+ export function centerRectY(rect) {
8
+ return (rect.y + (rect.height / 2));
9
+ }
10
+ // Get the Center of a Rectangle
11
+ export function centerRect(rect) {
12
+ let vector = { x: centerRectX(rect), y: centerRectY(rect) };
13
+ return vector;
14
+ }
15
+ // Check if a Point in the Rectangle
16
+ export function isPointInRect(x, y, rect) {
17
+ return (x >= rect.x &&
18
+ x <= rect.x + rect.width &&
19
+ y >= rect.y &&
20
+ y <= rect.y + rect.height);
21
+ }
22
+ export function isMouseInRect(mouse, rect) {
23
+ return (mouse.x >= rect.x &&
24
+ mouse.x <= rect.x + rect.width &&
25
+ mouse.y >= rect.y &&
26
+ mouse.y <= rect.y + rect.height);
27
+ }
28
+ // Function to check if a Rectangle is clicked
29
+ export function isRectClicked(mouse, rect) {
30
+ return isMouseInRect(mouse, rect) && mouse.justPressed;
31
+ }
@@ -0,0 +1,7 @@
1
+ export type Vector2d = {
2
+ x: number;
3
+ y: number;
4
+ };
5
+ export declare function normalize2d(vector: Vector2d): Vector2d;
6
+ export declare function clamp2d(vector: Vector2d, min: Vector2d, max: Vector2d): Vector2d;
7
+ export declare function lerp2d(start: Vector2d, end: Vector2d, t: Vector2d): Vector2d;
@@ -0,0 +1,29 @@
1
+ // 2 Dimensional Vector Type
2
+ import { clamp, lerp } from "./math-utils";
3
+ // Function to normalize a Vector 2d
4
+ export function normalize2d(vector) {
5
+ // Check if the Vector is zero because then you dont need to
6
+ // calculate sth
7
+ if (vector.x == 0 && vector.y == 0) {
8
+ return vector;
9
+ }
10
+ let produkt = vector.x * vector.x + vector.y * vector.y;
11
+ let root = Math.sqrt(produkt);
12
+ vector.x = vector.x / root;
13
+ vector.y = vector.y / root;
14
+ return vector;
15
+ }
16
+ // Function to clamp a Vector 2d
17
+ export function clamp2d(vector, min, max) {
18
+ return {
19
+ x: clamp(vector.x, min.x, max.x),
20
+ y: clamp(vector.y, min.y, max.y),
21
+ };
22
+ }
23
+ // Lerp for a 2d Vector
24
+ export function lerp2d(start, end, t) {
25
+ return {
26
+ x: lerp(start.x, end.x, t.x),
27
+ y: lerp(start.y, end.y, t.y),
28
+ };
29
+ }
@@ -0,0 +1,8 @@
1
+ export type Vector3d = {
2
+ x: number;
3
+ y: number;
4
+ z: number;
5
+ };
6
+ export declare function normalize3d(vector: Vector3d): Vector3d;
7
+ export declare function clamp3d(vector: Vector3d, min: Vector3d, max: Vector3d): Vector3d;
8
+ export declare function lerp3d(start: Vector3d, end: Vector3d, t: Vector3d): Vector3d;
@@ -0,0 +1,32 @@
1
+ // 3d Vector
2
+ import { clamp, lerp } from "./math-utils";
3
+ // Function to normalize a Vector 2d
4
+ export function normalize3d(vector) {
5
+ // Check if the Vector is zero because then you dont need to
6
+ // calculate sth
7
+ if (vector.x == 0 && vector.y == 0 && vector.z) {
8
+ return vector;
9
+ }
10
+ let produkt = vector.x * vector.x + vector.y * vector.y + vector.z * vector.z;
11
+ let root = Math.sqrt(produkt);
12
+ vector.x = vector.x / root;
13
+ vector.y = vector.y / root;
14
+ vector.z = vector.z / root;
15
+ return vector;
16
+ }
17
+ // Function to clamp a Vector 2d
18
+ export function clamp3d(vector, min, max) {
19
+ return {
20
+ x: clamp(vector.x, min.x, max.x),
21
+ y: clamp(vector.y, min.y, max.y),
22
+ z: clamp(vector.y, min.y, max.y),
23
+ };
24
+ }
25
+ // Lerp for a 2d Vector
26
+ export function lerp3d(start, end, t) {
27
+ return {
28
+ x: lerp(start.x, end.x, t.x),
29
+ y: lerp(start.y, end.y, t.y),
30
+ z: lerp(start.y, end.y, t.y),
31
+ };
32
+ }
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@shadowdara/webgameengine",
3
+ "version": "1.0.0",
4
+ "description": "A TypeScript game library to make HTML Games",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "sideEffects": false,
8
+ "files": [
9
+ "dist"
10
+ ],
11
+ "scripts": {
12
+ "build": "tsc",
13
+ "pack": "npm run build && npm pack"
14
+ },
15
+ "bin": {
16
+ "webgameengine": "dist/build/tools/devserver/cli.js"
17
+ },
18
+ "exports": {
19
+ ".": "./dist/index.js",
20
+ "./build": "./dist/build/index.js",
21
+ "./types": "./dist/types/index.js"
22
+ },
23
+ "keywords": [
24
+ "game",
25
+ "typescript"
26
+ ],
27
+ "author": "Shadowdara",
28
+ "license": "MIT",
29
+ "devDependencies": {
30
+ "@types/node": "^25.5.2",
31
+ "@types/ws": "^8.18.1",
32
+ "typescript": "^6.0.2"
33
+ },
34
+ "dependencies": {
35
+ "esbuild": "^0.28.0",
36
+ "esbuild-register": "^3.6.0",
37
+ "mime": "^4.1.0",
38
+ "ws": "^8.20.0"
39
+ }
40
+ }