Skip to content

Commit 0fe786f

Browse files
committed
touch controls, deploy
1 parent d90cfab commit 0fe786f

4 files changed

Lines changed: 453 additions & 20 deletions

File tree

.github/workflows/deploy.yml

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
name: Deploy to GitHub Pages
2+
3+
on:
4+
push:
5+
branches: [main]
6+
workflow_dispatch:
7+
8+
permissions:
9+
contents: read
10+
pages: write
11+
id-token: write
12+
13+
concurrency:
14+
group: pages
15+
cancel-in-progress: true
16+
17+
jobs:
18+
build:
19+
runs-on: ubuntu-latest
20+
steps:
21+
- uses: actions/checkout@v4
22+
23+
- uses: actions/setup-node@v4
24+
with:
25+
node-version: 20
26+
cache: npm
27+
28+
- run: npm ci
29+
- run: npm run build
30+
31+
- uses: actions/upload-pages-artifact@v3
32+
with:
33+
path: dist
34+
35+
deploy:
36+
needs: build
37+
runs-on: ubuntu-latest
38+
environment:
39+
name: github-pages
40+
url: ${{ steps.deployment.outputs.page_url }}
41+
steps:
42+
- id: deployment
43+
uses: actions/deploy-pages@v4

src/main.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,15 @@ import { StarterSelectScene } from './scenes/StarterSelectScene';
44
import { OverworldScene } from './scenes/OverworldScene';
55
import { BattleScene } from './scenes/BattleScene';
66
import { SettingsScene } from './scenes/SettingsScene';
7+
import { PartyScene } from './scenes/PartyScene';
78

89
const config: Phaser.Types.Core.GameConfig = {
910
type: Phaser.AUTO,
1011
width: 800,
1112
height: 600,
1213
backgroundColor: '#1a1a2e',
1314
parent: document.body,
14-
scene: [BootScene, StarterSelectScene, OverworldScene, BattleScene, SettingsScene],
15+
scene: [BootScene, StarterSelectScene, OverworldScene, BattleScene, SettingsScene, PartyScene],
1516
physics: {
1617
default: 'arcade',
1718
arcade: { debug: false },

src/scenes/OverworldScene.ts

Lines changed: 178 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ export class OverworldScene extends Phaser.Scene {
3232
private partyText!: Phaser.GameObjects.Text;
3333
private statsText!: Phaser.GameObjects.Text;
3434

35+
/** Virtual D-pad state for touch controls */
36+
private dpadDir: { x: number; y: number } = { x: 0, y: 0 };
37+
3538
constructor() {
3639
super({ key: 'OverworldScene' });
3740
}
@@ -75,8 +78,17 @@ export class OverworldScene extends Phaser.Scene {
7578

7679
this.updateHud();
7780

81+
// Party button
82+
const partyBtn = this.add.text(this.cameras.main.width / 2 - 55, 6, '[ PARTY ]', {
83+
fontSize: '13px', fontFamily: 'monospace', color: '#88ffaa',
84+
}).setOrigin(0.5, 0).setInteractive({ useHandCursor: true });
85+
partyBtn.on('pointerdown', () => {
86+
this.persistSave();
87+
this.scene.start('PartyScene', { save: this.save });
88+
});
89+
7890
// Menu button
79-
const menuBtn = this.add.text(this.cameras.main.width / 2, 6, '[ MENU ]', {
91+
const menuBtn = this.add.text(this.cameras.main.width / 2 + 55, 6, '[ MENU ]', {
8092
fontSize: '13px', fontFamily: 'monospace', color: '#aaaaff',
8193
}).setOrigin(0.5, 0).setInteractive({ useHandCursor: true });
8294
menuBtn.on('pointerdown', () => {
@@ -85,20 +97,23 @@ export class OverworldScene extends Phaser.Scene {
8597
});
8698

8799
// Instructions
88-
this.add.text(this.cameras.main.width / 2, this.cameras.main.height - 14, 'WASD/Arrows to move Walk in tall grass to find creatures!', {
100+
this.add.text(this.cameras.main.width / 2, this.cameras.main.height - 14, 'WASD/Arrows to move \u2022 Walk in tall grass to find creatures!', {
89101
fontSize: '11px', fontFamily: 'monospace', color: '#aaaaaa',
90102
}).setOrigin(0.5);
103+
104+
// Virtual D-pad (touch / mobile)
105+
this.createDpad();
91106
}
92107

93108
update(_time: number, delta: number): void {
94109
// Movement
95110
this.playerVelX = 0;
96111
this.playerVelY = 0;
97112

98-
const left = this.cursors?.left.isDown || this.wasd?.A.isDown;
99-
const right = this.cursors?.right.isDown || this.wasd?.D.isDown;
100-
const up = this.cursors?.up.isDown || this.wasd?.W.isDown;
101-
const down = this.cursors?.down.isDown || this.wasd?.S.isDown;
113+
const left = this.cursors?.left.isDown || this.wasd?.A.isDown || this.dpadDir.x < 0;
114+
const right = this.cursors?.right.isDown || this.wasd?.D.isDown || this.dpadDir.x > 0;
115+
const up = this.cursors?.up.isDown || this.wasd?.W.isDown || this.dpadDir.y < 0;
116+
const down = this.cursors?.down.isDown || this.wasd?.S.isDown || this.dpadDir.y > 0;
102117

103118
if (left) this.playerVelX = -PLAYER_SPEED;
104119
else if (right) this.playerVelX = PLAYER_SPEED;
@@ -215,20 +230,164 @@ export class OverworldScene extends Phaser.Scene {
215230
}
216231

217232
private drawPlayer(): void {
218-
this.player.clear();
219-
// Body
220-
this.player.fillStyle(0xff4444, 1);
221-
this.player.fillCircle(0, 0, 10);
222-
// Hat
223-
this.player.fillStyle(0xdd2222, 1);
224-
this.player.fillRect(-8, -14, 16, 6);
233+
const p = this.player;
234+
p.clear();
235+
236+
// ── Shadow ──
237+
p.fillStyle(0x000000, 0.18);
238+
p.fillEllipse(0, 14, 20, 6);
239+
240+
// ── Legs ──
241+
p.fillStyle(0x2255aa, 1); // dark-blue jeans
242+
p.fillRoundedRect(-6, 6, 5, 9, 1);
243+
p.fillRoundedRect(1, 6, 5, 9, 1);
244+
// Shoes
245+
p.fillStyle(0x443322, 1);
246+
p.fillRoundedRect(-7, 13, 6, 3, 1);
247+
p.fillRoundedRect(1, 13, 6, 3, 1);
248+
249+
// ── Torso / jacket ──
250+
p.fillStyle(0x3366cc, 1); // blue jacket
251+
p.fillRoundedRect(-7, -4, 14, 12, 2);
252+
// Jacket zipper line
253+
p.lineStyle(1, 0x2244aa, 0.6);
254+
p.beginPath(); p.moveTo(0, -2); p.lineTo(0, 7); p.strokePath();
255+
256+
// ── Backpack (visible behind right shoulder) ──
257+
p.fillStyle(0xcc4422, 1);
258+
p.fillRoundedRect(6, -3, 5, 9, 2);
259+
p.lineStyle(1, 0x993311, 0.5);
260+
p.strokeRoundedRect(6, -3, 5, 9, 2);
261+
262+
// ── Arms ──
263+
p.fillStyle(0x3366cc, 1);
264+
// Left arm
265+
p.fillRoundedRect(-10, -2, 4, 9, 1);
266+
// Right arm
267+
p.fillRoundedRect(6, -2, 4, 9, 1);
268+
// Hands (skin)
269+
p.fillStyle(0xffccaa, 1);
270+
p.fillCircle(-8, 8, 2);
271+
p.fillCircle(8, 8, 2);
272+
273+
// ── Neck ──
274+
p.fillStyle(0xffccaa, 1);
275+
p.fillRect(-2, -6, 4, 3);
276+
277+
// ── Head ──
278+
p.fillStyle(0xffccaa, 1); // skin tone
279+
p.fillCircle(0, -11, 8);
280+
281+
// ── Hair ──
282+
p.fillStyle(0x332211, 1); // dark brown hair
283+
// Hair top
284+
p.beginPath();
285+
p.arc(0, -13, 8.5, Math.PI + 0.3, -0.3, false);
286+
p.fillPath();
287+
// Side tufts
288+
p.fillRect(-8, -15, 3, 5);
289+
p.fillRect(5, -15, 3, 5);
290+
291+
// ── Cap ──
292+
p.fillStyle(0xcc2222, 1); // red cap
293+
p.beginPath();
294+
p.arc(0, -14, 8, Math.PI + 0.15, -0.15, false);
295+
p.fillPath();
296+
// Cap brim
297+
p.fillStyle(0xaa1111, 1);
298+
p.fillRoundedRect(-9, -14, 18, 3, 1);
299+
// Cap logo dot
300+
p.fillStyle(0xffffff, 1);
301+
p.fillCircle(0, -17, 1.5);
302+
303+
// ── Face ──
225304
// Eyes
226-
this.player.fillStyle(0xffffff, 1);
227-
this.player.fillCircle(-4, -2, 3);
228-
this.player.fillCircle(4, -2, 3);
229-
this.player.fillStyle(0x000000, 1);
230-
this.player.fillCircle(-3, -2, 1.5);
231-
this.player.fillCircle(5, -2, 1.5);
305+
p.fillStyle(0xffffff, 1);
306+
p.fillEllipse(-3, -11, 4, 3.5);
307+
p.fillEllipse(3, -11, 4, 3.5);
308+
// Irises
309+
p.fillStyle(0x224488, 1);
310+
p.fillCircle(-3, -11, 1.4);
311+
p.fillCircle(3, -11, 1.4);
312+
// Pupils
313+
p.fillStyle(0x000000, 1);
314+
p.fillCircle(-3, -11, 0.7);
315+
p.fillCircle(3, -11, 0.7);
316+
// Highlights
317+
p.fillStyle(0xffffff, 0.9);
318+
p.fillCircle(-3.5, -11.5, 0.5);
319+
p.fillCircle(2.5, -11.5, 0.5);
320+
// Mouth
321+
p.lineStyle(1, 0xcc8877, 0.7);
322+
p.beginPath();
323+
p.arc(0, -7.5, 2, 0.2, Math.PI - 0.2, false);
324+
p.strokePath();
325+
}
326+
327+
// ─── Virtual D-pad ─────────────────────────────────────────
328+
private createDpad(): void {
329+
const h = this.cameras.main.height;
330+
const cx = 90; // centre of the D-pad
331+
const cy = h - 90;
332+
const btnSize = 40; // each direction button
333+
const gap = 2;
334+
const alpha = 0.3; // resting opacity
335+
const alphaActive = 0.55; // pressed opacity
336+
337+
// Centre circle (cosmetic)
338+
const centre = this.add.graphics();
339+
centre.fillStyle(0xffffff, alpha * 0.4);
340+
centre.fillCircle(cx, cy, 12);
341+
342+
const makeDpadBtn = (
343+
ox: number, oy: number,
344+
dirX: number, dirY: number,
345+
arrow: string,
346+
) => {
347+
const bx = cx + ox - btnSize / 2;
348+
const by = cy + oy - btnSize / 2;
349+
350+
const bg = this.add.graphics();
351+
bg.fillStyle(0xffffff, alpha);
352+
bg.fillRoundedRect(bx, by, btnSize, btnSize, 6);
353+
354+
this.add.text(cx + ox, cy + oy, arrow, {
355+
fontSize: '18px', fontFamily: 'monospace', color: '#ffffff',
356+
}).setOrigin(0.5).setAlpha(alpha + 0.15);
357+
358+
const zone = this.add.zone(cx + ox, cy + oy, btnSize, btnSize)
359+
.setInteractive({ useHandCursor: false });
360+
361+
zone.on('pointerdown', () => {
362+
this.dpadDir.x = dirX;
363+
this.dpadDir.y = dirY;
364+
bg.clear();
365+
bg.fillStyle(0xffffff, alphaActive);
366+
bg.fillRoundedRect(bx, by, btnSize, btnSize, 6);
367+
});
368+
369+
// Release on this specific button
370+
zone.on('pointerup', () => {
371+
if (dirX !== 0) this.dpadDir.x = 0;
372+
if (dirY !== 0) this.dpadDir.y = 0;
373+
bg.clear();
374+
bg.fillStyle(0xffffff, alpha);
375+
bg.fillRoundedRect(bx, by, btnSize, btnSize, 6);
376+
});
377+
zone.on('pointerout', () => {
378+
if (dirX !== 0) this.dpadDir.x = 0;
379+
if (dirY !== 0) this.dpadDir.y = 0;
380+
bg.clear();
381+
bg.fillStyle(0xffffff, alpha);
382+
bg.fillRoundedRect(bx, by, btnSize, btnSize, 6);
383+
});
384+
};
385+
386+
const offset = btnSize + gap;
387+
makeDpadBtn(0, -offset, 0, -1, '\u25B2'); // Up
388+
makeDpadBtn(0, offset, 0, 1, '\u25BC'); // Down
389+
makeDpadBtn(-offset, 0, -1, 0, '\u25C0'); // Left
390+
makeDpadBtn(offset, 0, 1, 0, '\u25B6'); // Right
232391
}
233392

234393
private triggerEncounter(): void {

0 commit comments

Comments
 (0)