|
|
|
|
@@ -1,5 +1,4 @@
|
|
|
|
|
import { Scene } from 'phaser';
|
|
|
|
|
import { GAME_WIDTH, GAME_HEIGHT } from '../config/game.config.js';
|
|
|
|
|
|
|
|
|
|
export class BootScene extends Scene {
|
|
|
|
|
constructor() {
|
|
|
|
|
@@ -24,185 +23,143 @@ export class BootScene extends Scene {
|
|
|
|
|
this.scene.start('MenuScene');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- Canvas2D background painting (rich gradients, glows, bokeh) ---
|
|
|
|
|
|
|
|
|
|
_canvasTex(key, draw) {
|
|
|
|
|
const w = GAME_WIDTH;
|
|
|
|
|
const h = GAME_HEIGHT;
|
|
|
|
|
if (this.textures.exists(key)) this.textures.remove(key);
|
|
|
|
|
const canvas = document.createElement('canvas');
|
|
|
|
|
canvas.width = w;
|
|
|
|
|
canvas.height = h;
|
|
|
|
|
const ctx = canvas.getContext('2d');
|
|
|
|
|
draw(ctx, w, h);
|
|
|
|
|
this.textures.addCanvas(key, canvas);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_vgrad(ctx, w, h, stops) {
|
|
|
|
|
const g = ctx.createLinearGradient(0, 0, 0, h);
|
|
|
|
|
stops.forEach(([o, c]) => g.addColorStop(o, c));
|
|
|
|
|
ctx.fillStyle = g;
|
|
|
|
|
ctx.fillRect(0, 0, w, h);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Soft radial glow filling the canvas; color is an rgba string at full center.
|
|
|
|
|
_glow(ctx, x, y, r, color) {
|
|
|
|
|
const g = ctx.createRadialGradient(x, y, 0, x, y, r);
|
|
|
|
|
g.addColorStop(0, color);
|
|
|
|
|
g.addColorStop(1, 'rgba(0,0,0,0)');
|
|
|
|
|
ctx.fillStyle = g;
|
|
|
|
|
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Elongated, rotated glow used for aurora ribbons / light beams.
|
|
|
|
|
_ribbon(ctx, cx, cy, rx, ry, rot, color) {
|
|
|
|
|
ctx.save();
|
|
|
|
|
ctx.translate(cx, cy);
|
|
|
|
|
ctx.rotate(rot);
|
|
|
|
|
ctx.scale(rx / ry, 1);
|
|
|
|
|
const g = ctx.createRadialGradient(0, 0, 0, 0, 0, ry);
|
|
|
|
|
g.addColorStop(0, color);
|
|
|
|
|
g.addColorStop(1, 'rgba(0,0,0,0)');
|
|
|
|
|
ctx.fillStyle = g;
|
|
|
|
|
ctx.beginPath();
|
|
|
|
|
ctx.arc(0, 0, ry, 0, Math.PI * 2);
|
|
|
|
|
ctx.fill();
|
|
|
|
|
ctx.restore();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_stars(ctx, w, h, count, colors, maxR = 1.6, yMax = 1) {
|
|
|
|
|
for (let i = 0; i < count; i++) {
|
|
|
|
|
const x = Math.random() * w;
|
|
|
|
|
const y = Math.random() * h * yMax;
|
|
|
|
|
const r = Math.random() * maxR + 0.3;
|
|
|
|
|
ctx.globalAlpha = 0.3 + Math.random() * 0.7;
|
|
|
|
|
ctx.fillStyle = colors[(Math.random() * colors.length) | 0];
|
|
|
|
|
ctx.beginPath();
|
|
|
|
|
ctx.arc(x, y, r, 0, Math.PI * 2);
|
|
|
|
|
ctx.fill();
|
|
|
|
|
}
|
|
|
|
|
ctx.globalAlpha = 1;
|
|
|
|
|
// Generates a texture via an off-screen graphics buffer.
|
|
|
|
|
_tex(key, w, h, draw) {
|
|
|
|
|
const g = this.make.graphics({ x: 0, y: 0, add: false });
|
|
|
|
|
draw(g);
|
|
|
|
|
g.generateTexture(key, w, h);
|
|
|
|
|
g.destroy();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
createBackgroundTextures() {
|
|
|
|
|
// 1) Nebula — colorful deep-space clouds
|
|
|
|
|
this._canvasTex('bg_nebula', (ctx, w, h) => {
|
|
|
|
|
this._vgrad(ctx, w, h, [[0, '#05010f'], [1, '#0a0420']]);
|
|
|
|
|
ctx.globalCompositeOperation = 'screen';
|
|
|
|
|
this._glow(ctx, w * 0.28, h * 0.28, w * 0.75, 'rgba(236,72,153,0.85)');
|
|
|
|
|
this._glow(ctx, w * 0.80, h * 0.46, w * 0.70, 'rgba(34,211,238,0.6)');
|
|
|
|
|
this._glow(ctx, w * 0.50, h * 0.72, w * 0.85, 'rgba(168,85,247,0.8)');
|
|
|
|
|
this._glow(ctx, w * 0.15, h * 0.9, w * 0.5, 'rgba(245,158,11,0.35)');
|
|
|
|
|
ctx.globalCompositeOperation = 'source-over';
|
|
|
|
|
this._stars(ctx, w, h, 220, ['#ffffff', '#d8b4fe', '#a5f3fc', '#fbcfe8']);
|
|
|
|
|
// a few bright stars with halo
|
|
|
|
|
ctx.globalCompositeOperation = 'screen';
|
|
|
|
|
for (let i = 0; i < 7; i++) {
|
|
|
|
|
const x = Math.random() * w; const y = Math.random() * h;
|
|
|
|
|
this._glow(ctx, x, y, 22, 'rgba(255,255,255,0.5)');
|
|
|
|
|
const S = 256;
|
|
|
|
|
|
|
|
|
|
// 1) Grid — Monad blockchain grid (default look, larger so kept as bg_grid)
|
|
|
|
|
this._tex('bg_grid', S, S, (g) => {
|
|
|
|
|
g.fillStyle(0x0f001f, 1);
|
|
|
|
|
g.fillRect(0, 0, S, S);
|
|
|
|
|
g.lineStyle(1, 0x2e0059, 0.4);
|
|
|
|
|
for (let i = 0; i <= S; i += 32) {
|
|
|
|
|
g.moveTo(i, 0); g.lineTo(i, S);
|
|
|
|
|
g.moveTo(0, i); g.lineTo(S, i);
|
|
|
|
|
}
|
|
|
|
|
g.strokePath();
|
|
|
|
|
// node dots at some intersections (deterministic -> tiles)
|
|
|
|
|
g.fillStyle(0x7c3aed, 0.35);
|
|
|
|
|
for (let x = 0; x <= S; x += 64) {
|
|
|
|
|
for (let y = 0; y <= S; y += 64) {
|
|
|
|
|
if (((x + y) / 64) % 2 === 0) g.fillCircle(x, y, 2);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
ctx.globalCompositeOperation = 'source-over';
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 2) Aurora — northern lights over a night sky
|
|
|
|
|
this._canvasTex('bg_aurora', (ctx, w, h) => {
|
|
|
|
|
this._vgrad(ctx, w, h, [[0, '#02030f'], [0.5, '#061a2e'], [1, '#01020a']]);
|
|
|
|
|
this._stars(ctx, w, h, 130, ['#ffffff', '#bfdbfe'], 1.4, 0.55);
|
|
|
|
|
ctx.globalCompositeOperation = 'lighter';
|
|
|
|
|
this._ribbon(ctx, w * 0.45, h * 0.32, w * 0.9, h * 0.16, -0.35, 'rgba(34,245,170,0.45)');
|
|
|
|
|
this._ribbon(ctx, w * 0.55, h * 0.45, w * 0.85, h * 0.14, 0.28, 'rgba(34,211,238,0.4)');
|
|
|
|
|
this._ribbon(ctx, w * 0.5, h * 0.6, w * 1.0, h * 0.18, -0.18, 'rgba(168,85,247,0.38)');
|
|
|
|
|
this._ribbon(ctx, w * 0.35, h * 0.52, w * 0.5, h * 0.1, 0.5, 'rgba(132,255,214,0.35)');
|
|
|
|
|
ctx.globalCompositeOperation = 'source-over';
|
|
|
|
|
// 2) Hex Nodes — honeycomb
|
|
|
|
|
this._tex('bg_hex', S, S, (g) => {
|
|
|
|
|
g.fillStyle(0x0d0420, 1);
|
|
|
|
|
g.fillRect(0, 0, S, S);
|
|
|
|
|
const r = 22;
|
|
|
|
|
const w = Math.sqrt(3) * r;
|
|
|
|
|
const vSpace = 1.5 * r;
|
|
|
|
|
g.lineStyle(1.5, 0x6d28d9, 0.30);
|
|
|
|
|
for (let row = -1, ry = 0; ry < S + r; row++, ry = row * vSpace) {
|
|
|
|
|
const offset = (row % 2 === 0) ? 0 : w / 2;
|
|
|
|
|
for (let cx = -w; cx < S + w; cx += w) {
|
|
|
|
|
this._hex(g, cx + offset, ry, r);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// accent glow nodes
|
|
|
|
|
g.fillStyle(0xa855f7, 0.25);
|
|
|
|
|
for (let row = 0, ry = 0; ry < S; row += 2, ry = row * vSpace) {
|
|
|
|
|
for (let cx = 0; cx < S; cx += w * 2) g.fillCircle(cx, ry, 2.5);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 3) Sunset — synthwave sky with a glowing sun + horizon grid
|
|
|
|
|
this._canvasTex('bg_sunset', (ctx, w, h) => {
|
|
|
|
|
this._vgrad(ctx, w, h, [
|
|
|
|
|
[0, '#241047'], [0.4, '#6d28d9'], [0.58, '#db2777'],
|
|
|
|
|
[0.74, '#fb6f4d'], [1, '#ffd27a'],
|
|
|
|
|
]);
|
|
|
|
|
const sunY = h * 0.52;
|
|
|
|
|
ctx.globalCompositeOperation = 'lighter';
|
|
|
|
|
this._glow(ctx, w * 0.5, sunY, w * 0.6, 'rgba(255,225,140,0.9)');
|
|
|
|
|
this._glow(ctx, w * 0.5, sunY, w * 0.35, 'rgba(255,120,90,0.7)');
|
|
|
|
|
ctx.globalCompositeOperation = 'source-over';
|
|
|
|
|
// sun disc
|
|
|
|
|
const sun = ctx.createLinearGradient(0, sunY - 90, 0, sunY + 90);
|
|
|
|
|
sun.addColorStop(0, '#fff1a8');
|
|
|
|
|
sun.addColorStop(1, '#ff5e8a');
|
|
|
|
|
ctx.fillStyle = sun;
|
|
|
|
|
ctx.beginPath();
|
|
|
|
|
ctx.arc(w * 0.5, sunY, 90, 0, Math.PI * 2);
|
|
|
|
|
ctx.fill();
|
|
|
|
|
// horizon perspective grid
|
|
|
|
|
ctx.strokeStyle = 'rgba(255,45,149,0.55)';
|
|
|
|
|
ctx.lineWidth = 1.5;
|
|
|
|
|
const horizon = h * 0.62;
|
|
|
|
|
for (let i = 1; i <= 9; i++) {
|
|
|
|
|
const y = horizon + Math.pow(i / 9, 2) * (h - horizon);
|
|
|
|
|
ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(w, y); ctx.stroke();
|
|
|
|
|
// 3) Starfield — deep space
|
|
|
|
|
this._tex('bg_starfield', S, S, (g) => {
|
|
|
|
|
g.fillStyle(0x070016, 1);
|
|
|
|
|
g.fillRect(0, 0, S, S);
|
|
|
|
|
// faint distant dust
|
|
|
|
|
const dust = [0x1a0b3a, 0x12082b];
|
|
|
|
|
for (let i = 0; i < 26; i++) {
|
|
|
|
|
g.fillStyle(Phaser.Utils.Array.GetRandom(dust), 0.5);
|
|
|
|
|
g.fillCircle(Phaser.Math.Between(0, S), Phaser.Math.Between(0, S), Phaser.Math.Between(20, 55));
|
|
|
|
|
}
|
|
|
|
|
ctx.strokeStyle = 'rgba(0,229,255,0.4)';
|
|
|
|
|
for (let i = -7; i <= 7; i++) {
|
|
|
|
|
ctx.beginPath();
|
|
|
|
|
ctx.moveTo(w / 2 + i * 24, horizon);
|
|
|
|
|
ctx.lineTo(w / 2 + i * 120, h);
|
|
|
|
|
ctx.stroke();
|
|
|
|
|
// stars
|
|
|
|
|
const cols = [0xffffff, 0xd8b4fe, 0x93c5fd, 0xa855f7];
|
|
|
|
|
for (let i = 0; i < 150; i++) {
|
|
|
|
|
g.fillStyle(Phaser.Utils.Array.GetRandom(cols), Phaser.Math.FloatBetween(0.35, 1));
|
|
|
|
|
g.fillCircle(Phaser.Math.Between(0, S), Phaser.Math.Between(0, S), Phaser.Math.FloatBetween(0.6, 1.8));
|
|
|
|
|
}
|
|
|
|
|
this._stars(ctx, w, h, 50, ['#ffffff', '#ffe1c0'], 1.2, 0.4);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 4) Ocean — sunlit depths
|
|
|
|
|
this._canvasTex('bg_ocean', (ctx, w, h) => {
|
|
|
|
|
this._vgrad(ctx, w, h, [
|
|
|
|
|
[0, '#9fe9ff'], [0.22, '#34a7d8'], [0.5, '#0c5b8f'],
|
|
|
|
|
[0.78, '#06304f'], [1, '#021526'],
|
|
|
|
|
]);
|
|
|
|
|
ctx.globalCompositeOperation = 'lighter';
|
|
|
|
|
// caustic light beams from the surface
|
|
|
|
|
// a few bright glows
|
|
|
|
|
for (let i = 0; i < 5; i++) {
|
|
|
|
|
const x = w * (0.15 + i * 0.18);
|
|
|
|
|
this._ribbon(ctx, x, h * 0.18, w * 0.07, h * 0.45, 0.12 * (i % 2 ? 1 : -1), 'rgba(200,245,255,0.18)');
|
|
|
|
|
const x = Phaser.Math.Between(0, S); const y = Phaser.Math.Between(0, S);
|
|
|
|
|
g.fillStyle(0xffffff, 0.18); g.fillCircle(x, y, 6);
|
|
|
|
|
g.fillStyle(0xffffff, 0.9); g.fillCircle(x, y, 1.6);
|
|
|
|
|
}
|
|
|
|
|
ctx.globalCompositeOperation = 'source-over';
|
|
|
|
|
// bubbles
|
|
|
|
|
for (let i = 0; i < 40; i++) {
|
|
|
|
|
const x = Math.random() * w; const y = h * (0.35 + Math.random() * 0.65);
|
|
|
|
|
ctx.globalAlpha = 0.1 + Math.random() * 0.2;
|
|
|
|
|
ctx.fillStyle = '#ffffff';
|
|
|
|
|
ctx.beginPath(); ctx.arc(x, y, Math.random() * 3 + 1, 0, Math.PI * 2); ctx.fill();
|
|
|
|
|
}
|
|
|
|
|
ctx.globalAlpha = 1;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 5) Dreamscape — soft pastel bokeh
|
|
|
|
|
this._canvasTex('bg_dream', (ctx, w, h) => {
|
|
|
|
|
this._vgrad(ctx, w, h, [
|
|
|
|
|
[0, '#cdbdff'], [0.35, '#f2abfc'], [0.68, '#fbcfe8'], [1, '#ffd9b3'],
|
|
|
|
|
]);
|
|
|
|
|
ctx.globalCompositeOperation = 'lighter';
|
|
|
|
|
const orbs = ['rgba(255,255,255,0.35)', 'rgba(244,114,182,0.25)', 'rgba(167,139,250,0.25)', 'rgba(125,211,252,0.22)'];
|
|
|
|
|
for (let i = 0; i < 16; i++) {
|
|
|
|
|
const x = Math.random() * w; const y = Math.random() * h;
|
|
|
|
|
const r = 30 + Math.random() * 90;
|
|
|
|
|
this._glow(ctx, x, y, r, orbs[(Math.random() * orbs.length) | 0]);
|
|
|
|
|
// 4) Synthwave — neon horizontal grid
|
|
|
|
|
this._tex('bg_synthwave', S, S, (g) => {
|
|
|
|
|
g.fillStyle(0x0a0118, 1);
|
|
|
|
|
g.fillRect(0, 0, S, S);
|
|
|
|
|
// vertical faint lines
|
|
|
|
|
g.lineStyle(1, 0x3b0764, 0.5);
|
|
|
|
|
for (let x = 0; x <= S; x += 32) { g.moveTo(x, 0); g.lineTo(x, S); }
|
|
|
|
|
g.strokePath();
|
|
|
|
|
// neon horizontal lines, alternating magenta/cyan
|
|
|
|
|
for (let y = 0; y <= S; y += 28) {
|
|
|
|
|
const cyan = (y / 28) % 2 === 0;
|
|
|
|
|
const col = cyan ? 0x00e5ff : 0xff2d95;
|
|
|
|
|
g.lineStyle(3, col, 0.10); g.beginPath(); g.moveTo(0, y); g.lineTo(S, y); g.strokePath();
|
|
|
|
|
g.lineStyle(1, col, 0.55); g.beginPath(); g.moveTo(0, y); g.lineTo(S, y); g.strokePath();
|
|
|
|
|
}
|
|
|
|
|
ctx.globalCompositeOperation = 'source-over';
|
|
|
|
|
this._stars(ctx, w, h, 40, ['#ffffff']);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 6) Monad — signature purple cosmos
|
|
|
|
|
this._canvasTex('bg_monad', (ctx, w, h) => {
|
|
|
|
|
this._vgrad(ctx, w, h, [[0, '#1a0533'], [0.5, '#2e0a52'], [1, '#0d0020']]);
|
|
|
|
|
ctx.globalCompositeOperation = 'screen';
|
|
|
|
|
this._glow(ctx, w * 0.3, h * 0.24, w * 0.8, 'rgba(192,38,211,0.7)');
|
|
|
|
|
this._glow(ctx, w * 0.78, h * 0.58, w * 0.75, 'rgba(124,58,237,0.8)');
|
|
|
|
|
this._glow(ctx, w * 0.5, h * 0.92, w * 0.7, 'rgba(219,39,119,0.5)');
|
|
|
|
|
this._glow(ctx, w * 0.5, h * 0.5, w * 0.55, 'rgba(168,85,247,0.25)');
|
|
|
|
|
ctx.globalCompositeOperation = 'source-over';
|
|
|
|
|
this._stars(ctx, w, h, 180, ['#ffffff', '#d8b4fe', '#f0abfc']);
|
|
|
|
|
// 5) Circuit — PCB traces
|
|
|
|
|
this._tex('bg_circuit', S, S, (g) => {
|
|
|
|
|
g.fillStyle(0x04120e, 1);
|
|
|
|
|
g.fillRect(0, 0, S, S);
|
|
|
|
|
g.lineStyle(1, 0x0f766e, 0.45);
|
|
|
|
|
for (let i = 0; i <= S; i += 32) { g.moveTo(i, 0); g.lineTo(i, S); g.moveTo(0, i); g.lineTo(S, i); }
|
|
|
|
|
g.strokePath();
|
|
|
|
|
// brighter trace accents (deterministic checker -> tiles)
|
|
|
|
|
g.lineStyle(2, 0x10b981, 0.5);
|
|
|
|
|
for (let x = 0; x < S; x += 32) {
|
|
|
|
|
for (let y = 0; y < S; y += 32) {
|
|
|
|
|
if (((x / 32) + (y / 32)) % 3 === 0) { g.beginPath(); g.moveTo(x, y); g.lineTo(x + 32, y); g.strokePath(); }
|
|
|
|
|
if (((x / 32) + (y / 32)) % 4 === 0) { g.beginPath(); g.moveTo(x, y); g.lineTo(x, y + 32); g.strokePath(); }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// solder nodes
|
|
|
|
|
g.fillStyle(0x34d399, 0.8);
|
|
|
|
|
for (let x = 0; x <= S; x += 32) {
|
|
|
|
|
for (let y = 0; y <= S; y += 32) {
|
|
|
|
|
if (((x + y) / 32) % 2 === 0) g.fillCircle(x, y, 2);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 6) Void — minimal dark
|
|
|
|
|
this._tex('bg_void', S, S, (g) => {
|
|
|
|
|
g.fillStyle(0x060010, 1);
|
|
|
|
|
g.fillRect(0, 0, S, S);
|
|
|
|
|
g.fillStyle(0x1b1036, 0.6);
|
|
|
|
|
for (let x = 24; x < S; x += 48) {
|
|
|
|
|
for (let y = 24; y < S; y += 48) g.fillCircle(x, y, 1);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_hex(g, cx, cy, r) {
|
|
|
|
|
g.beginPath();
|
|
|
|
|
for (let i = 0; i < 6; i++) {
|
|
|
|
|
const a = Math.PI / 180 * (60 * i - 90);
|
|
|
|
|
const px = cx + r * Math.cos(a);
|
|
|
|
|
const py = cy + r * Math.sin(a);
|
|
|
|
|
if (i === 0) g.moveTo(px, py); else g.lineTo(px, py);
|
|
|
|
|
}
|
|
|
|
|
g.closePath();
|
|
|
|
|
g.strokePath();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
createParticleTextures() {
|
|
|
|
|
|