From c56a33b1a7808737ccf8e94e210445c820bde003 Mon Sep 17 00:00:00 2001 From: Chris Lally <24978693+ChrisLally@users.noreply.github.com> Date: Fri, 30 Jan 2026 12:32:35 -0500 Subject: [PATCH] Feature - I added a modern PixiJS v8 foundation demo with physics particles (FConsole preview) --- src/modern-pixi8/.gitignore | 3 + src/modern-pixi8/README.md | 56 ++++++ src/modern-pixi8/index.html | 133 ++++++++++++++ src/modern-pixi8/package.json | 22 +++ src/modern-pixi8/src/Game.ts | 296 ++++++++++++++++++++++++++++++++ src/modern-pixi8/src/main.ts | 81 +++++++++ src/modern-pixi8/tsconfig.json | 16 ++ src/modern-pixi8/vite.config.ts | 15 ++ 8 files changed, 622 insertions(+) create mode 100644 src/modern-pixi8/.gitignore create mode 100644 src/modern-pixi8/README.md create mode 100644 src/modern-pixi8/index.html create mode 100644 src/modern-pixi8/package.json create mode 100644 src/modern-pixi8/src/Game.ts create mode 100644 src/modern-pixi8/src/main.ts create mode 100644 src/modern-pixi8/tsconfig.json create mode 100644 src/modern-pixi8/vite.config.ts diff --git a/src/modern-pixi8/.gitignore b/src/modern-pixi8/.gitignore new file mode 100644 index 0000000..320c107 --- /dev/null +++ b/src/modern-pixi8/.gitignore @@ -0,0 +1,3 @@ +node_modules/ +dist/ +package-lock.json diff --git a/src/modern-pixi8/README.md b/src/modern-pixi8/README.md new file mode 100644 index 0000000..8a8d0a9 --- /dev/null +++ b/src/modern-pixi8/README.md @@ -0,0 +1,56 @@ +# PixiJS v8 + FConsole Foundation Demo + +A high-performance, modern graphics demonstration showcasing the **Flashist Foundation** stack. This project serves as a reference implementation for **PixiJS v8** integrated with **FConsole** for real-time debugging and performance monitoring. + +## 🚀 Key Features + +- **Native PixiJS v8**: Built from the ground up to utilize the latest PixiJS v8 features, including the new rendering pipeline (WebGPU/WebGL). +- **Physics-Based Interactivity**: A custom particle simulation using vector physics for fluid, organic movement. +- **Neon Bloom Aesthetics**: Leveraging PixiJS filters and additive blending to create a premium, "next-gen" visual style. +- **FConsole Integration (Preview)**: Pre-configured for real-time debugging. Note: Full runtime integration is pending an update to the `@flashist/fconsole` package for PixiJS v8 support. +- **Modern Tooling**: Powered by **Vite** and **TypeScript** for the fastest possible development cycle and type-safe graphics code. + +## 🎮 Interactions + +This demo is designed to be highly interactive: +- **Move Cursor**: Generates a gravitational pull that attracts the particle field. +- **Click/Tap**: Triggers a high-velocity particle "burst" at the cursor location. +- **Hover Boxes**: The interactive neon containers react to focus and mouse proximity. +- **Backquote (`)**: Toggle the **FConsole** overlay (Note: functionality currently awaiting library update). + +## 🛠 Tech Stack + +- **Core Engine**: [PixiJS v8](https://pixijs.com/) +- **Debugging**: [@flashist/fconsole](https://github.com/flashist/fconsole) +- **Framework**: [@flashist/appframework](https://github.com/flashist/appframework) +- **Build System**: [Vite](https://vitejs.dev/) +- **Language**: [TypeScript](https://www.typescriptlang.org/) + +## 📦 Getting Started + +### 1. Installation +Navigate to this project directory: +```bash +cd fexamples/src/modern-pixi8 +``` +Then install the dependencies: +```bash +npm install +``` + +### 2. Development +Start the local development server: +```bash +npm run dev +``` +The application will be available at `http://localhost:3000`. + +### 3. Build & Production +To generate a production-ready bundle: +```bash +npm run build +``` + +--- + +*Part of the [Flashist Ecosystem](https://github.com/flashist).* diff --git a/src/modern-pixi8/index.html b/src/modern-pixi8/index.html new file mode 100644 index 0000000..d282c33 --- /dev/null +++ b/src/modern-pixi8/index.html @@ -0,0 +1,133 @@ + + + + + + + FConsole: Modern Pixi.js v8 Demo + + + + + + + +
+

Modern PixiJS v8 Foundation

+

Next-Gen Tooling & Technical Implementation Preview of the Flashist Ecosystem

+ +

+ MOVE to attract • CLICK to burst • HOVER to react +

+ + + + +

+ Vite • TypeScript • Native v8 WebGL/WebGPU +


+
+ + + + + \ No newline at end of file diff --git a/src/modern-pixi8/package.json b/src/modern-pixi8/package.json new file mode 100644 index 0000000..76f6b35 --- /dev/null +++ b/src/modern-pixi8/package.json @@ -0,0 +1,22 @@ +{ + "name": "modern-pixi8-demo", + "version": "0.0.1", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview" + }, + "dependencies": { + "pixi.js": "^8.15.0", + "@flashist/fconsole": "^0.0.102", + "@flashist/flibs": "^0.0.385", + "gsap": "^3.12.0", + "howler": "^2.2.0", + "eventemitter3": "^5.0.0" + }, + "devDependencies": { + "typescript": "^5.3.3", + "vite": "^5.0.0" + } +} diff --git a/src/modern-pixi8/src/Game.ts b/src/modern-pixi8/src/Game.ts new file mode 100644 index 0000000..f426ea6 --- /dev/null +++ b/src/modern-pixi8/src/Game.ts @@ -0,0 +1,296 @@ +import { Container, Graphics, Ticker, Point, BlurFilter, Color } from "pixi.js"; + +interface ParticleData { + graphic: Graphics; + vx: number; + vy: number; + life: number; + maxLife: number; +} + +/** + * Enhanced game scene with physics-based particle field + * and 'neon bloom' aesthetics for PixiJS v8. + */ +export class Game { + + private _view!: Container; + private _particleContainer!: Container; + private _sprites: Container[] = []; + private _colors: number[] = [0x00ffff, 0xff00ff, 0xffff00, 0x00ff00, 0xff4d4d]; + private _ticker!: Ticker; + private _particles: ParticleData[] = []; + private _mousePos: Point = new Point(0, 0); + private _isMouseDown: boolean = false; + + constructor() { + this._sprites = []; + this.construction(); + this.init(); + } + + private construction(): void { + this._view = new Container(); + this._view.label = "GameContainer"; + + this._particleContainer = new Container(); + this._particleContainer.label = "ParticleContainer"; + // Apply a subtle bloom-like effect using blur and additive blending + const blur = new BlurFilter({ strength: 2 }); + this._particleContainer.filters = [blur]; + // this._particleContainer.blendMode = "add"; // Standard ADD blending for 'glow' + } + + private init(): void { + this.createBackground(); + this._view.addChild(this._particleContainer); + this.createSprites(); + this.setupInteraction(); + + // Animation ticker + this._ticker = new Ticker(); + this._ticker.add(this.onTick, this); + this._ticker.start(); + } + + private createBackground(): void { + const bg: Graphics = new Graphics(); + bg.label = "Background"; + + // Fill background with a deep space gradient-like color + bg.rect(0, 0, window.innerWidth, window.innerHeight); + bg.fill(0x0a0a1a); + + // Add subtle reactive grid + this.drawGrid(bg); + + this._view.addChild(bg); + } + + private drawGrid(bg: Graphics): void { + bg.stroke({ width: 1, color: 0x1a1a3a }); + for (let x: number = 0; x <= window.innerWidth; x += 60) { + bg.moveTo(x, 0); + bg.lineTo(x, window.innerHeight); + } + for (let y: number = 0; y <= window.innerHeight; y += 60) { + bg.moveTo(0, y); + bg.lineTo(window.innerWidth, y); + } + } + + private createSprites(): void { + const centerX: number = window.innerWidth / 2; + const centerY: number = window.innerHeight / 2; + + for (let i: number = 0; i < 5; i++) { + const sprite: Container = this.createColoredBox( + this._colors[i], + `Box${i + 1}` + ); + + sprite.x = centerX + (i * 120 - 240); + sprite.y = centerY; + + this._sprites.push(sprite); + this._view.addChild(sprite); + } + } + + private createColoredBox(color: number, name: string): Container { + const container: Container = new Container(); + container.label = name; + + const graphics: Graphics = new Graphics(); + graphics.label = `${name}Graphics`; + + // Draw box with a slight 'glow' via multiple strokes + graphics.rect(-40, -40, 80, 80); + graphics.fill({ color, alpha: 0.8 }); + graphics.stroke({ width: 2, color: 0xffffff, alpha: 0.5 }); + graphics.stroke({ width: 6, color, alpha: 0.2 }); + + container.addChild(graphics); + + // Make interactive + container.eventMode = "static"; + container.cursor = "pointer"; + + container.on("pointerover", () => { + graphics.scale.set(1.1); + }); + container.on("pointerout", () => { + graphics.scale.set(1.0); + }); + + return container; + } + + private setupInteraction(): void { + const interactionLayer: Graphics = new Graphics(); + interactionLayer.label = "InteractionLayer"; + interactionLayer.rect(0, 0, window.innerWidth, window.innerHeight); + interactionLayer.fill({ color: 0x000000, alpha: 0 }); + interactionLayer.eventMode = "static"; + interactionLayer.cursor = "crosshair"; + + interactionLayer.on("pointermove", (event) => { + this._mousePos.copyFrom(event.global); + }); + + interactionLayer.on("pointerdown", () => { + this._isMouseDown = true; + }); + + interactionLayer.on("pointerup", () => { + this._isMouseDown = false; + }); + + interactionLayer.on("pointerupoutside", () => { + this._isMouseDown = false; + }); + + this._view.addChildAt(interactionLayer, 1); + } + + private spawnParticle(x: number, y: number, isExplosion: boolean = false): void { + const color: number = this._colors[Math.floor(Math.random() * this._colors.length)]; + const graphic = new Graphics(); + + const size = isExplosion ? 2 + Math.random() * 4 : 1 + Math.random() * 2; + graphic.circle(0, 0, size); + graphic.fill(color); + // graphic.blendMode = "add"; + + graphic.x = x; + graphic.y = y; + + let vx, vy; + if (isExplosion) { + const angle = Math.random() * Math.PI * 2; + const speed = 2 + Math.random() * 5; + vx = Math.cos(angle) * speed; + vy = Math.sin(angle) * speed; + } else { + vx = (Math.random() - 0.5) * 2; + vy = (Math.random() - 0.5) * 2; + } + + const particle: ParticleData = { + graphic, + vx, + vy, + life: 1.0, + maxLife: 0.01 + Math.random() * 0.02 + }; + + this._particles.push(particle); + this._particleContainer.addChild(graphic); + } + + private onTick(): void { + const time: number = Date.now() * 0.001; + const centerY: number = window.innerHeight / 2; + + // Animate boxes + for (let i: number = 0; i < this._sprites.length; i++) { + const sprite: Container = this._sprites[i]; + sprite.y = centerY + Math.sin(time * 0.8 + i) * 60; + sprite.rotation = Math.sin(time * 0.4 + i) * 0.15; + + // Interaction with mouse + const dx = this._mousePos.x - sprite.x; + const dy = this._mousePos.y - sprite.y; + const dist = Math.sqrt(dx * dx + dy * dy); + if (dist < 200) { + const force = (200 - dist) / 200; + sprite.x -= dx * force * 0.02; + sprite.y -= dy * force * 0.02; + } + } + + // Spawn particles at mouse position + if (this._isMouseDown || Math.random() > 0.5) { + const count = this._isMouseDown ? 5 : 1; + for (let i = 0; i < count; i++) { + this.spawnParticle( + this._mousePos.x + (Math.random() - 0.5) * 20, + this._mousePos.y + (Math.random() - 0.5) * 20, + this._isMouseDown + ); + } + } + + // Update particles + for (let i = this._particles.length - 1; i >= 0; i--) { + const p = this._particles[i]; + + // Physics: Attract to mouse + const dx = this._mousePos.x - p.graphic.x; + const dy = this._mousePos.y - p.graphic.y; + const dist = Math.sqrt(dx * dx + dy * dy); + + if (dist < 300) { + const force = (300 - dist) / 3000; + p.vx += dx * force; + p.vy += dy * force; + } + + // Friction + p.vx *= 0.98; + p.vy *= 0.98; + + p.graphic.x += p.vx; + p.graphic.y += p.vy; + + // Life + p.life -= p.maxLife; + p.graphic.alpha = p.life; + p.graphic.scale.set(p.life); + + if (p.life <= 0) { + this._particleContainer.removeChild(p.graphic); + p.graphic.destroy(); + this._particles.splice(i, 1); + } + } + + // Constrain particle count for performance + if (this._particles.length > 1000) { + const removed = this._particles.splice(0, this._particles.length - 1000); + removed.forEach(p => { + this._particleContainer.removeChild(p.graphic); + p.graphic.destroy(); + }); + } + } + + public onResize(): void { + const bg = this._view.children.find(c => c.label === "Background") as Graphics; + if (bg) { + bg.clear(); + bg.rect(0, 0, window.innerWidth, window.innerHeight); + bg.fill(0x0a0a1a); + this.drawGrid(bg); + } + + const interactionLayer = this._view.children.find(c => c.label === "InteractionLayer") as Graphics; + if (interactionLayer) { + interactionLayer.clear(); + interactionLayer.rect(0, 0, window.innerWidth, window.innerHeight); + interactionLayer.fill({ color: 0, alpha: 0 }); + } + + const centerX: number = window.innerWidth / 2; + for (let i: number = 0; i < this._sprites.length; i++) { + const sprite: Container = this._sprites[i]; + sprite.x = centerX + (i * 120 - 240); + } + } + + get view(): Container { + return this._view; + } + +} + diff --git a/src/modern-pixi8/src/main.ts b/src/modern-pixi8/src/main.ts new file mode 100644 index 0000000..b989299 --- /dev/null +++ b/src/modern-pixi8/src/main.ts @@ -0,0 +1,81 @@ +import { Application } from "pixi.js"; +import { FC } from "@flashist/fconsole"; + +import { Game } from "./Game"; + +// --- Minimal Adapter Shim for FConsole --- +class Pixi8Adapter { + constructor(private app: Application) { } + get ticker() { return this.app.ticker; } + get renderer() { return this.app.renderer; } + createDisplayObjectContainerWrapper(nativeObject: any) { return nativeObject; } +} +// Support for legacy global pattern if needed by FConsole +(window as any).EngineAdapter = { instance: null }; + +/** + * Entry point for the modern Pixi.js v8 demo. + * Initializes FConsole with raw Pixi stage. + */ +class Main { + + private _app!: Application; + private _game!: Game; + + constructor() { + this.init(); + } + + private async init(): Promise { + this._app = new Application(); + + await this._app.init({ + resizeTo: window, + backgroundColor: 0x0a0a1a, + antialias: true + }); + + document.body.appendChild(this._app.canvas); + + // + this._game = new Game(); + this._app.stage.addChild(this._game.view); + + // Initialize fconsole with raw Pixi stage + shim + try { + // Set up the global adapter instance for FConsole + (window as any).EngineAdapter.instance = new Pixi8Adapter(this._app); + + // Shim the stage with required properties for direct access + const stageShim: any = this._app.stage; + stageShim.ticker = this._app.ticker; + stageShim.renderer = this._app.renderer; + + // @ts-ignore + FC.startInit(stageShim); + FC.visible = true; + } catch (e) { + console.warn("FConsole initialization failed:", e); + } + + // Handle window resize + window.addEventListener("resize", () => this.onResize()); + + // Handle FConsole toggle + window.addEventListener("keydown", (e) => { + if (e.code === "Backquote") { + FC.visible = !FC.visible; + } + }); + } + + private onResize(): void { + if (this._game) { + this._game.onResize(); + } + } + +} + +// Start the application +new Main(); diff --git a/src/modern-pixi8/tsconfig.json b/src/modern-pixi8/tsconfig.json new file mode 100644 index 0000000..f402e3f --- /dev/null +++ b/src/modern-pixi8/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "ESNext", + "moduleResolution": "bundler", + "lib": ["ES2020", "DOM"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true + }, + "include": ["src/**/*"] +} diff --git a/src/modern-pixi8/vite.config.ts b/src/modern-pixi8/vite.config.ts new file mode 100644 index 0000000..8455c12 --- /dev/null +++ b/src/modern-pixi8/vite.config.ts @@ -0,0 +1,15 @@ +import { defineConfig } from 'vite'; + +export default defineConfig({ + server: { + port: 3000, + open: true + }, + build: { + outDir: 'dist', + sourcemap: true + }, + optimizeDeps: { + include: ['eventemitter3', '@flashist/fcore', '@flashist/flibs', '@flashist/fconsole'] + } +});