refactor DotDude: standardize grid to 10x15 and overhaul movement logic
- Reduce playfield from 20x15 to 10x15 tiles (60x60px) and center in canvas - Replace raw pixel math with tile-based coordinate helpers (`_px`, `_py`, `_col`, `_row`) - Rewrite player movement to use hard-snap alignment and tile-center turning thresholds - Switch ghosts to tile-target-based navigation with `_pickNextTarget` and distance checks - Update layout to a symmetric 10-column maze with ghost house and corridors - Redraw HUD and background for the new layout dimensions
This commit is contained in:
parent
b6002fed00
commit
2aabbb158a
|
|
@ -1,23 +1,28 @@
|
|||
const LAYOUT = [
|
||||
[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],
|
||||
[1,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,1],
|
||||
[1,0,1,1,0,1,0,1,1,0,0,1,1,0,1,0,1,1,0,1],
|
||||
[1,0,1,1,0,0,0,1,1,0,0,1,1,0,0,0,1,1,0,1],
|
||||
[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
|
||||
[1,0,1,1,0,1,1,0,1,1,1,1,0,1,1,0,1,1,0,1],
|
||||
[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
|
||||
[1,0,1,1,0,1,1,0,1,1,1,1,0,1,1,0,1,1,0,1],
|
||||
[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
|
||||
[1,0,1,1,0,1,1,0,1,1,1,1,0,1,1,0,1,1,0,1],
|
||||
[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
|
||||
[1,0,1,1,0,1,1,0,1,1,1,1,0,1,1,0,1,1,0,1],
|
||||
[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
|
||||
[1,0,1,1,0,0,0,1,1,0,0,1,1,0,0,0,1,1,0,1],
|
||||
[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],
|
||||
];
|
||||
|
||||
// 10 cols × 15 rows, 60 × 60 px tiles
|
||||
// Playfield is 600 × 900 px, centered at x = 500..1100 in the 1600 × 900 canvas
|
||||
const COLS = 10;
|
||||
const ROWS = 15;
|
||||
const COLS = 20;
|
||||
const TW = 60;
|
||||
const TH = 60;
|
||||
const OX = (1600 - COLS * TW) / 2; // left edge of maze = 500
|
||||
|
||||
const LAYOUT = [
|
||||
[1,1,1,1,1,1,1,1,1,1], // 0 top wall
|
||||
[1,0,0,0,0,0,0,0,0,1], // 1 open corridor
|
||||
[1,0,1,1,0,0,1,1,0,1], // 2 block rooms
|
||||
[1,0,1,1,0,0,1,1,0,1], // 3 (thick walls, same as row 2)
|
||||
[1,0,0,0,0,0,0,0,0,1], // 4 open corridor
|
||||
[1,0,1,0,1,1,0,1,0,1], // 5 pre-ghost-house
|
||||
[1,0,1,0,0,0,0,1,0,1], // 6 ghost house (open centre)
|
||||
[1,0,1,0,1,1,0,1,0,1], // 7 post-ghost-house (mirror of 5)
|
||||
[1,0,0,0,0,0,0,0,0,1], // 8 open corridor
|
||||
[1,0,1,1,0,0,1,1,0,1], // 9 block rooms (mirror of 2)
|
||||
[1,0,1,1,0,0,1,1,0,1], // 10 (mirror of 3)
|
||||
[1,0,0,0,0,0,0,0,0,1], // 11 open corridor
|
||||
[1,1,0,1,0,0,1,0,1,1], // 12 narrow passage
|
||||
[1,0,0,0,0,0,0,0,0,1], // 13 open corridor (player spawn)
|
||||
[1,1,1,1,1,1,1,1,1,1], // 14 bottom wall
|
||||
];
|
||||
|
||||
export default class DotDude extends Phaser.Scene {
|
||||
constructor() { super({ key: 'DotDude' }); }
|
||||
|
|
@ -33,17 +38,16 @@ export default class DotDude extends Phaser.Scene {
|
|||
|
||||
create() {
|
||||
this.cfg = this.cache.json.get('ddConfig');
|
||||
this.TW = 80;
|
||||
this.TH = 60;
|
||||
|
||||
// Dots grid: copy of open cells
|
||||
this.dots = LAYOUT.map(row => row.map(cell => cell === 0 ? 1 : 0));
|
||||
this.dots = LAYOUT.map(row => row.map(cell => (cell === 0 ? 1 : 0)));
|
||||
this.totalDots = this.dots.flat().filter(d => d === 1).length;
|
||||
this.dotsEaten = 0;
|
||||
|
||||
this._initPlayer();
|
||||
this._initGhosts();
|
||||
|
||||
// Background must be added first so it sits below all other layers
|
||||
this._drawBackground();
|
||||
this.mazeGfx = this.add.graphics();
|
||||
this.mainGfx = this.add.graphics();
|
||||
this.gameActive = true;
|
||||
|
|
@ -55,128 +59,119 @@ export default class DotDude extends Phaser.Scene {
|
|||
this._setupInput();
|
||||
}
|
||||
|
||||
_initPlayer() {
|
||||
const startCol = 10, startRow = 12;
|
||||
this.player = {
|
||||
x: (startCol + 0.5) * 80,
|
||||
y: (startRow + 0.5) * 60,
|
||||
dir: { dx: 0, dy: 0 },
|
||||
wantDir: { dx: 1, dy: 0 },
|
||||
};
|
||||
}
|
||||
// ── helpers ─────────────────────────────────────────────────────────────
|
||||
|
||||
_initGhosts() {
|
||||
const ghostCols = [7, 10, 13];
|
||||
const ghostRow = 6;
|
||||
const colors = [0xff4444, 0xff88ff, 0x44ffff];
|
||||
this.ghosts = ghostCols.map((col, i) => ({
|
||||
x: (col + 0.5) * 80,
|
||||
y: (ghostRow + 0.5) * 60,
|
||||
dir: { dx: i === 1 ? -1 : 1, dy: 0 },
|
||||
color: colors[i],
|
||||
active: false,
|
||||
}));
|
||||
this.cfg.ghostStartDelay.forEach((delay, i) => {
|
||||
this.time.delayedCall(delay, () => {
|
||||
if (this.gameActive && this.ghosts[i]) this.ghosts[i].active = true;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
_buildHUD() {
|
||||
this.livesText = this.add.text(800, 6, '♥ '.repeat(this.globalLives).trim(), {
|
||||
fontSize: '20px', fontFamily: 'monospace', color: '#ff4444',
|
||||
}).setOrigin(0.5, 0).setDepth(10);
|
||||
this.dotsText = this.add.text(1580, 6, `DOTS: ${this.totalDots}`, {
|
||||
fontSize: '20px', fontFamily: 'monospace', color: '#ffff00',
|
||||
}).setOrigin(1, 0).setDepth(10);
|
||||
this.add.text(20, 6, 'DOT DUDE', {
|
||||
fontSize: '20px', fontFamily: 'monospace', color: '#ffaa00',
|
||||
}).setDepth(10);
|
||||
}
|
||||
|
||||
_setupInput() {
|
||||
this.cursors = this.input.keyboard.createCursorKeys();
|
||||
}
|
||||
_px(col) { return OX + (col + 0.5) * TW; } // tile col → pixel x centre
|
||||
_py(row) { return (row + 0.5) * TH; } // tile row → pixel y centre
|
||||
_col(px) { return Math.floor((px - OX) / TW); }
|
||||
_row(py) { return Math.floor(py / TH); }
|
||||
|
||||
_wallAt(col, row) {
|
||||
if (col < 0 || col >= COLS || row < 0 || row >= ROWS) return true;
|
||||
return LAYOUT[row][col] === 1;
|
||||
}
|
||||
|
||||
_canMoveTo(px, py) {
|
||||
const r = 15;
|
||||
const tileOf = (x, y) => ({
|
||||
col: Math.floor(x / this.TW),
|
||||
row: Math.floor(y / this.TH),
|
||||
});
|
||||
const corners = [
|
||||
tileOf(px - r + 1, py - r + 1),
|
||||
tileOf(px + r - 1, py - r + 1),
|
||||
tileOf(px - r + 1, py + r - 1),
|
||||
tileOf(px + r - 1, py + r - 1),
|
||||
];
|
||||
return corners.every(({ col, row }) => !this._wallAt(col, row));
|
||||
_canMoveTo(px, py, r) {
|
||||
return [
|
||||
[px - r + 1, py - r + 1],
|
||||
[px + r - 1, py - r + 1],
|
||||
[px - r + 1, py + r - 1],
|
||||
[px + r - 1, py + r - 1],
|
||||
].every(([x, y]) => !this._wallAt(this._col(x), this._row(y)));
|
||||
}
|
||||
|
||||
_canGhostMoveTo(px, py) {
|
||||
const r = 13;
|
||||
const tileOf = (x, y) => ({
|
||||
col: Math.floor(x / this.TW),
|
||||
row: Math.floor(y / this.TH),
|
||||
});
|
||||
const corners = [
|
||||
tileOf(px - r + 1, py - r + 1),
|
||||
tileOf(px + r - 1, py - r + 1),
|
||||
tileOf(px - r + 1, py + r - 1),
|
||||
tileOf(px + r - 1, py + r - 1),
|
||||
];
|
||||
return corners.every(({ col, row }) => !this._wallAt(col, row));
|
||||
// ── init ─────────────────────────────────────────────────────────────────
|
||||
|
||||
_initPlayer() {
|
||||
this.player = {
|
||||
x: this._px(4), y: this._py(13),
|
||||
dir: { dx: 0, dy: 0 },
|
||||
wantDir: { dx: 1, dy: 0 },
|
||||
};
|
||||
}
|
||||
|
||||
_initGhosts() {
|
||||
const defs = [
|
||||
{ col: 3, row: 6, dir: { dx: -1, dy: 0 }, color: 0xff4444 },
|
||||
{ col: 5, row: 6, dir: { dx: 1, dy: 0 }, color: 0xff88ff },
|
||||
{ col: 6, row: 6, dir: { dx: 1, dy: 0 }, color: 0x44ffff },
|
||||
];
|
||||
this.ghosts = defs.map(g => ({
|
||||
x: this._px(g.col), y: this._py(g.row),
|
||||
targetX: this._px(g.col), targetY: this._py(g.row),
|
||||
dir: g.dir, color: g.color, active: false,
|
||||
}));
|
||||
this.cfg.ghostStartDelay.forEach((ms, i) => {
|
||||
this.time.delayedCall(ms, () => {
|
||||
if (!this.gameActive || !this.ghosts[i]) return;
|
||||
const ghost = this.ghosts[i];
|
||||
ghost.active = true;
|
||||
this._pickNextTarget(ghost);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
_pickNextTarget(ghost) {
|
||||
const col = this._col(ghost.x);
|
||||
const row = this._row(ghost.y);
|
||||
const dir = this._chooseGhostDir(ghost, col, row, false);
|
||||
ghost.dir = dir;
|
||||
if (dir.dx !== 0 || dir.dy !== 0) {
|
||||
ghost.targetX = this._px(col + dir.dx);
|
||||
ghost.targetY = this._py(row + dir.dy);
|
||||
}
|
||||
}
|
||||
|
||||
_buildHUD() {
|
||||
// Left panel
|
||||
this.add.text(250, 80, 'DOT DUDE', {
|
||||
fontSize: '30px', fontFamily: 'monospace', color: '#ffaa00',
|
||||
}).setOrigin(0.5).setDepth(10);
|
||||
|
||||
this.livesText = this.add.text(250, 160, '♥ '.repeat(this.globalLives).trim(), {
|
||||
fontSize: '30px', fontFamily: 'monospace', color: '#ff4444',
|
||||
}).setOrigin(0.5).setDepth(10);
|
||||
|
||||
// Right panel
|
||||
this.add.text(1350, 80, 'DOTS', {
|
||||
fontSize: '26px', fontFamily: 'monospace', color: '#aaaaaa',
|
||||
}).setOrigin(0.5).setDepth(10);
|
||||
|
||||
this.dotsText = this.add.text(1350, 140, `${this.totalDots}`, {
|
||||
fontSize: '48px', fontFamily: 'monospace', color: '#ffff00',
|
||||
}).setOrigin(0.5).setDepth(10);
|
||||
}
|
||||
|
||||
_setupInput() {
|
||||
this.cursors = this.input.keyboard.createCursorKeys();
|
||||
}
|
||||
|
||||
// ── update ───────────────────────────────────────────────────────────────
|
||||
|
||||
update(time, delta) {
|
||||
if (!this.gameActive) return;
|
||||
const dt = delta / 1000;
|
||||
|
||||
// Input
|
||||
// Input → buffer wantDir
|
||||
if (this.cursors.left.isDown) this.player.wantDir = { dx: -1, dy: 0 };
|
||||
else if (this.cursors.right.isDown) this.player.wantDir = { dx: 1, dy: 0 };
|
||||
else if (this.cursors.up.isDown) this.player.wantDir = { dx: 0, dy: -1 };
|
||||
else if (this.cursors.down.isDown) this.player.wantDir = { dx: 0, dy: 1 };
|
||||
|
||||
const speed = this.cfg.playerSpeed;
|
||||
const { wantDir, dir } = this.player;
|
||||
this._movePlayer(dt);
|
||||
|
||||
// Try wanted direction
|
||||
const wx = this.player.x + wantDir.dx * speed * dt;
|
||||
const wy = this.player.y + wantDir.dy * speed * dt;
|
||||
if (this._canMoveTo(wx, wy)) {
|
||||
this.player.dir = { ...wantDir };
|
||||
this.player.x = wx;
|
||||
this.player.y = wy;
|
||||
} else {
|
||||
// Keep current direction
|
||||
const dx2 = this.player.x + dir.dx * speed * dt;
|
||||
const dy2 = this.player.y + dir.dy * speed * dt;
|
||||
if (this._canMoveTo(dx2, dy2)) {
|
||||
this.player.x = dx2;
|
||||
this.player.y = dy2;
|
||||
}
|
||||
}
|
||||
|
||||
// Eat dot
|
||||
const col = Math.floor(this.player.x / this.TW);
|
||||
const row = Math.floor(this.player.y / this.TH);
|
||||
if (row >= 0 && row < ROWS && col >= 0 && col < COLS && this.dots[row][col] === 1) {
|
||||
// Eat dot at current tile
|
||||
const col = this._col(this.player.x);
|
||||
const row = this._row(this.player.y);
|
||||
if (col >= 0 && col < COLS && row >= 0 && row < ROWS && this.dots[row][col] === 1) {
|
||||
this.dots[row][col] = 0;
|
||||
this.dotsEaten++;
|
||||
this.dotsText.setText(`DOTS: ${this.totalDots - this.dotsEaten}`);
|
||||
this.dotsText.setText(`${this.totalDots - this.dotsEaten}`);
|
||||
}
|
||||
|
||||
// Update ghosts
|
||||
// Ghosts
|
||||
this.ghosts.forEach(g => this._updateGhost(g, dt));
|
||||
|
||||
// Ghost collision
|
||||
for (const g of this.ghosts) {
|
||||
if (g.active && Math.hypot(g.x - this.player.x, g.y - this.player.y) < 26) {
|
||||
this._endGame(false);
|
||||
|
|
@ -184,11 +179,7 @@ export default class DotDude extends Phaser.Scene {
|
|||
}
|
||||
}
|
||||
|
||||
// Win check
|
||||
if (this.dotsEaten >= this.totalDots) {
|
||||
this._endGame(true);
|
||||
return;
|
||||
}
|
||||
if (this.dotsEaten >= this.totalDots) { this._endGame(true); return; }
|
||||
|
||||
// Mouth animation
|
||||
this.mouthAngle += this.mouthDir * 3 * dt;
|
||||
|
|
@ -198,68 +189,133 @@ export default class DotDude extends Phaser.Scene {
|
|||
this._drawDynamic();
|
||||
}
|
||||
|
||||
_movePlayer(dt) {
|
||||
const p = this.player;
|
||||
const move = this.cfg.playerSpeed * dt;
|
||||
|
||||
// Tile the player's centre currently occupies
|
||||
const col = this._col(p.x);
|
||||
const row = this._row(p.y);
|
||||
const cx = this._px(col);
|
||||
const cy = this._py(row);
|
||||
|
||||
// Allow a direction change from anywhere within the current tile (half-tile threshold).
|
||||
// This is forgiving: no pixel-perfect timing needed.
|
||||
const { dx: wdx, dy: wdy } = p.wantDir;
|
||||
if (Math.abs(p.x - cx) < TW / 2 && Math.abs(p.y - cy) < TH / 2) {
|
||||
if (!this._wallAt(col + wdx, row + wdy)) {
|
||||
p.dir = { dx: wdx, dy: wdy };
|
||||
}
|
||||
}
|
||||
|
||||
const { dx, dy } = p.dir;
|
||||
|
||||
if (dx !== 0) {
|
||||
// ── Horizontal movement ──────────────────────────────────────────
|
||||
// Hard-snap Y to the row's lane centre every frame.
|
||||
p.y = cy;
|
||||
|
||||
const nx = p.x + dx * move;
|
||||
const nCol = this._col(nx);
|
||||
|
||||
if (nCol !== col && this._wallAt(nCol, row)) {
|
||||
// About to enter a wall column: park at the near edge of the open tile.
|
||||
p.x = dx > 0 ? OX + (col + 1) * TW - 1 : OX + col * TW + 1;
|
||||
p.dir = { dx: 0, dy: 0 };
|
||||
} else {
|
||||
p.x = nx;
|
||||
}
|
||||
} else if (dy !== 0) {
|
||||
// ── Vertical movement ────────────────────────────────────────────
|
||||
// Hard-snap X to the column's lane centre every frame.
|
||||
p.x = cx;
|
||||
|
||||
const ny = p.y + dy * move;
|
||||
const nRow = this._row(ny);
|
||||
|
||||
if (nRow !== row && this._wallAt(col, nRow)) {
|
||||
p.y = dy > 0 ? (row + 1) * TH - 1 : row * TH + 1;
|
||||
p.dir = { dx: 0, dy: 0 };
|
||||
} else {
|
||||
p.y = ny;
|
||||
}
|
||||
} else {
|
||||
// Stopped: snap to tile centre so the turn threshold works correctly.
|
||||
p.x = cx;
|
||||
p.y = cy;
|
||||
}
|
||||
}
|
||||
|
||||
_updateGhost(ghost, dt) {
|
||||
if (!ghost.active) return;
|
||||
const speed = this.cfg.ghostSpeed;
|
||||
const { dir } = ghost;
|
||||
const move = this.cfg.ghostSpeed * dt;
|
||||
|
||||
const nx = ghost.x + dir.dx * speed * dt;
|
||||
const ny = ghost.y + dir.dy * speed * dt;
|
||||
const tdx = ghost.targetX - ghost.x;
|
||||
const tdy = ghost.targetY - ghost.y;
|
||||
const dist = Math.hypot(tdx, tdy);
|
||||
|
||||
if (this._canGhostMoveTo(nx, ny)) {
|
||||
ghost.x = nx;
|
||||
ghost.y = ny;
|
||||
}
|
||||
|
||||
// Near tile center: rechoose direction
|
||||
const col = Math.floor(ghost.x / this.TW);
|
||||
const row = Math.floor(ghost.y / this.TH);
|
||||
const cx = (col + 0.5) * this.TW;
|
||||
const cy = (row + 0.5) * this.TH;
|
||||
const snapD = speed * dt * 4;
|
||||
|
||||
if (Math.abs(ghost.x - cx) < snapD && Math.abs(ghost.y - cy) < snapD) {
|
||||
ghost.x = cx;
|
||||
ghost.y = cy;
|
||||
ghost.dir = this._chooseGhostDir(ghost, col, row);
|
||||
if (dist <= move) {
|
||||
// Arrived at the target tile centre: snap and choose next tile.
|
||||
ghost.x = ghost.targetX;
|
||||
ghost.y = ghost.targetY;
|
||||
this._pickNextTarget(ghost);
|
||||
} else {
|
||||
ghost.x += (tdx / dist) * move;
|
||||
ghost.y += (tdy / dist) * move;
|
||||
}
|
||||
}
|
||||
|
||||
_chooseGhostDir(ghost, col, row) {
|
||||
// allowReverse: true when the ghost is wall-blocked and needs an escape route.
|
||||
_chooseGhostDir(ghost, col, row, allowReverse) {
|
||||
const dirs = [{ dx: 1, dy: 0 }, { dx: -1, dy: 0 }, { dx: 0, dy: 1 }, { dx: 0, dy: -1 }];
|
||||
const reverse = { dx: -ghost.dir.dx, dy: -ghost.dir.dy };
|
||||
const pCol = Math.floor(this.player.x / this.TW);
|
||||
const pRow = Math.floor(this.player.y / this.TH);
|
||||
const rev = { dx: -ghost.dir.dx, dy: -ghost.dir.dy };
|
||||
const pc = this._col(this.player.x);
|
||||
const pr = this._row(this.player.y);
|
||||
|
||||
let best = null, bestScore = Infinity;
|
||||
for (const d of dirs) {
|
||||
if (d.dx === reverse.dx && d.dy === reverse.dy) continue;
|
||||
if (!allowReverse && d.dx === rev.dx && d.dy === rev.dy) continue;
|
||||
const nc = col + d.dx, nr = row + d.dy;
|
||||
if (this._wallAt(nc, nr)) continue;
|
||||
const score = Math.abs(nc - pCol) + Math.abs(nr - pRow);
|
||||
const score = Math.abs(nc - pc) + Math.abs(nr - pr);
|
||||
if (score < bestScore) { bestScore = score; best = d; }
|
||||
}
|
||||
return best || reverse;
|
||||
// Last resort: try reverse even if not explicitly allowed
|
||||
if (!best && !this._wallAt(col + rev.dx, row + rev.dy)) best = rev;
|
||||
return best || { dx: 0, dy: 0 };
|
||||
}
|
||||
|
||||
// ── drawing ──────────────────────────────────────────────────────────────
|
||||
|
||||
_drawBackground() {
|
||||
const bg = this.add.graphics();
|
||||
// Full canvas
|
||||
bg.fillStyle(0x000011);
|
||||
bg.fillRect(0, 0, 1600, 900);
|
||||
// Side panels slightly lighter
|
||||
bg.fillStyle(0x08081a);
|
||||
bg.fillRect(0, 0, OX, 900);
|
||||
bg.fillRect(OX + COLS * TW, 0, OX, 900);
|
||||
// Subtle panel separators
|
||||
bg.lineStyle(1, 0x222244, 0.8);
|
||||
bg.lineBetween(OX, 0, OX, 900);
|
||||
bg.lineBetween(OX + COLS * TW, 0, OX + COLS * TW, 900);
|
||||
}
|
||||
|
||||
_drawMaze() {
|
||||
const g = this.mazeGfx;
|
||||
g.clear();
|
||||
|
||||
// Background
|
||||
g.fillStyle(0x000022);
|
||||
g.fillRect(0, 0, 1600, 900);
|
||||
g.fillRect(OX, 0, COLS * TW, ROWS * TH);
|
||||
|
||||
// Walls
|
||||
for (let r = 0; r < ROWS; r++) {
|
||||
for (let c = 0; c < COLS; c++) {
|
||||
if (LAYOUT[r][c] === 1) {
|
||||
const x = c * this.TW;
|
||||
const y = r * this.TH;
|
||||
g.fillStyle(0x0000aa);
|
||||
g.fillRect(x + 1, y + 1, this.TW - 2, this.TH - 2);
|
||||
g.lineStyle(2, 0x4444ff);
|
||||
g.strokeRect(x + 2, y + 2, this.TW - 4, this.TH - 4);
|
||||
const x = OX + c * TW;
|
||||
const y = r * TH;
|
||||
g.fillStyle(0x0000cc);
|
||||
g.fillRect(x + 1, y + 1, TW - 2, TH - 2);
|
||||
g.lineStyle(2, 0x4455ff);
|
||||
g.strokeRect(x + 3, y + 3, TW - 6, TH - 6);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -273,19 +329,14 @@ export default class DotDude extends Phaser.Scene {
|
|||
for (let r = 0; r < ROWS; r++) {
|
||||
for (let c = 0; c < COLS; c++) {
|
||||
if (this.dots[r][c] === 1) {
|
||||
const x = (c + 0.5) * this.TW;
|
||||
const y = (r + 0.5) * this.TH;
|
||||
g.fillStyle(0xffd700);
|
||||
g.fillCircle(x, y, this.cfg.dotRadius);
|
||||
g.fillCircle(this._px(c), this._py(r), this.cfg.dotRadius);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ghosts
|
||||
this.ghosts.forEach(gh => {
|
||||
if (!gh.active) return;
|
||||
this._drawGhost(g, gh.x, gh.y, gh.color);
|
||||
});
|
||||
this.ghosts.forEach(gh => { if (gh.active) this._drawGhost(g, gh.x, gh.y, gh.color); });
|
||||
|
||||
// Player
|
||||
this._drawPlayer(g, this.player.x, this.player.y, this.player.dir, this.mouthAngle);
|
||||
|
|
@ -293,10 +344,8 @@ export default class DotDude extends Phaser.Scene {
|
|||
|
||||
_drawPlayer(g, x, y, dir, mouthAngle) {
|
||||
const r = 18;
|
||||
// Yellow circle
|
||||
g.fillStyle(0xffff00);
|
||||
g.fillCircle(x, y, r);
|
||||
// Black mouth triangle cutout
|
||||
const rot = Math.atan2(dir.dy, dir.dx);
|
||||
g.fillStyle(0x000022);
|
||||
g.fillTriangle(
|
||||
|
|
@ -304,20 +353,16 @@ export default class DotDude extends Phaser.Scene {
|
|||
x + Math.cos(rot - mouthAngle) * (r + 2), y + Math.sin(rot - mouthAngle) * (r + 2),
|
||||
x + Math.cos(rot + mouthAngle) * (r + 2), y + Math.sin(rot + mouthAngle) * (r + 2)
|
||||
);
|
||||
// Eye
|
||||
g.fillStyle(0x000000);
|
||||
g.fillCircle(x + Math.cos(rot - 0.8) * 8, y + Math.sin(rot - 0.8) * 8, 3);
|
||||
}
|
||||
|
||||
_drawGhost(g, x, y, color) {
|
||||
// Body
|
||||
g.fillStyle(color);
|
||||
g.fillCircle(x, y - 6, 16);
|
||||
g.fillRect(x - 16, y - 6, 32, 20);
|
||||
// Wavy bottom
|
||||
g.fillTriangle(x - 16, y + 14, x - 8, y + 20, x, y + 14);
|
||||
g.fillTriangle(x, y + 14, x + 8, y + 20, x + 16, y + 14);
|
||||
// Eyes
|
||||
g.fillStyle(0xffffff);
|
||||
g.fillCircle(x - 6, y - 8, 5);
|
||||
g.fillCircle(x + 6, y - 8, 5);
|
||||
|
|
|
|||
Loading…
Reference in New Issue