1
0
Fork 0
Code Issues Pull requests Projects Releases Packages Wiki Activity Actions Pages

first commit

This commit is contained in:
Andraxion 2026-06-29 12:59:15 -04:00
commit 821778036c
6 changed files with 1675 additions and 0 deletions

66
audio.html Normal file
View file

@ -0,0 +1,66 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Plugin System: Audio Example</title>
<style>
body { font-family: sans-serif; line-height: 1.6; max-width: 800px; margin: 40px auto; padding: 0 20px; color: #333; }
code { background: #f4f4f4; padding: 2px 4px; border-radius: 4px; }
pre { background: #2d2d2d; color: #fff; padding: 15px; border-radius: 6px; overflow-x: auto; }
</style>
</head>
<body>
<h1>Plugin Architecture: Audio System</h1>
<p>This guide demonstrates how to create a "pop-in" plugin that listens for game events and triggers audio, maintaining strict separation of concerns.</p>
<h2>1. The Design Principle</h2>
<p>The core engine does <strong>not</strong> contain a "play sound" function inside the combat logic. Instead, the combat logic emits an event. The Audio Plugin sits in the background, listening for that specific event, and plays the sound when it hears it.</p>
<h2>2. The Plugin Implementation</h2>
<pre><code>// plugins/AudioPlugin.js
export const AudioPlugin = {
id: 'audio-system',
// The engine calls this upon loading the plugin
init(eventBus, assetManager) {
this.eventBus = eventBus;
this.assetManager = assetManager;
// Listen for global game events
this.eventBus.on('ENTITY_DIED', (data) => {
this.playSound('explosion_01.mp3');
});
this.eventBus.on('PLAYER_JUMP', () => {
this.playSound('jump_sfx.ogg');
});
},
playSound(fileName) {
// Access pre-loaded audio buffers from the asset manager
const sound = this.assetManager.getAudio(fileName);
sound.play();
}
};</code></pre>
<h2>3. How it "Pops In"</h2>
<p>Because the plugin follows a standard interface (the <code>init</code> function), the core engine's loader handles it automatically:</p>
<pre><code>// Core Engine Loader
async function loadPlugins() {
const pluginFiles = ['plugins/AudioPlugin.js', 'plugins/RendererPlugin.js'];
for (const path of pluginFiles) {
const { plugin } = await import(path);
// Inject the Event Bus so the plugin can "talk" to the rest of the game
plugin.init(globalEventBus, globalAssetManager);
}
}</code></pre>
<h2>4. Key Benefits</h2>
<ul>
<li><strong>Zero Coupling:</strong> If you remove <code>AudioPlugin.js</code>, the combat system continues to function perfectly without errors.</li>
<li><strong>Easy Testing:</strong> You can test the audio system in isolation by manually firing an <code>ENTITY_DIED</code> event from the browser console.</li>
<li><strong>Performance:</strong> The audio plugin only reacts to events. It does not run in the main 60fps render loop, keeping the core engine lightweight.</li>
</ul>
</body>
</html>

207
chunks.html Normal file
View file

@ -0,0 +1,207 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Infinite Chunk System - Architecture Manual</title>
<style>
:root {
--bg-color: #f4f4f9;
--text-color: #333;
--sidebar-bg: #2c3e50;
--sidebar-text: #ecf0f1;
--link-color: #3498db;
--code-bg: #e2e2e8;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
margin: 0;
padding: 0;
display: flex;
background-color: var(--bg-color);
color: var(--text-color);
line-height: 1.6;
}
/* Sidebar Styles */
nav {
width: 280px;
background-color: var(--sidebar-bg);
color: var(--sidebar-text);
height: 100vh;
position: fixed;
overflow-y: auto;
padding: 20px;
box-sizing: border-box;
}
nav h2 {
margin-top: 0;
font-size: 1.2rem;
border-bottom: 1px solid #455a64;
padding-bottom: 10px;
}
nav ul {
list-style: none;
padding: 0;
}
nav ul li {
margin-bottom: 10px;
}
nav ul li a {
color: var(--sidebar-text);
text-decoration: none;
font-size: 0.95rem;
display: block;
padding: 5px;
border-radius: 4px;
transition: background 0.2s;
}
nav ul li a:hover {
background-color: #34495e;
}
/* Main Content Styles */
main {
margin-left: 280px;
padding: 40px;
max-width: 900px;
box-sizing: border-box;
}
section {
margin-bottom: 50px;
background: #fff;
padding: 30px;
border-radius: 8px;
box-shadow: 0 4px 6px rgba(0,0,0,0.05);
}
h1 {
border-bottom: 2px solid var(--link-color);
padding-bottom: 10px;
margin-top: 0;
}
h2 {
color: #2c3e50;
margin-top: 0;
}
h3 {
color: #34495e;
}
code {
background-color: var(--code-bg);
padding: 2px 5px;
border-radius: 4px;
font-family: "Courier New", Courier, monospace;
font-size: 0.9em;
}
pre {
background-color: #2d2d2d;
color: #ccc;
padding: 15px;
border-radius: 6px;
overflow-x: auto;
}
pre code {
background-color: transparent;
color: inherit;
padding: 0;
}
</style>
</head>
<body>
<nav>
<h2>Chunk System Manual</h2>
<ul>
<li><a href="#react-pixi-bridge">1. The React-Pixi Bridge</a></li>
<li><a href="#chunk-management">2. Managing the Infinite Grid</a></li>
<li><a href="#object-pooling">3. Rendering & Object Pools</a></li>
<li><a href="#plugin-system">4. Plugins for an Infinite Grid</a></li>
</ul>
</nav>
<main>
<h1>Infinite Chunk & Integration Architecture</h1>
<p>A focused guide on coordinating a React-based UI with a PixiJS rendering engine to manage and render an infinite 32x32 grid-based world, alongside a lifecycle-hooked plugin system.</p>
<section id="react-pixi-bridge">
<h2>1. The React-Pixi Bridge: Separating UI State from Engine State</h2>
<p>To keep things modular and performant, PixiJS and React must be treated as two completely separate applications that talk across a bridge.</p>
<ul>
<li><strong>The Engine (PixiJS)</strong> runs on a <code>requestAnimationFrame</code> loop. It controls the game loop and renders the canvas.</li>
<li><strong>The Editor UI (React)</strong> runs on state updates. It should <em>not</em> try to control or re-render Pixi objects directly via React state, as that will tank performance.</li>
</ul>
<h3>The Communication Model</h3>
<p>Use a lightweight <strong>Event Bus</strong> or a <strong>Command Pattern</strong> to bridge the gap.</p>
<ul>
<li>When a user selects a tile in the React Asset Browser and clicks on the Pixi Canvas, React emits a global event: <code>EDITOR_BRUSH_CHANGED { type: 'grass_tile' }</code>.</li>
<li>The Pixi-based editor controller listens to this event, updates its internal brush state, and handles the actual vertex manipulation on the canvas.</li>
<li>Conversely, when Pixi updates the camera position, it emits a throttled <code>CAMERA_MOVED { x, y }</code> event so React can update the coordinate numbers in the status bar.</li>
</ul>
</section>
<section id="chunk-management">
<h2>2. Managing the Infinite Grid (32x32 Chunks)</h2>
<p>In an infinite world, you cannot keep every tile in memory. You need a <strong>Chunk Manager</strong> that dynamically loads and unloads data based on where the camera is looking.</p>
<h3>Data Structure: Hash Map of Coordinates</h3>
<p>Instead of a giant multi-dimensional array, store your chunks in a standard JavaScript <code>Map</code> using a string coordinate hash as the key:</p>
<pre><code>// Key format: "chunkX,chunkY" -> e.g., "0,0", "-1,4"
const chunkMap = new Map();
function getChunkKey(worldX, worldY) {
const chunkX = Math.floor(worldX / (32 * 32)); // 32 tiles * 32 pixels
const chunkY = Math.floor(worldY / (32 * 32));
return `${chunkX},${chunkY}`;
}
</code></pre>
</section>
<section id="object-pooling">
<h2>3. Rendering: PixiJS Container Pools</h2>
<p>Do not create and destroy Pixi <code>Container</code> or <code>Sprite</code> objects every time a chunk moves out of view. That will trigger the JavaScript Garbage Collector and cause massive frame drops.</p>
<p>Instead, use an <strong>Object Pool</strong>:</p>
<ol>
<li>Create a fixed number of Pixi <code>Container</code> objects (enough to cover the screen plus a 1-chunk padding buffer).</li>
<li>When a chunk leaves the screen, <em>do not delete it</em>. Instead, clear its children, return the container to the pool, and save its raw tile data (just integers representing tile IDs) back to your JavaScript <code>chunkMap</code>.</li>
<li>When a new chunk enters the screen, grab an idle container from the pool, populate it with the correct sprites from your Pixi Texture Atlas, and reposition it.</li>
</ol>
</section>
<section id="plugin-system">
<h2>4. Designing the "Pop-In" Plugin System for an Infinite Grid</h2>
<p>To make libraries "pop-in" seamlessly to an infinite chunk system, your plugins need to hook directly into the <strong>Chunk Lifecycle</strong>. Your plugin interface should expose events that trigger when chunks are created, loaded, simulated, or saved.</p>
<h3>Example: A Procedural Generation Plugin</h3>
<p>Imagine you want to "pop in" a Perlin Noise terrain generator library. The plugin code would look like this:</p>
<pre><code>// BiomeGeneratorPlugin.js
export const plugin = {
id: "procedural-biomes",
init(engine) {
// Register hooks into the engine's chunk lifecycle
engine.chunks.on('chunk_created', (chunk) => {
this.generateTerrain(chunk);
});
},
generateTerrain(chunk) {
// chunk.x and chunk.y tell us where we are in the infinite world
for (let x = 0; x < 32; x++) {
for (let y = 0; y < 32; y++) {
const globalX = (chunk.x * 32) + x;
const globalY = (chunk.y * 32) + y;
// Determine tile type using your library's math
const tileId = noise(globalX * 0.05, globalY * 0.05) > 0.5 ? 1 : 2;
chunk.setTile(x, y, tileId);
}
}
}
};
</code></pre>
<p>Because the Core Engine simply fires a <code>chunk_created</code> event every time the camera moves into ungenerated territory, any plugin can listen to that event, modify the raw tile data array, and let PixiJS handle the rendering automatically. The editor can expose toggle switches in React to enable or disable these loaded generation plugins on the fly.</p>
</section>
</main>
</body>
</html>

1070
manual.html Normal file

File diff suppressed because it is too large Load diff

54
painter.html Normal file
View file

@ -0,0 +1,54 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Plugin System: TypeScript Export</title>
<style>
body { font-family: sans-serif; line-height: 1.6; max-width: 800px; margin: 40px auto; padding: 0 20px; color: #333; }
code { background: #f4f4f4; padding: 2px 4px; border-radius: 4px; }
pre { background: #2d2d2d; color: #ccc; padding: 15px; border-radius: 6px; overflow-x: auto; }
</style>
</head>
<body>
<h1>Exporting to TypeScript</h1>
<p>Instead of relying on <code>JSON.parse()</code> at runtime, this plugin converts your 16x16 sprite data into static TypeScript files. This allows your IDE to provide autocomplete for your assets and ensures your data structure is validated at compile time.</p>
<h2>1. The TypeScript Exporter Plugin</h2>
<pre><code>// plugins/TypeScriptExporter.js
export const TypeScriptExporter = {
id: 'ts-exporter',
// Transforms internal pixel grid to a TypeScript source string
generateExport(spriteName, pixelData) {
const typeDefinition = `export interface SpriteData { name: string; pixels: number[]; }`;
const dataConstant = `export const ${spriteName}: SpriteData = {
name: "${spriteName}",
pixels: ${JSON.stringify(pixelData)}
};`;
return `${typeDefinition}\n\n${dataConstant}`;
}
};</code></pre>
<h2>2. The Workflow: From Canvas to Code</h2>
<ol>
<li><strong>Tooling:</strong> The user draws on the 16x16 canvas in the React Editor.</li>
<li><strong>Serialization:</strong> The Editor calls <code>TypeScriptExporter.generateExport('hero_walk', pixels)</code>.</li>
<li><strong>Write to Disk:</strong> The Editor (via Electron/Tauri/Node) saves the resulting string as <code>src/assets/hero_walk.ts</code>.</li>
<li><strong>Runtime Usage:</strong> In your game code, you simply import the asset:
<pre><code>import { hero_walk } from './assets/hero_walk';
console.log(hero_walk.pixels); // Type-safe and immediately available</code></pre>
</li>
</ol>
<h2>3. Why Export to TS?</h2>
<ul>
<li><strong>Zero Parse Latency:</strong> Your game doesn't need to load or parse JSON files at startup; the data is bundled directly into your application code.</li>
<li><strong>Type Safety:</strong> If you change your sprite format, TypeScript will throw an error immediately, preventing runtime crashes.</li>
<li><strong>Bundler Optimization:</strong> Webpack/Vite/Esbuild can tree-shake your assets, ensuring only the sprites you actually import are included in your final production build.</li>
</ul>
<h2>4. Architecture Tip: Dual-Mode</h2>
<p>You might want to keep the raw JSON for the editor to load/save while working, and use the TypeScript exporter only for the final build pipeline. This keeps your editor fast and iterative while making your runtime lean and performant.</p>
</body>
</html>

62
sprite editor.html Normal file
View file

@ -0,0 +1,62 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Plugin System: Sprite Editor</title>
<style>
body { font-family: sans-serif; line-height: 1.6; max-width: 800px; margin: 40px auto; padding: 0 20px; color: #333; }
code { background: #f4f4f4; padding: 2px 4px; border-radius: 4px; }
pre { background: #2d2d2d; color: #fff; padding: 15px; border-radius: 6px; overflow-x: auto; }
</style>
</head>
<body>
<h1>Plugin Architecture: Sprite Editor</h1>
<p>This plugin acts as a <strong>feature-set provider</strong>. It does two things: it injects a toolset into the React Editor UI and provides the serialization logic to convert 16x16 pixel arrays into your project's JSON format.</p>
<h2>1. The Plugin Structure</h2>
<p>To make this "pop in," the plugin exports a UI component for React and a logic object for the Engine.</p>
<pre><code>// plugins/SpriteEditorPlugin.js
export const SpriteEditorPlugin = {
id: 'sprite-editor',
// 1. Injected UI for React
renderTool(editorProps) {
return &lt;SpriteToolUI palette={PICO8_PALETTE} {...editorProps} /&gt;;
},
// 2. Logic for serialization
serialize(pixelData) {
// Converts 16x16 array to compact JSON
return {
type: 'sprite',
size: 16,
data: btoa(pixelData.join('')) // Base64 encoding for size
};
}
};</code></pre>
<h2>2. Dynamic Injection</h2>
<p>Your main Editor shell uses an "Extension Registry." On boot, it iterates through loaded plugins and asks if they have a <code>renderTool</code> method. If they do, it mounts them into the Sidebar.</p>
<pre><code>// EditorSidebar.jsx
function EditorSidebar({ plugins }) {
return (
&lt;div&gt;
{plugins.map(p =&gt; p.renderTool &amp;&amp; p.renderTool())}
&lt;/div&gt;
);
}</code></pre>
<h2>3. The "Tooling" Workflow</h2>
<ol>
<li><strong>Discovery:</strong> The Editor scans the <code>/plugins</code> directory.</li>
<li><strong>Registration:</strong> It adds <code>SpriteEditorPlugin</code> to the central <code>PluginRegistry</code>.</li>
<li><strong>UI Mounting:</strong> React sees the new tool in the Registry and dynamically renders the 16x16 canvas component in the sidebar.</li>
<li><strong>Serialization:</strong> When the user hits "Save," the Editor loops through all registered plugins, calling their <code>serialize()</code> method to bundle the file.</li>
</ol>
<h2>4. Why this keeps the Engine clean</h2>
<p>The Core Engine doesn't know "Sprite Editor" exists. It only knows that when it loads a map file, it might contain a <code>sprite</code> data block. The plugin handles the complexity of the 16x16 grid math and color palette, while the Engine just treats the result as another JSON asset to be rendered by PixiJS.</p>
</body>
</html>

216
topology of systems.html Normal file
View file

@ -0,0 +1,216 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Infinite Web Game Engine - Architecture Manual</title>
<style>
:root {
--bg-color: #f4f4f9;
--text-color: #333;
--sidebar-bg: #2c3e50;
--sidebar-text: #ecf0f1;
--link-color: #3498db;
--code-bg: #e2e2e8;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
margin: 0;
padding: 0;
display: flex;
background-color: var(--bg-color);
color: var(--text-color);
line-height: 1.6;
}
/* Sidebar Styles */
nav {
width: 280px;
background-color: var(--sidebar-bg);
color: var(--sidebar-text);
height: 100vh;
position: fixed;
overflow-y: auto;
padding: 20px;
box-sizing: border-box;
}
nav h2 {
margin-top: 0;
font-size: 1.2rem;
border-bottom: 1px solid #455a64;
padding-bottom: 10px;
}
nav ul {
list-style: none;
padding: 0;
}
nav ul li {
margin-bottom: 10px;
}
nav ul li a {
color: var(--sidebar-text);
text-decoration: none;
font-size: 0.95rem;
display: block;
padding: 5px;
border-radius: 4px;
transition: background 0.2s;
}
nav ul li a:hover {
background-color: #34495e;
}
/* Main Content Styles */
main {
margin-left: 280px;
padding: 40px;
max-width: 900px;
box-sizing: border-box;
}
section {
margin-bottom: 50px;
background: #fff;
padding: 30px;
border-radius: 8px;
box-shadow: 0 4px 6px rgba(0,0,0,0.05);
}
h1 {
border-bottom: 2px solid var(--link-color);
padding-bottom: 10px;
margin-top: 0;
}
h2 {
color: #2c3e50;
margin-top: 0;
}
h3 {
color: #34495e;
}
code {
background-color: var(--code-bg);
padding: 2px 5px;
border-radius: 4px;
font-family: "Courier New", Courier, monospace;
font-size: 0.9em;
}
pre {
background-color: #2d2d2d;
color: #ccc;
padding: 15px;
border-radius: 6px;
overflow-x: auto;
}
pre code {
background-color: transparent;
color: inherit;
padding: 0;
}
</style>
</head>
<body>
<nav>
<h2>Engine Manual</h2>
<ul>
<li><a href="#overview">1. Overview & Separation of Concerns</a></li>
<li><a href="#tech-stack">2. Tech Stack</a></li>
<li><a href="#ecs">3. Entity-Component-System (ECS)</a></li>
<li><a href="#world-chunks">4. Infinite World & Chunk Management</a></li>
<li><a href="#plugins">5. The "Pop-In" Plugin System</a></li>
<li><a href="#react-pixi-bridge">6. React & PixiJS Bridge</a></li>
</ul>
</nav>
<main>
<h1>Infinite Web Game Engine Architecture</h1>
<p>A comprehensive guide to building a modular, data-driven 2D game engine designed for infinite, chunk-based worlds using HTML5, JavaScript, React, and PixiJS.</p>
<section id="overview">
<h2>1. Overview & Separation of Concerns</h2>
<p>To keep the engine scalable and highly modular, the architecture relies on strict separation between three core pillars:</p>
<ul>
<li><strong>The Core Engine (PixiJS):</strong> The bedrock. Handles rendering, math, and the Entity-Component-System (ECS). It is entirely agnostic to game logic and only knows about raw data.</li>
<li><strong>The Editor (React):</strong> A specialized UI layer that sits on top of the engine. Its primary job is <em>Serialization</em>—allowing visual arrangement of resources and saving them as JSON data.</li>
<li><strong>The Runtime (Game):</strong> The lightweight entry point. It boots the Engine, parses the JSON data exported by the Editor, and executes the game loop.</li>
</ul>
</section>
<section id="tech-stack">
<h2>2. Tech Stack</h2>
<p>The chosen technologies leverage the speed of WebGL and the dynamic nature of JavaScript:</p>
<ul>
<li><strong>Rendering & Viewport:</strong> <code>PixiJS</code> (Handles the core canvas game loop and batching)</li>
<li><strong>Editor UI:</strong> <code>React</code> (Manages the complex state of the editor interface, scene graphs, and asset browsers)</li>
<li><strong>Modularity:</strong> Native ES Modules (<code>import()</code>)</li>
<li><strong>Data & Serialization:</strong> standard JSON</li>
</ul>
</section>
<section id="ecs">
<h2>3. Entity-Component-System (ECS) & Memory</h2>
<p>To avoid JavaScript Garbage Collection (GC) stutters, the engine uses a <strong>Data-Oriented Design</strong>.</p>
<ul>
<li><strong>Entities:</strong> Plain integer IDs (e.g., <code>Entity 45</code>).</li>
<li><strong>Components:</strong> Stored in flat TypedArrays (like <code>Float32Array</code>) rather than standard JS objects. This keeps memory contiguous and blazing fast for the CPU.</li>
<li><strong>Systems:</strong> Functions that iterate over these flat arrays to update game state.</li>
</ul>
</section>
<section id="world-chunks">
<h2>4. Infinite World & Chunk Management</h2>
<p>The world is based on a 32x32 infinite grid. Because you cannot hold an infinite world in memory, data is dynamically loaded and unloaded.</p>
<h3>Chunk Storage Map</h3>
<p>World data is stored in a JavaScript <code>Map</code> using a string coordinate hash as the key. This allows the world to expand infinitely in any direction, including negative coordinates.</p>
<pre><code>// Example hash key format: "chunkX,chunkY" -> "-1,4"
const chunkMap = new Map();
function getChunkKey(worldX, worldY) {
const chunkX = Math.floor(worldX / (32 * 32));
const chunkY = Math.floor(worldY / (32 * 32));
return `${chunkX},${chunkY}`;
}
</code></pre>
<h3>Rendering Object Pool</h3>
<p>To maintain performance, PixiJS objects are never created or destroyed on the fly. The engine uses an <strong>Object Pool</strong>.</p>
<ol>
<li>Pre-allocate enough Pixi <code>Container</code> objects to cover the screen plus a buffer.</li>
<li>When a chunk leaves the screen, clear its children, return the container to the pool, and save its raw tile data to the <code>chunkMap</code>.</li>
<li>When a new chunk enters, grab an idle container from the pool and populate it with sprites.</li>
</ol>
</section>
<section id="plugins">
<h2>5. The "Pop-In" Plugin System</h2>
<p>Libraries (like procedural generation or specialized AI) can simply be dropped into a folder and instantly recognized.</p>
<p>This is achieved using native ES Dynamic Imports. Plugins hook into the engine's <strong>Event Bus</strong> and chunk lifecycles.</p>
<h3>Example: Procedural Generation Hook</h3>
<pre><code>// plugins/BiomeGenerator.js
export const plugin = {
id: "procedural-biomes",
init(engine) {
// Hook into the core engine's chunk creation event
engine.chunks.on('chunk_created', (chunk) => {
this.generateTerrain(chunk);
});
},
generateTerrain(chunk) {
// Populate the 32x32 grid with noise data
}
};
</code></pre>
</section>
<section id="react-pixi-bridge">
<h2>6. React & PixiJS Communication Bridge</h2>
<p>React (State) and PixiJS (Render Loop) must remain strictly decoupled to protect performance.</p>
<p>They communicate entirely via a lightweight <strong>Event Bus</strong> or <strong>Command Pattern</strong>:</p>
<ul>
<li><strong>React to PixiJS:</strong> React emits events based on user input. For example, selecting a tile in the editor emits <code>EDITOR_BRUSH_CHANGED</code>. The Pixi loop listens and updates its internal placement state.</li>
<li><strong>PixiJS to React:</strong> Pixi emits throttled state events. For example, as the camera pans, it emits <code>CAMERA_MOVED</code>. React listens to this and updates the coordinate UI in the sidebar without re-rendering the game canvas.</li>
</ul>
</section>
</main>
</body>
</html>