@shadowdara/webgameengine 1.1.0 → 1.3.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.
package/README.md CHANGED
@@ -51,8 +51,10 @@ startEngine(init, gameLoop);
51
51
  ### Using Bun (local development)
52
52
 
53
53
  ```bash
54
- npx webgameengine # Build main game
54
+ npx webgameengine # Start Dev Server
55
55
  npx webgameengine --release # Production build
56
+ npx webgameengine --new # Create a new project with a simple Snake Clone as Template
57
+ npx webgameengine --new-empty # Create a new empty project
56
58
  ```
57
59
 
58
60
  ### Configuration
@@ -0,0 +1,166 @@
1
+ #!/usr/bin/env node
2
+ import { build as esbuild } from "esbuild";
3
+ import { createServer } from "http";
4
+ import { readFile, writeFile, mkdir } from "fs/promises";
5
+ import { watch } from "fs";
6
+ import path from "path";
7
+ import { WebSocketServer } from "ws";
8
+ import { createProject } from "./new.js";
9
+ import { copyFolder, flog, getContentType } from "../buildhelper.js";
10
+ import { GetDefaultHTML } from "../exporthtml.js";
11
+ import { loadUserConfig } from "./config.js";
12
+ // ================= ARG PARSING =================
13
+ function parseArgs() {
14
+ const args = process.argv.slice(2);
15
+ const options = {
16
+ release: false,
17
+ port: 3000,
18
+ newProject: null,
19
+ empty: false,
20
+ };
21
+ for (let i = 0; i < args.length; i++) {
22
+ const arg = args[i];
23
+ switch (arg) {
24
+ case "--release":
25
+ case "-r":
26
+ options.release = true;
27
+ break;
28
+ case "--port":
29
+ case "-p":
30
+ options.port = Number(args[i + 1]);
31
+ i++;
32
+ break;
33
+ case "--new":
34
+ case "-n":
35
+ options.newProject = args[i + 1];
36
+ i++;
37
+ break;
38
+ case "--new-empty":
39
+ options.empty = true;
40
+ break;
41
+ case "-h":
42
+ case "--help":
43
+ // TODO
44
+ // make the help Message here
45
+ console.log("Cli Tools for Webgameengine");
46
+ default:
47
+ console.warn(`⚠️ Unknown Argument: ${arg}`);
48
+ }
49
+ }
50
+ return options;
51
+ }
52
+ // ================= APP FACTORY =================
53
+ function createCLIApp(args, config) {
54
+ const isRelease = args.release;
55
+ const isDev = !isRelease;
56
+ // 👉 State kapseln statt global
57
+ let isBuilding = false;
58
+ let restarting = false;
59
+ let pendingRestart = false;
60
+ const sockets = new Set();
61
+ // ================= BUILD =================
62
+ async function build() {
63
+ if (isBuilding)
64
+ return;
65
+ isBuilding = true;
66
+ flog("🔄 Building new ...");
67
+ await esbuild({
68
+ entryPoints: [`./game/${config.entryname}`],
69
+ outdir: `./${config.outdir}`,
70
+ bundle: true,
71
+ platform: "browser",
72
+ minify: isRelease,
73
+ sourcemap: isDev,
74
+ define: {
75
+ "import.meta.env.DEV": JSON.stringify(isDev),
76
+ },
77
+ });
78
+ const html = GetDefaultHTML(config);
79
+ await writeFile("./dist/index.html", html);
80
+ await copyFolder("./resources", "./dist/resources");
81
+ flog("✅ Build finished!");
82
+ isBuilding = false;
83
+ }
84
+ // ================= SERVER =================
85
+ function startServer() {
86
+ const server = createServer(async (req, res) => {
87
+ const url = req.url || "/";
88
+ const filePath = url === "/" ? "/index.html" : url;
89
+ try {
90
+ const fullPath = path.join(process.cwd(), "dist", filePath);
91
+ const file = await readFile(fullPath);
92
+ res.writeHead(200, {
93
+ "Content-Type": getContentType(filePath),
94
+ });
95
+ res.end(file);
96
+ }
97
+ catch {
98
+ res.writeHead(404);
99
+ res.end("Not Found");
100
+ }
101
+ });
102
+ const wss = new WebSocketServer({ server });
103
+ wss.on("connection", (ws) => {
104
+ sockets.add(ws);
105
+ ws.on("close", () => sockets.delete(ws));
106
+ });
107
+ server.listen(args.port, () => {
108
+ flog(`🚀 Dev Server is running on http://localhost:${args.port}`);
109
+ });
110
+ return server;
111
+ }
112
+ // ================= RELOAD =================
113
+ function reloadClients() {
114
+ flog("🔄 Browser reload...");
115
+ for (const ws of sockets) {
116
+ ws.send("reload");
117
+ }
118
+ }
119
+ // ================= RESTART =================
120
+ async function restart() {
121
+ if (restarting) {
122
+ pendingRestart = true;
123
+ return;
124
+ }
125
+ restarting = true;
126
+ do {
127
+ pendingRestart = false;
128
+ flog("♻️ Restart...");
129
+ await build();
130
+ reloadClients();
131
+ } while (pendingRestart);
132
+ restarting = false;
133
+ }
134
+ // ================= WATCH =================
135
+ function startWatcher() {
136
+ mkdir("resources", { recursive: true });
137
+ mkdir("game", { recursive: true });
138
+ ["resources", "game"].forEach((dir) => {
139
+ watch(dir, { recursive: true }, async () => {
140
+ flog(`📁 Change noticed in ${dir}`);
141
+ await restart();
142
+ });
143
+ });
144
+ flog("👀 Watcher active ...");
145
+ }
146
+ // ================= RUN =================
147
+ async function run() {
148
+ await build();
149
+ if (isDev) {
150
+ startServer();
151
+ startWatcher();
152
+ }
153
+ flog(`Build finished! Mode: ${isRelease ? "Release" : "Dev"}`);
154
+ }
155
+ return { run };
156
+ }
157
+ // ================= MAIN =================
158
+ const args = parseArgs();
159
+ // Create a bew Project
160
+ if (args.newProject) {
161
+ await createProject(args.newProject, args.empty);
162
+ process.exit(0);
163
+ }
164
+ const config = await loadUserConfig();
165
+ const app = createCLIApp(args, config);
166
+ await app.run();
@@ -1,3 +1,3 @@
1
1
  import "esbuild-register";
2
- import { buildconfig } from "../../buildconfig.js";
2
+ import { buildconfig } from "../buildconfig.js";
3
3
  export declare function loadUserConfig(): Promise<buildconfig>;
@@ -13,6 +13,6 @@ export async function loadUserConfig() {
13
13
  }
14
14
  catch (e) {
15
15
  console.error(e);
16
- throw new Error("❌ Konnte webgameengine.config.ts nicht laden\nPATH: " + configPath);
16
+ throw new Error("❌ Could not find 'webgameengine.config.ts - PATH: " + configPath);
17
17
  }
18
18
  }
@@ -0,0 +1 @@
1
+ export declare function createProject(name: string, empty: boolean): Promise<void>;
@@ -0,0 +1,192 @@
1
+ import path from "path";
2
+ import { writeFile, mkdir } from "fs/promises";
3
+ import { flog } from "../buildhelper.js";
4
+ // ================= NEW PROJECT =================
5
+ export async function createProject(name, empty) {
6
+ const base = path.resolve(name);
7
+ flog(`📦 Erstelle neues Projekt: ${name}`);
8
+ await mkdir(base, { recursive: true });
9
+ await mkdir(path.join(base, "game"), { recursive: true });
10
+ await mkdir(path.join(base, "resources"), { recursive: true });
11
+ await mkdir(path.join(base, "dist"), { recursive: true });
12
+ let content = `
13
+ // A empty Project with the Web Framework
14
+
15
+ import { createCanvas, enableFullscreen, setupFullscreenButton } from "@shadowdara/webgameengine";
16
+ import { setupInput, resetInput, getMouse } from "@shadowdara/webgameengine";
17
+ import { startEngine } from "@shadowdara/webgameengine";
18
+
19
+ const { canvas, ctx, applyScaling } = createCanvas({fullscreen: true, scaling: "fit", virtualWidth: window.innerWidth, virtualHeight: window.innerHeight});
20
+ setupInput(canvas);
21
+
22
+ enableFullscreen(canvas);
23
+ setupFullscreenButton(canvas);
24
+
25
+ async function gameStart() {
26
+ // Code which runs at the Game Start
27
+ }
28
+
29
+ function gameLoop(dt: number) {
30
+ // Code which runs every Frame
31
+
32
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
33
+
34
+ const mouse = getMouse();
35
+
36
+ applyScaling();
37
+
38
+ resetInput();
39
+ }
40
+
41
+ // Because start Game is Async
42
+ startEngine(() => { gameStart().then(() => {/* ready */}) }, gameLoop);
43
+ `;
44
+ if (empty) {
45
+ content = `
46
+ // A mini Snake Clone with my Webframework
47
+
48
+ import { createCanvas } from "@shadowdara/webgameengine";
49
+ import { setupInput, isKeyJustPressed, resetInput } from "@shadowdara/webgameengine";
50
+ import { startEngine } from "@shadowdara/webgameengine";
51
+ import { renderText } from "@shadowdara/webgameengine";
52
+ import { Vector2d } from "@shadowdara/webgameengine/types";
53
+ import { dlog } from "@shadowdara/webgameengine";
54
+ import { Key } from "@shadowdara/webgameengine";
55
+
56
+ const { canvas, ctx } = createCanvas({fullscreen: true, scaling: "fit", virtualWidth: window.innerWidth, virtualHeight: window.innerHeight});
57
+ setupInput(canvas);
58
+
59
+ let snake: Vector2d[] = [{ x: 10, y: 10 }];
60
+ let dir: Vector2d = { x: 1, y: 0 };
61
+ let food: Vector2d = { x: 15, y: 10 };
62
+ let gridSize = 20;
63
+ let lastMove = 0;
64
+ let speed = 0.2; // seconds per cell
65
+ let start = false;
66
+
67
+ async function gameStart() {
68
+ dlog("Snake gestartet");
69
+ }
70
+
71
+ function gameLoop(dt: number) {
72
+ if (isKeyJustPressed(Key.Escape)) {
73
+ start = !start
74
+ }
75
+
76
+ if (!start) {
77
+ ctx.fillStyle = "white";
78
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
79
+
80
+ renderText(ctx, "Snake", 10, 10, "black", "24px Arial");
81
+ renderText(ctx, "Press ESC to start or Pause the Game!", 10, 50, "black", "24px Arial");
82
+
83
+ // return;
84
+ } else {
85
+
86
+ // Input
87
+ if (isKeyJustPressed(Key.ArrowUp) && dir.y === 0) dir = { x: 0, y: -1 };
88
+ if (isKeyJustPressed(Key.ArrowDown) && dir.y === 0) dir = { x: 0, y: 1 };
89
+ if (isKeyJustPressed(Key.ArrowLeft) && dir.x === 0) dir = { x: -1, y: 0 };
90
+ if (isKeyJustPressed(Key.ArrowRight) && dir.x === 0) dir = { x: 1, y: 0 };
91
+
92
+ lastMove += dt;
93
+
94
+ if (lastMove >= speed) {
95
+ lastMove = 0;
96
+ const head = { x: snake[0].x + dir.x, y: snake[0].y + dir.y };
97
+
98
+ // Kollision mit Walls
99
+ if (head.x < 0 || head.y < 0 || head.x >= canvas.width / gridSize || head.y >= canvas.height / gridSize) {
100
+ snake = [{ x: 10, y: 10 }];
101
+ dir = { x: 1, y: 0 };
102
+ dlog("Game Over");
103
+ return;
104
+ }
105
+
106
+ // Kollision mit sich selbst
107
+ if (snake.some(s => s.x === head.x && s.y === head.y)) {
108
+ snake = [{ x: 10, y: 10 }];
109
+ dir = { x: 1, y: 0 };
110
+ dlog(\`Game Over! Highscore: \${snake.length - 1}\`);
111
+ return;
112
+ }
113
+
114
+ snake.unshift(head);
115
+
116
+ // Food Check
117
+ if (head.x === food.x && head.y === food.y) {
118
+ food = { x: Math.floor(Math.random() * (canvas.width / gridSize)), y: Math.floor(Math.random() * (canvas.height / gridSize)) };
119
+ } else {
120
+ snake.pop();
121
+ }
122
+ }
123
+
124
+ // Zeichnen
125
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
126
+
127
+ ctx.fillStyle = "black";
128
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
129
+
130
+ ctx.fillStyle = "green";
131
+ snake.forEach(s => ctx.fillRect(s.x * gridSize, s.y * gridSize, gridSize, gridSize));
132
+
133
+ ctx.fillStyle = "red";
134
+ ctx.fillRect(food.x * gridSize, food.y * gridSize, gridSize, gridSize);
135
+
136
+ renderText(ctx, "Score: " + (snake.length - 1), 10, 10, "yellow", "24px Arial");
137
+
138
+ }
139
+
140
+ // Reset Input
141
+ resetInput();
142
+ }
143
+
144
+ // Because start Game is Async
145
+ startEngine(() => { gameStart().then(() => {/* ready */}) }, gameLoop);
146
+ `;
147
+ }
148
+ // Basic game entry
149
+ await writeFile(path.join(base, "game", "main.ts"), content);
150
+ // Config
151
+ await writeFile(path.join(base, "webgameengine.config.js"), `
152
+ // Project File for the Game
153
+
154
+ import type { buildconfig } from "@shadowdara/webgameengine/build";
155
+ import { new_buildconfig } from "@shadowdara/webgameengine/build";
156
+
157
+ export default function defineConfig(): buildconfig {
158
+ let config: buildconfig = new_buildconfig();
159
+ return config;
160
+ }
161
+ `);
162
+ // package.json
163
+ // await writeFile(
164
+ // path.join(base, "package.json"),
165
+ // JSON.stringify(
166
+ // {
167
+ // name,
168
+ // version: "1.0.0",
169
+ // type: "module",
170
+ // scripts: {
171
+ // dev: "mycli",
172
+ // build: "mycli --release",
173
+ // },
174
+ // },
175
+ // null,
176
+ // 2
177
+ // )
178
+ // );
179
+ flog("✅ Projekt created!");
180
+ // flog(`👉 cd ${name}`);
181
+ // flog(`👉 npm install`);
182
+ // flog(`👉 npm run dev`);
183
+ console.log(`Add to your package.json file:
184
+ "scripts": {
185
+ "dev": "npx webgameengine",
186
+ "build": "npx webgameengine --release"
187
+ },
188
+
189
+ Run "npm run dev" to start the Dev Server and play a little Snake Clone
190
+ `);
191
+ process.exit(0);
192
+ }
package/dist/index.d.ts CHANGED
@@ -1,9 +1,10 @@
1
1
  export { startEngine } from "./core.js";
2
- export { renderText, renderBitmapText } from "./renderer.js";
3
- export { setupInput, isKeyJustPressed, resetInput, getMouse } from "./input.js";
2
+ export { renderText, renderBitmapText, drawRect, drawRectOutline, drawCircle, drawCircleOutline, drawTriangle, drawTriangleOutline, } from "./renderer.js";
4
3
  export type { Mouse } from "./input.js";
4
+ export { setupInput, isKeyJustPressed, resetInput, getMouse } from "./input.js";
5
5
  export { dlog } from "./logger.js";
6
6
  export { saveGame, loadGame, clearSave } from "./save.js";
7
- export { drawTexture, getTexture, type Texture, loadTextureAsync } from "./texture.js";
7
+ export type { Texture, TextureAtlas, Animation, } from "./texture.js";
8
+ export { loadTextureAsync, getTexture, drawTexture, loadAtlas, drawAtlasFrame, AnimationPlayer, drawAnimation, getFlipFromDirection, } from "./texture.js";
8
9
  export { createCanvas, enableFullscreen, setupFullscreenButton } from "./html.js";
9
10
  export { Key } from "./keys.js";
package/dist/index.js CHANGED
@@ -1,15 +1,13 @@
1
1
  // Core Engine Exports
2
2
  export { startEngine } from "./core.js";
3
3
  // Rendering
4
- export { renderText, renderBitmapText } from "./renderer.js";
5
- // Input System
4
+ export { renderText, renderBitmapText, drawRect, drawRectOutline, drawCircle, drawCircleOutline, drawTriangle, drawTriangleOutline, } from "./renderer.js";
6
5
  export { setupInput, isKeyJustPressed, resetInput, getMouse } from "./input.js";
7
6
  // Logging
8
7
  export { dlog } from "./logger.js";
9
8
  // Save System
10
9
  export { saveGame, loadGame, clearSave } from "./save.js";
11
- // Texture Management
12
- export { drawTexture, getTexture, loadTextureAsync } from "./texture.js";
10
+ export { loadTextureAsync, getTexture, drawTexture, loadAtlas, drawAtlasFrame, AnimationPlayer, drawAnimation, getFlipFromDirection, } from "./texture.js";
13
11
  // HTML Generation
14
12
  export { createCanvas, enableFullscreen, setupFullscreenButton } from "./html.js";
15
13
  // Keys Reference
package/dist/input.d.ts CHANGED
@@ -5,6 +5,8 @@ export type Mouse = {
5
5
  justPressed: boolean;
6
6
  justReleased: boolean;
7
7
  rightPressed: boolean;
8
+ rightjustPressed: boolean;
9
+ rightjustReleased: boolean;
8
10
  wheelDelta: number;
9
11
  };
10
12
  export declare function setupInput(canvas: HTMLCanvasElement, vWidth?: number, vHeight?: number): void;
package/dist/input.js CHANGED
@@ -9,6 +9,8 @@ const mouse = {
9
9
  // TODO
10
10
  // do the same for the right Buttons
11
11
  rightPressed: false,
12
+ rightjustPressed: false,
13
+ rightjustReleased: false,
12
14
  wheelDelta: 0,
13
15
  };
14
16
  let canvasRef;
@@ -45,6 +47,8 @@ export function setupInput(canvas, vWidth = 800, vHeight = 800) {
45
47
  mouse.pressed = true;
46
48
  }
47
49
  if (e.button === 2) {
50
+ if (!mouse.rightPressed)
51
+ mouse.rightjustPressed = true;
48
52
  mouse.rightPressed = true;
49
53
  }
50
54
  });
@@ -55,6 +59,7 @@ export function setupInput(canvas, vWidth = 800, vHeight = 800) {
55
59
  }
56
60
  if (e.button === 2) {
57
61
  mouse.rightPressed = false;
62
+ mouse.rightjustReleased = false;
58
63
  }
59
64
  });
60
65
  // 👉 wichtig: verhindert context menu bei right click
@@ -100,5 +105,7 @@ export function resetInput() {
100
105
  }
101
106
  mouse.justPressed = false;
102
107
  mouse.justReleased = false;
108
+ mouse.rightjustPressed = false;
109
+ mouse.rightjustReleased = false;
103
110
  mouse.wheelDelta = 0;
104
111
  }
@@ -1,5 +1,13 @@
1
1
  import { type Rect } from "./types/rectangle.js";
2
+ import { type Circle } from "./types/circle.js";
3
+ import { type Triangle } from "./types/triangle.js";
2
4
  export declare function renderText(ctx: CanvasRenderingContext2D, text: string, x: number, y: number, color?: string, font?: string): void;
3
5
  type CharMap = Record<string, Rect>;
4
6
  export declare function renderBitmapText(ctx: CanvasRenderingContext2D, text: string, x: number, y: number, sprite: HTMLImageElement, charMap: CharMap, scale?: number): void;
7
+ export declare function drawRect(ctx: CanvasRenderingContext2D, rect: Rect, color?: string): void;
8
+ export declare function drawRectOutline(ctx: CanvasRenderingContext2D, rect: Rect, color?: string, lineWidth?: number): void;
9
+ export declare function drawCircle(ctx: CanvasRenderingContext2D, circle: Circle, color?: string): void;
10
+ export declare function drawCircleOutline(ctx: CanvasRenderingContext2D, circle: Circle, color?: string, lineWidth?: number): void;
11
+ export declare function drawTriangle(ctx: CanvasRenderingContext2D, triangle: Triangle, color?: string): void;
12
+ export declare function drawTriangleOutline(ctx: CanvasRenderingContext2D, triangle: Triangle, color?: string, lineWidth?: number): void;
5
13
  export {};
package/dist/renderer.js CHANGED
@@ -15,3 +15,51 @@ export function renderBitmapText(ctx, text, x, y, sprite, charMap, scale = 1) {
15
15
  offsetX += rect.width * scale;
16
16
  }
17
17
  }
18
+ // ===== SHAPE DRAWING =====
19
+ // Function to draw a filled Rectangle
20
+ export function drawRect(ctx, rect, color = "white") {
21
+ ctx.fillStyle = color;
22
+ ctx.fillRect(rect.x, rect.y, rect.width, rect.height);
23
+ }
24
+ // Function to draw a Rectangle outline
25
+ export function drawRectOutline(ctx, rect, color = "white", lineWidth = 1) {
26
+ ctx.strokeStyle = color;
27
+ ctx.lineWidth = lineWidth;
28
+ ctx.strokeRect(rect.x, rect.y, rect.width, rect.height);
29
+ }
30
+ // Function to draw a filled Circle
31
+ export function drawCircle(ctx, circle, color = "white") {
32
+ ctx.fillStyle = color;
33
+ ctx.beginPath();
34
+ ctx.arc(circle.x, circle.y, circle.radius, 0, Math.PI * 2);
35
+ ctx.fill();
36
+ }
37
+ // Function to draw a Circle outline
38
+ export function drawCircleOutline(ctx, circle, color = "white", lineWidth = 1) {
39
+ ctx.strokeStyle = color;
40
+ ctx.lineWidth = lineWidth;
41
+ ctx.beginPath();
42
+ ctx.arc(circle.x, circle.y, circle.radius, 0, Math.PI * 2);
43
+ ctx.stroke();
44
+ }
45
+ // Function to draw a filled Triangle
46
+ export function drawTriangle(ctx, triangle, color = "white") {
47
+ ctx.fillStyle = color;
48
+ ctx.beginPath();
49
+ ctx.moveTo(triangle.x1, triangle.y1);
50
+ ctx.lineTo(triangle.x2, triangle.y2);
51
+ ctx.lineTo(triangle.x3, triangle.y3);
52
+ ctx.closePath();
53
+ ctx.fill();
54
+ }
55
+ // Function to draw a Triangle outline
56
+ export function drawTriangleOutline(ctx, triangle, color = "white", lineWidth = 1) {
57
+ ctx.strokeStyle = color;
58
+ ctx.lineWidth = lineWidth;
59
+ ctx.beginPath();
60
+ ctx.moveTo(triangle.x1, triangle.y1);
61
+ ctx.lineTo(triangle.x2, triangle.y2);
62
+ ctx.lineTo(triangle.x3, triangle.y3);
63
+ ctx.closePath();
64
+ ctx.stroke();
65
+ }
package/dist/texture.d.ts CHANGED
@@ -1,4 +1,29 @@
1
+ import { Rect } from "./types/rectangle.js";
1
2
  export type Texture = HTMLImageElement | undefined;
2
3
  export declare function loadTextureAsync(src: string): Promise<HTMLImageElement>;
3
4
  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;
5
+ export declare function drawTexture(ctx: CanvasRenderingContext2D, texture: Texture, x: number, y: number, width?: number, height?: number, rotation?: number, flipX?: boolean, flipY?: boolean): void;
6
+ export type TextureAtlas = {
7
+ image: HTMLImageElement;
8
+ frames: Record<string, Rect>;
9
+ };
10
+ export declare function loadAtlas(imageSrc: string, dataSrc: string): Promise<TextureAtlas>;
11
+ export declare function drawAtlasFrame(ctx: CanvasRenderingContext2D, atlas: TextureAtlas, frameName: string, x: number, y: number, width?: number, height?: number, rotation?: number, flipX?: boolean, flipY?: boolean): void;
12
+ export declare class AnimationPlayer {
13
+ animation: Animation;
14
+ private time;
15
+ private currentFrameIndex;
16
+ private finished;
17
+ constructor(animation: Animation);
18
+ update(deltaTime: number): void;
19
+ getCurrentFrame(): string;
20
+ reset(): void;
21
+ isFinished(): boolean;
22
+ }
23
+ export type Animation = {
24
+ frames: string[];
25
+ fps: number;
26
+ loop?: boolean;
27
+ };
28
+ export declare function drawAnimation(ctx: CanvasRenderingContext2D, atlas: TextureAtlas, player: AnimationPlayer, x: number, y: number, width?: number, height?: number, rotation?: number, flipX?: boolean, flipY?: boolean): void;
29
+ export declare function getFlipFromDirection(dir: number): boolean;
package/dist/texture.js CHANGED
@@ -27,20 +27,138 @@ export function loadTextureAsync(src) {
27
27
  img.src = finalSrc;
28
28
  });
29
29
  }
30
+ // Function to get the Texture
30
31
  export function getTexture(src) {
31
32
  return textures[getresourcepath(src)];
32
33
  }
33
- export function drawTexture(ctx, texture, x, y, width, height) {
34
+ // Function to draw a texture to the Screen
35
+ export function drawTexture(ctx, texture, x, y, width, height, rotation = 0, flipX = false, flipY = false) {
34
36
  if (!texture) {
35
37
  dlog("Texture not found");
36
38
  ctx.fillStyle = "magenta";
37
39
  ctx.fillRect(x, y, width ?? 32, height ?? 32);
38
40
  return;
39
41
  }
40
- if (width && height) {
41
- ctx.drawImage(texture, x, y, width, height);
42
+ const w = width ?? texture.width;
43
+ const h = height ?? texture.height;
44
+ ctx.save();
45
+ const cx = x + w / 2;
46
+ const cy = y + h / 2;
47
+ ctx.translate(cx, cy);
48
+ ctx.rotate(rotation);
49
+ // 🔹 Flip anwenden
50
+ ctx.scale(flipX ? -1 : 1, flipY ? -1 : 1);
51
+ ctx.drawImage(texture, -w / 2, -h / 2, w, h);
52
+ ctx.restore();
53
+ }
54
+ // Funtion to load an Atlas
55
+ //
56
+ // JSON Format for the LoadAtlas Function
57
+ //
58
+ // {
59
+ // "frames": {
60
+ // "player_idle_0": { "x": 0, "y": 0, "w": 32, "h": 32 },
61
+ // "player_idle_1": { "x": 32, "y": 0, "w": 32, "h": 32 }
62
+ // }
63
+ // }
64
+ //
65
+ export async function loadAtlas(imageSrc, dataSrc) {
66
+ const [image, data] = await Promise.all([
67
+ loadTextureAsync(imageSrc),
68
+ fetch(getresourcepath(dataSrc)).then(r => r.json())
69
+ ]);
70
+ return {
71
+ image,
72
+ frames: data.frames
73
+ };
74
+ }
75
+ export function drawAtlasFrame(ctx, atlas, frameName, x, y, width, height, rotation = 0, flipX = false, flipY = false) {
76
+ const frame = atlas.frames[frameName];
77
+ if (!frame) {
78
+ dlog(`Frame not found: ${frameName}`);
79
+ return;
80
+ }
81
+ const w = width ?? frame.width;
82
+ const h = height ?? frame.height;
83
+ ctx.save();
84
+ const cx = x + w / 2;
85
+ const cy = y + h / 2;
86
+ ctx.translate(cx, cy);
87
+ ctx.rotate(rotation);
88
+ // 🔹 Flip
89
+ ctx.scale(flipX ? -1 : 1, flipY ? -1 : 1);
90
+ ctx.drawImage(atlas.image, frame.x, frame.y, frame.width, frame.height, -w / 2, -h / 2, w, h);
91
+ ctx.restore();
92
+ }
93
+ //
94
+ //
95
+ // Animation Player
96
+ //
97
+ //
98
+ //
99
+ // // Define Animation
100
+ // const walkAnimation: Animation = {
101
+ // frames: ["walk_0", "walk_1", "walk_2", "walk_3"],
102
+ // fps: 8,
103
+ // loop: true
104
+ // };
105
+ //
106
+ // const player = new AnimationPlayer(walkAnimation);
107
+ //
108
+ // // Game Loop
109
+ // function update(dt: number) {
110
+ // player.update(dt);
111
+ // }
112
+ //
113
+ // function render(ctx: CanvasRenderingContext2D) {
114
+ // drawAnimation(ctx, atlas, player, 100, 100, 64, 64);
115
+ // }
116
+ //
117
+ //
118
+ // Animation Player Class
119
+ export class AnimationPlayer {
120
+ // Konstruktor Function
121
+ constructor(animation) {
122
+ this.animation = animation;
123
+ this.time = 0;
124
+ this.currentFrameIndex = 0;
125
+ this.finished = false;
42
126
  }
43
- else {
44
- ctx.drawImage(texture, x, y);
127
+ update(deltaTime) {
128
+ if (this.finished)
129
+ return;
130
+ this.time += deltaTime;
131
+ const frameDuration = 1 / this.animation.fps;
132
+ while (this.time >= frameDuration) {
133
+ this.time -= frameDuration;
134
+ this.currentFrameIndex++;
135
+ if (this.currentFrameIndex >= this.animation.frames.length) {
136
+ if (this.animation.loop) {
137
+ this.currentFrameIndex = 0;
138
+ }
139
+ else {
140
+ this.currentFrameIndex = this.animation.frames.length - 1;
141
+ this.finished = true;
142
+ }
143
+ }
144
+ }
45
145
  }
146
+ getCurrentFrame() {
147
+ return this.animation.frames[this.currentFrameIndex];
148
+ }
149
+ reset() {
150
+ this.time = 0;
151
+ this.currentFrameIndex = 0;
152
+ this.finished = false;
153
+ }
154
+ isFinished() {
155
+ return this.finished;
156
+ }
157
+ }
158
+ export function drawAnimation(ctx, atlas, player, x, y, width, height, rotation = 0, flipX = false, flipY = false) {
159
+ const frame = player.getCurrentFrame();
160
+ drawAtlasFrame(ctx, atlas, frame, x, y, width, height, rotation, flipX, flipY);
161
+ }
162
+ export function getFlipFromDirection(dir) {
163
+ return dir < 0; // links = true
46
164
  }
@@ -0,0 +1,13 @@
1
+ import { Mouse } from "../input.js";
2
+ import { type Vector2d } from "./vector2d.js";
3
+ export type Circle = {
4
+ x: number;
5
+ y: number;
6
+ radius: number;
7
+ };
8
+ export declare function centerCircle(circle: Circle): Vector2d;
9
+ export declare function isPointInCircle(x: number, y: number, circle: Circle): boolean;
10
+ export declare function isMouseInCircle(mouse: Mouse, circle: Circle): boolean;
11
+ export declare function isCircleClicked(mouse: Mouse, circle: Circle): boolean;
12
+ export declare function isCircleColliding(circle1: Circle, circle2: Circle): boolean;
13
+ export declare function getCircleDistance(circle1: Circle, circle2: Circle): number;
@@ -0,0 +1,30 @@
1
+ // Circle Type for Hitboxes
2
+ // Function to get the Center of the Circle as Vector2d
3
+ export function centerCircle(circle) {
4
+ return { x: circle.x, y: circle.y };
5
+ }
6
+ // Check if a Point is in the Circle
7
+ export function isPointInCircle(x, y, circle) {
8
+ const distance = Math.sqrt((x - circle.x) * (x - circle.x) +
9
+ (y - circle.y) * (y - circle.y));
10
+ return distance <= circle.radius;
11
+ }
12
+ // Check if Mouse is in the Circle
13
+ export function isMouseInCircle(mouse, circle) {
14
+ return isPointInCircle(mouse.x, mouse.y, circle);
15
+ }
16
+ // Function to check if a Circle is clicked
17
+ export function isCircleClicked(mouse, circle) {
18
+ return isMouseInCircle(mouse, circle) && mouse.justPressed;
19
+ }
20
+ // Check if two Circles collide
21
+ export function isCircleColliding(circle1, circle2) {
22
+ const distance = Math.sqrt((circle1.x - circle2.x) * (circle1.x - circle2.x) +
23
+ (circle1.y - circle2.y) * (circle1.y - circle2.y));
24
+ return distance <= (circle1.radius + circle2.radius);
25
+ }
26
+ // Check if Circle collides with Rectangle (imported from rectangle.ts would cause circular dependency)
27
+ export function getCircleDistance(circle1, circle2) {
28
+ return Math.sqrt((circle1.x - circle2.x) * (circle1.x - circle2.x) +
29
+ (circle1.y - circle2.y) * (circle1.y - circle2.y));
30
+ }
@@ -1,5 +1,7 @@
1
- export { type Vector2d, normalize2d, clamp2d, lerp2d, map2d } from "./vector2d.js";
2
- export { type Vector3d, normalize3d, clamp3d, lerp3d, map3d } from "./vector3d.js";
1
+ export { type Vector2d, add2d, subtract2d, length2d, normalize2d, dot2d, distance2d, clamp2d, lerp2d, map2d } from "./vector2d.js";
2
+ export { type Vector3d, add3d, subtract3d, length3d, normalize3d, dot3d, crossprodukt3d, distance3d, clamp3d, lerp3d, map3d } from "./vector3d.js";
3
3
  export { type Color, invertcolor, invertHexColor } from "./color.js";
4
4
  export { type Rect, centerRectX, centerRectY, centerRect, isPointInRect, isMouseInRect, isRectClicked } from "./rectangle.js";
5
+ export { type Circle, centerCircle, isPointInCircle, isMouseInCircle, isCircleClicked, isCircleColliding, getCircleDistance } from "./circle.js";
6
+ export { type Triangle, centerTriangle, isPointInTriangle, isMouseInTriangle, isTriangleClicked, getTrianglePerimeter, } from "./triangle.js";
5
7
  export { clamp, lerp, map } from "./math-utils.js";
@@ -1,11 +1,15 @@
1
1
  // Types Export
2
2
  // Vector 2D
3
- export { normalize2d, clamp2d, lerp2d, map2d } from "./vector2d.js";
3
+ export { add2d, subtract2d, length2d, normalize2d, dot2d, distance2d, clamp2d, lerp2d, map2d } from "./vector2d.js";
4
4
  // Vector 3D
5
- export { normalize3d, clamp3d, lerp3d, map3d } from "./vector3d.js";
5
+ export { add3d, subtract3d, length3d, normalize3d, dot3d, crossprodukt3d, distance3d, clamp3d, lerp3d, map3d } from "./vector3d.js";
6
6
  // Color Type
7
7
  export { invertcolor, invertHexColor } from "./color.js";
8
8
  // Retangle Type
9
9
  export { centerRectX, centerRectY, centerRect, isPointInRect, isMouseInRect, isRectClicked } from "./rectangle.js";
10
+ // Circle Type
11
+ export { centerCircle, isPointInCircle, isMouseInCircle, isCircleClicked, isCircleColliding, getCircleDistance } from "./circle.js";
12
+ // Triangle Type
13
+ export { centerTriangle, isPointInTriangle, isMouseInTriangle, isTriangleClicked, getTrianglePerimeter, } from "./triangle.js";
10
14
  // Math Utilities
11
15
  export { clamp, lerp, map } from "./math-utils.js";
@@ -0,0 +1,15 @@
1
+ import { Mouse } from "../input.js";
2
+ import { type Vector2d } from "./vector2d.js";
3
+ export type Triangle = {
4
+ x1: number;
5
+ y1: number;
6
+ x2: number;
7
+ y2: number;
8
+ x3: number;
9
+ y3: number;
10
+ };
11
+ export declare function centerTriangle(triangle: Triangle): Vector2d;
12
+ export declare function isPointInTriangle(x: number, y: number, triangle: Triangle): boolean;
13
+ export declare function isMouseInTriangle(mouse: Mouse, triangle: Triangle): boolean;
14
+ export declare function isTriangleClicked(mouse: Mouse, triangle: Triangle): boolean;
15
+ export declare function getTrianglePerimeter(triangle: Triangle): number;
@@ -0,0 +1,38 @@
1
+ // Triangle Type for Hitboxes
2
+ // Helper function to calculate the area of a triangle (used for point-in-triangle)
3
+ function getTriangleArea(x1, y1, x2, y2, x3, y3) {
4
+ return Math.abs((x1 * (y2 - y3) + x2 * (y3 - y1) + x3 * (y1 - y2)) / 2.0);
5
+ }
6
+ // Function to get the Center of the Triangle
7
+ export function centerTriangle(triangle) {
8
+ return {
9
+ x: (triangle.x1 + triangle.x2 + triangle.x3) / 3,
10
+ y: (triangle.y1 + triangle.y2 + triangle.y3) / 3
11
+ };
12
+ }
13
+ // Check if a Point is in the Triangle using barycentric coordinates
14
+ export function isPointInTriangle(x, y, triangle) {
15
+ const areaTriangle = getTriangleArea(triangle.x1, triangle.y1, triangle.x2, triangle.y2, triangle.x3, triangle.y3);
16
+ const area1 = getTriangleArea(x, y, triangle.x2, triangle.y2, triangle.x3, triangle.y3);
17
+ const area2 = getTriangleArea(triangle.x1, triangle.y1, x, y, triangle.x3, triangle.y3);
18
+ const area3 = getTriangleArea(triangle.x1, triangle.y1, triangle.x2, triangle.y2, x, y);
19
+ return Math.abs(areaTriangle - (area1 + area2 + area3)) < 0.01;
20
+ }
21
+ // Check if Mouse is in the Triangle
22
+ export function isMouseInTriangle(mouse, triangle) {
23
+ return isPointInTriangle(mouse.x, mouse.y, triangle);
24
+ }
25
+ // Function to check if a Triangle is clicked
26
+ export function isTriangleClicked(mouse, triangle) {
27
+ return isMouseInTriangle(mouse, triangle) && mouse.justPressed;
28
+ }
29
+ // Get the Perimeter of the Triangle
30
+ export function getTrianglePerimeter(triangle) {
31
+ const side1 = Math.sqrt((triangle.x2 - triangle.x1) * (triangle.x2 - triangle.x1) +
32
+ (triangle.y2 - triangle.y1) * (triangle.y2 - triangle.y1));
33
+ const side2 = Math.sqrt((triangle.x3 - triangle.x2) * (triangle.x3 - triangle.x2) +
34
+ (triangle.y3 - triangle.y2) * (triangle.y3 - triangle.y2));
35
+ const side3 = Math.sqrt((triangle.x1 - triangle.x3) * (triangle.x1 - triangle.x3) +
36
+ (triangle.y1 - triangle.y3) * (triangle.y1 - triangle.y3));
37
+ return side1 + side2 + side3;
38
+ }
@@ -2,7 +2,12 @@ export type Vector2d = {
2
2
  x: number;
3
3
  y: number;
4
4
  };
5
+ export declare function add2d(vector1: Vector2d, vector2: Vector2d): Vector2d;
6
+ export declare function subtract2d(vector1: Vector2d, vector2: Vector2d): Vector2d;
7
+ export declare function length2d(vector: Vector2d): number;
5
8
  export declare function normalize2d(vector: Vector2d): Vector2d;
9
+ export declare function dot2d(v1: Vector2d, v2: Vector2d): number;
10
+ export declare function distance2d(v1: Vector2d, v2: Vector2d): number;
6
11
  export declare function clamp2d(vector: Vector2d, min: Vector2d, max: Vector2d): Vector2d;
7
12
  export declare function lerp2d(start: Vector2d, end: Vector2d, t: Vector2d): Vector2d;
8
13
  export declare function map2d(value: Vector2d, inMin: Vector2d, inMax: Vector2d, outMin: Vector2d, outMax: Vector2d): Vector2d;
@@ -1,5 +1,25 @@
1
1
  // 2 Dimensional Vector Type
2
2
  import { clamp, lerp, map } from "./math-utils.js";
3
+ // Function to add 2 Vectors together
4
+ export function add2d(vector1, vector2) {
5
+ return {
6
+ x: vector1.x + vector2.x,
7
+ y: vector1.y + vector2.y,
8
+ };
9
+ }
10
+ // Function to subtract 2 Vectors from each other
11
+ export function subtract2d(vector1, vector2) {
12
+ return {
13
+ x: vector1.x - vector2.x,
14
+ y: vector1.y - vector2.y,
15
+ };
16
+ }
17
+ // Function to get the length from an Vector
18
+ export function length2d(vector) {
19
+ let produkt = vector.x * vector.x + vector.y * vector.y;
20
+ let root = Math.sqrt(produkt);
21
+ return root;
22
+ }
3
23
  // Function to normalize a Vector 2d
4
24
  export function normalize2d(vector) {
5
25
  // Check if the Vector is zero because then you dont need to
@@ -7,12 +27,21 @@ export function normalize2d(vector) {
7
27
  if (vector.x == 0 && vector.y == 0) {
8
28
  return vector;
9
29
  }
10
- let produkt = vector.x * vector.x + vector.y * vector.y;
11
- let root = Math.sqrt(produkt);
30
+ let root = length2d(vector);
12
31
  vector.x = vector.x / root;
13
32
  vector.y = vector.y / root;
14
33
  return vector;
15
34
  }
35
+ // Function to make scalar produkt from an Vector
36
+ export function dot2d(v1, v2) {
37
+ return (v1.x * v2.x + v1.y * v2.y);
38
+ }
39
+ // crossprodukt (only for 3 Dimensinal Vectors)
40
+ // Calculate the Distance between 2 Vectors
41
+ export function distance2d(v1, v2) {
42
+ let tmp = subtract2d(v1, v2);
43
+ return length2d(tmp);
44
+ }
16
45
  // Function to clamp a Vector 2d
17
46
  export function clamp2d(vector, min, max) {
18
47
  return {
@@ -3,7 +3,13 @@ export type Vector3d = {
3
3
  y: number;
4
4
  z: number;
5
5
  };
6
+ export declare function add3d(vector1: Vector3d, vector2: Vector3d): Vector3d;
7
+ export declare function subtract3d(vector1: Vector3d, vector2: Vector3d): Vector3d;
8
+ export declare function length3d(vector: Vector3d): number;
6
9
  export declare function normalize3d(vector: Vector3d): Vector3d;
10
+ export declare function dot3d(v1: Vector3d, v2: Vector3d): number;
11
+ export declare function crossprodukt3d(v1: Vector3d, v2: Vector3d): Vector3d;
12
+ export declare function distance3d(v1: Vector3d, v2: Vector3d): number;
7
13
  export declare function clamp3d(vector: Vector3d, min: Vector3d, max: Vector3d): Vector3d;
8
14
  export declare function lerp3d(start: Vector3d, end: Vector3d, t: Vector3d): Vector3d;
9
15
  export declare function map3d(value: Vector3d, inMin: Vector3d, inMax: Vector3d, outMin: Vector3d, outMax: Vector3d): Vector3d;
@@ -1,5 +1,27 @@
1
1
  // 3d Vector
2
2
  import { clamp, lerp, map } from "./math-utils.js";
3
+ // Function to add 2 Vectors together
4
+ export function add3d(vector1, vector2) {
5
+ return {
6
+ x: vector1.x + vector2.x,
7
+ y: vector1.y + vector2.y,
8
+ z: vector1.z + vector2.z,
9
+ };
10
+ }
11
+ // Function to subtract 2 Vectors from each other
12
+ export function subtract3d(vector1, vector2) {
13
+ return {
14
+ x: vector1.x - vector2.x,
15
+ y: vector1.y - vector2.y,
16
+ z: vector1.z - vector2.z,
17
+ };
18
+ }
19
+ // Function to get the length from an Vector
20
+ export function length3d(vector) {
21
+ let produkt = vector.x * vector.x + vector.y * vector.y + vector.z * vector.z;
22
+ let root = Math.sqrt(produkt);
23
+ return root;
24
+ }
3
25
  // Function to normalize a Vector 2d
4
26
  export function normalize3d(vector) {
5
27
  // Check if the Vector is zero because then you dont need to
@@ -7,13 +29,29 @@ export function normalize3d(vector) {
7
29
  if (vector.x == 0 && vector.y == 0 && vector.z) {
8
30
  return vector;
9
31
  }
10
- let produkt = vector.x * vector.x + vector.y * vector.y + vector.z * vector.z;
11
- let root = Math.sqrt(produkt);
32
+ let root = length3d(vector);
12
33
  vector.x = vector.x / root;
13
34
  vector.y = vector.y / root;
14
35
  vector.z = vector.z / root;
15
36
  return vector;
16
37
  }
38
+ // Function to make scalar produkt from an Vector
39
+ export function dot3d(v1, v2) {
40
+ return (v1.x * v2.x + v1.y * v2.y + v1.z * v2.z);
41
+ }
42
+ // Function to calculate the cross produkt
43
+ export function crossprodukt3d(v1, v2) {
44
+ return {
45
+ x: (v1.y * v2.z - v1.z * v2.y),
46
+ y: (v1.z * v2.x - v1.x * v2.z),
47
+ z: (v1.x * v2.y - v1.y * v2.x),
48
+ };
49
+ }
50
+ // Calculate the Distance between 2 Vectors
51
+ export function distance3d(v1, v2) {
52
+ let tmp = subtract3d(v1, v2);
53
+ return length3d(tmp);
54
+ }
17
55
  // Function to clamp a Vector 3d
18
56
  export function clamp3d(vector, min, max) {
19
57
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shadowdara/webgameengine",
3
- "version": "1.1.0",
3
+ "version": "1.3.0",
4
4
  "description": "A TypeScript game library to make HTML Games",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -10,10 +10,10 @@
10
10
  ],
11
11
  "scripts": {
12
12
  "build": "tsc",
13
- "pack": "npm run build && npm pack"
13
+ "pack": "node clean.js && npm run build && npm pack"
14
14
  },
15
15
  "bin": {
16
- "webgameengine": "dist/build/tools/devserver/cli.js"
16
+ "webgameengine": "dist/build/cli/cli.js"
17
17
  },
18
18
  "exports": {
19
19
  ".": "./dist/index.js",
@@ -1,105 +0,0 @@
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"}`);
File without changes