@@ -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