@@ -6,6 +6,7 @@ import PatternFormation from './PatternFormation.js'; // Add this import
6
6
import IntroScreen from './screens/IntroScreen.js' ;
7
7
import MusicPlayer from './audio/MusicPlayer.js' ;
8
8
import StartupScreen from './screens/StartupScreen.js' ;
9
+ import DebugWindow from './DebugWindow.js' ;
9
10
10
11
class Game {
11
12
constructor ( ) {
@@ -75,6 +76,9 @@ class Game {
75
76
76
77
// Initialize music player without starting it
77
78
this . musicPlayer = new MusicPlayer ( ) ;
79
+ // Create persistent offscreen canvas for player tinting effects
80
+ this . offCanvasCache = document . createElement ( 'canvas' ) ;
81
+ this . debugWindow = new DebugWindow ( ) ;
78
82
}
79
83
80
84
initGameScreen ( ) {
@@ -146,7 +150,13 @@ class Game {
146
150
}
147
151
148
152
bindEvents ( ) {
149
- window . addEventListener ( 'resize' , ( ) => this . setupCanvas ( ) ) ;
153
+ let resizeTimeout ;
154
+ window . addEventListener ( 'resize' , ( ) => {
155
+ clearTimeout ( resizeTimeout ) ;
156
+ resizeTimeout = setTimeout ( ( ) => {
157
+ this . setupCanvas ( ) ;
158
+ } , 100 ) ;
159
+ } ) ;
150
160
}
151
161
152
162
reset ( ) {
@@ -157,6 +167,10 @@ class Game {
157
167
}
158
168
159
169
handleInput ( e ) {
170
+ // Toggle debug window when d is pressed
171
+ if ( e . key === 'd' || e . key === 'D' ) {
172
+ this . debugWindow . visible = ! this . debugWindow . visible ;
173
+ }
160
174
// Add null check for current screen
161
175
if ( this . screens [ this . currentScreen ] && this . screens [ this . currentScreen ] . handleInput ) {
162
176
const nextScreen = this . screens [ this . currentScreen ] . handleInput ( e . key ) ;
@@ -193,105 +207,98 @@ class Game {
193
207
update ( timestamp ) {
194
208
const delta = ( timestamp - ( this . lastTime || timestamp ) ) / 1000 ;
195
209
this . lastTime = timestamp ;
196
-
197
- // Check if startup screen is complete
198
- if ( this . currentScreen === 'startup' ) {
199
- this . screens . startup . update ( delta ) ;
200
- if ( this . screens . startup . complete ) {
201
- this . switchScreen ( 'intro' ) ;
202
- }
203
- } else if ( this . currentScreen === 'intro' ) {
204
- this . screens . intro . update ( delta ) ;
205
- } else if ( this . currentScreen === 'game' ) {
210
+
211
+ if ( this . currentScreen === 'game' ) {
212
+ // Cache properties to avoid repeated lookups
213
+ const player = this . player ;
214
+ const formation = this . formation ;
215
+
206
216
this . bgScroller . update ( delta ) ;
207
- this . player . update ( delta ) ;
208
- // Update emitter positions relative to the player's sprite.
209
- const engine1X = this . player . x + this . player . width / 2 ;
210
- const engine1Y = this . player . y + this . player . height - 25 ; // was 50
211
- // For engine 2 and 3, adjust: 10px down and 8px inward.
212
- const engine2X = engine1X - 25 + 4 ; // was 50+8
213
<
10000
td data-grid-cell-id="diff-cd24f7c6b8fa2f39a95342f4f3a33a775696fb9d5290d25edf22464a22a9a361-213-216-1" data-selected="false" role="gridcell" style="background-color:var(--diffBlob-deletionNum-bgColor, var(--diffBlob-deletion-bgColor-num));text-align:center" tabindex="-1" valign="top" class="focusable-grid-cell diff-line-number position-relative left-side">
- const engine2Y = engine1Y - 25 + 5 ; // was 50+10
214
- const engine3X = engine1X + 25 - 4 ; // was 50-8
215
- const engine3Y = engine1Y - 25 + 5 ; // was 50+10
217
+ player . update ( delta ) ;
218
+
219
+ // Update emitter positions with local variables
220
+ const engine1X = player . x + player . width / 2 ;
221
+ const engine1Y = player . y + player . height - 25 ;
222
+ const engine2X = engine1X - 21 ; // simplified math
223
+ const engine2Y = engine1Y - 20 ;
224
+ const engine3X = engine1X + 21 ;
225
+ const engine3Y = engine1Y - 20 ;
226
+
216
227
this . particleEngine . setEmitter ( engine1X , engine1Y ) ;
217
228
this . particleEngine2 . setEmitter ( engine2X , engine2Y ) ;
218
229
this . particleEngine3 . setEmitter ( engine3X , engine3Y ) ;
219
- // Update particle engines with the new emitter positions.
220
230
this . particleEngine . update ( delta ) ;
221
231
this . particleEngine2 . update ( delta ) ;
222
232
this . particleEngine3 . update ( delta ) ;
223
233
224
- // Update laser firing state
225
- this . laserEngineLeft . setFiring ( this . player . isFiring ) ;
226
- this . laserEngineRight . setFiring ( this . player . isFiring ) ;
227
-
228
- // Update laser emitter positions (from top of sprite, spread apart)
229
- const laserLeftX = this . player . x + this . player . width * 0.3 ; // 30% from left
230
- const laserRightX = this . player . x + this . player . width * 0.7 ; // 70% from left
231
- const laserY = this . player . y ; // Top of sprite
232
-
234
+ // Update lasers using cached firing state
235
+ const firing = player . isFiring ;
236
+ this . laserEngineLeft . setFiring ( firing ) ;
237
+ this . laserEngineRight . setFiring ( firing ) ;
238
+ const laserLeftX = player . x + player . width * 0.3 ;
239
+ const laserRightX = player . x + player . width * 0.7 ;
240
+ const laserY = player . y ;
233
241
this . laserEngineLeft . setEmitter ( laserLeftX , laserY ) ;
234
242
this . laserEngineRight . setEmitter ( laserRightX , laserY ) ;
235
243
236
244
this . laserEngineLeft . update ( delta ) ;
237
245
this . laserEngineRight . update ( delta ) ;
238
246
239
- this . formation . update ( delta ) ;
240
-
241
- // Check player collision with alien lasers with pixel-perfect detection
242
- const alienLasers = this . formation . lasers ;
243
- for ( const laser of alienLasers ) {
244
- // Only check collision if laser is within player bounds
245
- if ( laser . x >= this . player . x &&
246
- laser . x <= this . player . x + this . player . width &&
247
- laser . y >= this . player . y &&
248
- laser . y <= this . player . y + this . player . height ) {
249
-
250
- // Do pixel-perfect collision test
251
- if ( this . player . checkPixelCollision ( laser . x , laser . y ) ) {
247
+ formation . update ( delta ) ;
248
+
249
+ // Collision detection using cached player bounds
250
+ const px = player . x , py = player . y , pw = player . width , ph = player . height ;
251
+ for ( const laser of formation . lasers ) {
252
+ if ( laser . x >= px && laser . x <= px + pw &&
253
+ laser . y >= py && laser . y <= py + ph ) {
254
+ if ( player . checkPixelCollision ( laser . x , laser . y ) ) {
252
255
this . handlePlayerHit ( ) ;
253
- laser . life = 0 ; // Destroy laser
254
- break ; // Exit loop after hit
256
+ laser . life = 0 ;
257
+ break ;
255
258
}
256
259
}
257
260
}
258
-
259
- // Check laser collisions with aliens
260
- if ( this . laserEngineLeft ) {
261
- this . laserEngineLeft . particles . forEach ( laser => {
262
- if ( this . formation . checkCollision ( laser . x , laser . y ) ) {
263
- laser . life = 0 ; // Destroy laser on hit
264
- }
265
- } ) ;
266
- }
267
- if ( this . laserEngineRight ) {
268
- this . laserEngineRight . particles . forEach ( laser => {
269
- if ( this . formation . checkCollision ( laser . x , laser . y ) ) {
270
- laser . life = 0 ; // Destroy laser on hit
261
+
262
+ // Check collisions for player lasers
263
+ [ this . laserEngineLeft , this . laserEngineRight ] . forEach ( engine => {
264
+ engine . particles . forEach ( laser => {
265
+ if ( formation . checkCollision ( laser . x , laser . y ) ) {
266
+ laser . life = 0 ;
271
267
}
272
268
} ) ;
273
- }
274
-
275
- // Check if all aliens are destroyed
276
- if ( this . formation . aliens . length === 0 ) {
277
- // Create new formation with increased difficulty
269
+ } ) ;
270
+
271
+ if ( formation . aliens . length === 0 ) {
278
272
this . formation = new PatternFormation ( this . ctx , {
279
273
virtualWidth : this . virtualWidth ,
280
274
virtualHeight : this . virtualHeight ,
281
275
pattern : 'infinity' ,
282
276
bgScroller : this . bgScroller ,
283
- difficulty : this . formation . difficulty + 1
277
+ difficulty : formation . difficulty + 1
284
278
} ) ;
285
279
}
286
-
287
- // Handle invulnerability
280
+
288
281
if ( this . playerInvulnerable ) {
289
282
this . invulnerabilityTimer += delta ;
290
283
if ( this . invulnerabilityTimer >= this . invulnerabilityTime ) {
291
284
this . playerInvulnerable = false ;
292
285
this . invulnerabilityTimer = 0 ;
293
286
}
294
287
}
288
+ } else {
289
+ // ...existing screen update code...
290
+ // Check if startup screen is complete
291
+ if ( this . currentScreen === 'startup' ) {
292
+ this . screens . startup . update ( delta ) ;
293
+ if ( this . screens . startup . complete ) {
294
+ this . switchScreen ( 'intro' ) ;
295
+ }
296
+ } else if ( this . currentScreen === 'intro' ) {
297
+ this . screens . intro . update ( delta ) ;
298
+ }
299
+ }
300
+ if ( this . debugWindow . visible ) {
301
+ this . debugWindow . update ( delta ) ;
295
302
}
296
303
}
297
304
@@ -331,11 +338,12 @@ class Game {
331
338
) ;
332
339
333
340
// Draw player with radiosity effect applied directly on the player's image
334
- // Create an offscreen canvas to tint the image
335
- const offCanvas = document . createElement ( 'canvas' ) ;
341
+ // Use offCanvasCache instead of creating a new canvas each frame
342
+ const offCanvas = this . offCanvasCache ;
336
343
offCanvas . width = this . player . img . width ;
337
344
offCanvas . height = this . player . img . height ;
338
345
const offCtx = offCanvas . getContext ( '2d' ) ;
346
+ offCtx . clearRect ( 0 , 0 , offCanvas . width , offCanvas . height ) ;
339
347
offCtx . drawImage ( this . player . img , 0 , 0 ) ;
340
348
offCtx . globalCompositeOperation = 'source-atop' ;
341
349
offCtx . fillStyle = `rgba(${ bgColor . r } , ${ bgColor . g } , ${ bgColor . b } , 0.33)` ;
@@ -367,6 +375,11 @@ class Game {
367
375
// Draw HUD if in game screen
368
376
this . drawHUD ( ) ;
369
377
}
378
+
379
+ // Draw debug window if enabled
380
+ if ( this . debugWindow . visible ) {
381
+ this . debugWindow . draw ( this . ctx ) ;
382
+ }
370
383
}
371
384
372
385
drawHUD ( ) {
0 commit comments