cutechicken commited on
Commit
f79a717
·
verified ·
1 Parent(s): c0642cd

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +624 -643
index.html CHANGED
@@ -48,52 +48,34 @@
48
  display: none;
49
  z-index: 1000;
50
  }
51
- #titleScreen {
52
  position: fixed;
53
- top: 0;
54
- left: 0;
55
- width: 100%;
56
- height: 100%;
57
- background: url('city2.png') no-repeat center center;
58
- background-size: cover;
59
- z-index: 2000;
60
- display: flex;
61
- flex-direction: column;
62
- justify-content: center;
63
- align-items: center;
64
- }
65
- #titleScreen h1 {
66
  font-size: 72px;
67
  color: white;
68
- text-shadow: 2px 2px 5px black;
69
- margin-bottom: 50px;
70
- }
71
- .stageButton {
72
- padding: 15px 30px;
73
- font-size: 24px;
74
- background: #4CAF50;
75
- color: white;
76
- border: none;
77
- border-radius: 5px;
78
- cursor: pointer;
79
- margin: 10px;
80
- }
81
- .stageButton:disabled {
82
- background: #666;
83
- cursor: not-allowed;
84
  }
85
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
86
  </style>
87
- <div id="titleScreen">
88
- <h1>TANK WAR</h1>
89
- <div id="stageSelect">
90
- <button class="stageButton" onclick="startStage(1)">Stage 1</button>
91
- <button class="stageButton" disabled>Stage 2</button>
92
- <button class="stageButton" disabled>Stage 3</button>
93
- <button class="stageButton" disabled>Stage 4</button>
94
- </div>
95
- </div>
96
-
97
  </head>
98
  <body>
99
  <div id="instructions">
@@ -109,18 +91,8 @@
109
  <button id="nextRound" class="button">Next Round</button>
110
  <button id="restart" class="button">Restart Game</button>
111
  <canvas id="gameCanvas"></canvas>
112
- <div id="titleScreen">
113
- <h1>TANK WAR</h1>
114
- <div id="stageSelect">
115
- <button class="stageButton" onclick="startStage(1)">Stage 1</button>
116
- <button class="stageButton" disabled>Stage 2</button>
117
- <button class="stageButton" disabled>Stage 3</button>
118
- <button class="stageButton" disabled>Stage 4</button>
119
- </div>
120
- </div>
121
- </body>
122
- </html>
123
- <div id="shop" style="display:none; position:fixed; top:50%; left:50%; transform:translate(-50%,-50%); background:rgba(0,0,0,0.9); padding:20px; border-radius:10px; color:white; z-index:1000;">
124
  <h2>Tank Shop</h2>
125
  <div style="display:flex; gap:20px;">
126
  <div id="tank1" style="text-align:center;">
@@ -138,188 +110,415 @@
138
  <p style="color: #ff6b6b;">-30% Speed</p>
139
  <button onclick="buyTank('player3.png', 500, 'tank2')">Buy</button>
140
  </div>
141
- <div id="bf109" style="text-align:center;">
142
  <h3>BF-109</h3>
143
  <img src="bf109.png" width="100" height="100">
144
  <p>1000 Gold</p>
145
  <p style="color: #4CAF50;">Air support from BF-109</p>
146
  <button onclick="buyBF109()">Buy</button>
147
  </div>
148
- <div id="ju87" style="text-align:center;">
149
  <h3>JU-87</h3>
150
  <img src="ju87.png" width="100" height="100">
151
  <p>1500 Gold</p>
152
  <p style="color: #4CAF50;">Get ju-87 air support</p>
153
  <button onclick="buyJU87()">Buy</button>
154
  </div>
155
- <div id="apcr" style="text-align:center;">
156
- <h3>APCR</h3>
157
- <img src="apcr.png" width="80" height="20">
158
- <p>1000 Gold</p>
159
- <p style="color: #4CAF50;">+100% Bullet Speed</p>
160
- <button onclick="buyAPCR()">Buy</button>
161
- </div>
162
  </div>
163
  </div>
164
- <button id="bossButton" class="button">Fight Boss!</button>
165
- <div id="winMessage" class="button" style="font-size: 72px; background: none;">You Win!</div>
166
- <script>
167
- const canvas = document.getElementById('gameCanvas');
168
- const ctx = canvas.getContext('2d');
169
- const nextRoundBtn = document.getElementById('nextRound');
170
- const restartBtn = document.getElementById('restart');
171
- const weaponInfo = document.getElementById('weaponInfo');
172
- const countdownEl = document.getElementById('countdown');
173
- const bossButton = document.getElementById('bossButton');
174
-
175
- canvas.width = window.innerWidth;
176
- canvas.height = window.innerHeight;
177
-
178
- // Game state
179
- let currentRound = 1;
180
- let gameOver = false;
181
- let currentWeapon = 'cannon';
182
- let enemies = [];
183
- let bullets = [];
184
- let items = [];
185
- let lastShot = 0;
186
- let isCountingDown = true;
187
- let countdownTime = 3;
188
- let autoFire = false;
189
- let gold = 0;
190
- let isBossStage = false;
191
- let effects = [];
192
- let hasAPCR = false;
193
- let hasBF109 = false;
194
- let hasJU87 = false;
195
- let lastJU87Spawn = 0;
196
- let supportUnits = [];
197
- let lastSupportSpawn = 0;
198
- let gameStarted = false; // 게임 시작 상태를 추적하는 새로운 변수
199
-
200
- // Load assets
201
- const backgroundImg = new Image();
202
- backgroundImg.src = 'city.png';
203
- const titleBackgroundImg = new Image();
204
- titleBackgroundImg.src = 'city2.png';
205
- const playerImg = new Image();
206
- playerImg.src = 'player.png';
207
- const enemyImg = new Image();
208
- enemyImg.src = 'enemy.png';
209
- const bulletImg = new Image();
210
- bulletImg.src = 'apcr2.png';
211
-
212
- // Audio setup
213
- const cannonSound = new Audio('firemn.ogg');
214
- const machinegunSound = new Audio('firemg.ogg');
215
- const enemyFireSound = new Audio('fireenemy.ogg');
216
- let bgm = new Audio('title.ogg');
217
- const countSound = new Audio('count.ogg');
218
- const deathSound = new Audio('death.ogg');
219
- bgm.loop = true;
220
- enemyFireSound.volume = 0.5;
221
-
222
- // Weapons configuration
223
- const weapons = {
224
- cannon: {
225
- fireRate: 1000,
226
- damage: 0.25,
227
- bulletSize: 5,
228
- sound: cannonSound
229
- },
230
- machinegun: {
231
- fireRate: 200,
232
- damage: 0.05,
233
- bulletSize: 2,
234
- sound: machinegunSound
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
235
  }
236
- };
237
 
238
- // Player setup
239
- const player = {
240
- x: canvas.width/2,
241
- y: canvas.height/2,
242
- speed: 5,
243
- angle: 0,
244
- width: 100,
245
- height: 45,
246
- health: 1000,
247
- maxHealth: 1000
248
- };
249
-
250
- function startStage(stageNumber) {
251
- if (stageNumber === 1) {
252
- document.getElementById('titleScreen').style.display = 'none';
253
- document.getElementById('instructions').style.display = 'block';
254
- document.getElementById('weaponInfo').style.display = 'block';
255
- document.getElementById('gameCanvas').style.display = 'block';
256
-
257
- // BGM 변경
258
- bgm.pause();
259
- bgm = new Audio('BGM2.ogg');
260
- bgm.loop = true;
261
- bgm.play();
262
 
263
- gameStarted = true;
264
- currentRound = 1;
265
- initRound();
266
- gameLoop();
267
  }
268
  }
269
- // 게임 초기화 및 라운드 관련 함수
270
- function startCountdown() {
271
- isCountingDown = true;
272
- countdownTime = 3;
273
- countdownEl.style.display = 'block';
274
- countdownEl.textContent = countdownTime;
275
- bgm.pause();
276
- countSound.play();
277
-
278
- const countInterval = setInterval(() => {
279
- countdownTime--;
280
- if(countdownTime <= 0) {
281
- clearInterval(countInterval);
282
- countdownEl.style.display = 'none';
283
- isCountingDown = false;
284
- bgm.play();
 
 
 
 
 
 
 
 
 
 
285
  }
286
- countdownEl.textContent = countdownTime > 0 ? countdownTime : 'GO!';
287
- }, 1000);
288
- }
289
 
290
- function initRound() {
291
- enemies = [];
292
- for(let i = 0; i < 1 * currentRound; i++) {
293
- enemies.push(new Enemy());
 
 
 
294
  }
295
- player.health = player.maxHealth;
296
- bullets = [];
297
- items = [];
298
- supportUnits = [];
299
- lastSupportSpawn = 0;
300
 
301
- startCountdown();
 
 
 
 
 
 
 
302
 
303
- setTimeout(() => {
304
- if (hasJU87) {
305
- supportUnits.push(new JU87());
306
- lastJU87Spawn = Date.now();
 
307
  }
308
- }, 3000);
 
 
 
 
 
 
 
 
 
 
309
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
310
 
311
- // 상점 관련 함수
312
- function showShop() {
313
- document.getElementById('shop').style.display = 'block';
 
 
 
 
 
 
 
314
  }
 
 
 
 
 
 
 
315
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
316
  const defaultPlayerStats = {
317
  maxHealth: 1000,
318
  speed: 5,
319
  width: 100,
320
  height: 45
321
  };
322
-
323
  function buyTank(tankImg, cost, tankId) {
324
  if (gold >= cost) {
325
  gold -= cost;
@@ -327,22 +526,23 @@ function buyTank(tankImg, cost, tankId) {
327
  document.getElementById(tankId).style.display = 'none';
328
  document.getElementById('shop').style.display = 'none';
329
 
330
- if (tankId === 'tank1') {
331
  player.maxHealth = 1500;
332
- player.speed = defaultPlayerStats.speed;
333
- player.width = 90;
334
- player.height = 50;
335
- } else if (tankId === 'tank2') {
 
336
  player.maxHealth = 2000;
337
  player.speed = defaultPlayerStats.speed * 0.7;
338
- player.width = 100;
339
- player.height = 45;
340
  }
 
341
  player.health = player.maxHealth;
342
  }
343
  }
344
-
345
- function buyAPCR() {
346
  if (gold >= 1000 && !hasAPCR) {
347
  gold -= 1000;
348
  hasAPCR = true;
@@ -350,8 +550,7 @@ function buyAPCR() {
350
  document.getElementById('shop').style.display = 'none';
351
  }
352
  }
353
-
354
- function buyBF109() {
355
  if (gold >= 1000 && !hasBF109) {
356
  gold -= 1000;
357
  hasBF109 = true;
@@ -359,18 +558,92 @@ function buyBF109() {
359
  document.getElementById('shop').style.display = 'none';
360
  }
361
  }
362
-
363
- function buyJU87() {
364
  if (gold >= 1500 && !hasJU87) {
365
  gold -= 1500;
366
  hasJU87 = true;
367
  document.getElementById('ju87').style.display = 'none';
368
  document.getElementById('shop').style.display = 'none';
369
- lastJU87Spawn = Date.now();
370
  }
371
  }
372
- // 게임 업데이트 및 렌더링 관련 함수
373
- function updateGame() {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
374
  if(gameOver) return;
375
  if(!isCountingDown) {
376
  // 플레이어 움직임
@@ -378,17 +651,15 @@ function updateGame() {
378
  if(keys['s']) player.y += player.speed;
379
  if(keys['a']) player.x -= player.speed;
380
  if(keys['d']) player.x += player.speed;
381
-
382
  player.x = Math.max(player.width/2, Math.min(canvas.width - player.width/2, player.x));
383
  player.y = Math.max(player.height/2, Math.min(canvas.height - player.height/2, player.y));
384
-
385
  fireBullet();
386
  }
387
 
388
- // BF109 지원
389
  if (hasBF109 && !isCountingDown) {
390
  const now = Date.now();
391
- if (now - lastSupportSpawn > 10000) {
392
  supportUnits.push(
393
  new SupportUnit(canvas.height * 0.2),
394
  new SupportUnit(canvas.height * 0.5),
@@ -398,95 +669,100 @@ function updateGame() {
398
  }
399
  }
400
 
401
- // JU87 지원
402
  if (hasJU87 && !isCountingDown) {
403
  const now = Date.now();
404
- if (now - lastJU87Spawn > 15000) {
405
  supportUnits.push(new JU87());
406
  lastJU87Spawn = now;
407
  }
408
  }
409
 
410
- // 지원 유닛 업데이트
411
  supportUnits = supportUnits.filter(unit => unit.update());
412
 
413
- // 적 업데이트
414
  enemies.forEach(enemy => enemy.update());
415
-
416
- if(!isCountingDown) {
417
- // 총알 업데이트
418
- bullets = bullets.filter(bullet => {
419
- bullet.x += Math.cos(bullet.angle) * bullet.speed;
420
- bullet.y += Math.sin(bullet.angle) * bullet.speed;
421
-
422
- if(!bullet.isEnemy) {
423
- enemies = enemies.filter(enemy => {
424
- const dist = Math.hypot(bullet.x - enemy.x, bullet.y - enemy.y);
425
- if(dist < 30) {
426
- enemy.health -= bullet.damage * 1000;
427
- if(enemy.health <= 0) {
428
- spawnHealthItem(enemy.x, enemy.y);
429
- gold += 100;
430
- effects.push(new Effect(enemy.x, enemy.y, 1000, 'death'));
431
- deathSound.cloneNode().play();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
432
  return false;
433
  }
434
- return true;
435
  }
436
- return true;
 
437
  });
438
- } else {
439
- const dist = Math.hypot(bullet.x - player.x, bullet.y - player.y);
440
- if(dist < 30) {
441
- player.health -= bullet.damage;
442
- if(player.health <= 0) {
443
- gameOver = true;
444
- restartBtn.style.display = 'block';
445
- effects.push(new Effect(player.x, player.y, 1000, 'death'));
446
- deathSound.cloneNode().play();
447
  }
448
- return false;
449
- }
450
- }
451
- return bullet.x >= 0 && bullet.x <= canvas.width &&
452
- bullet.y >= 0 && bullet.y <= canvas.height;
453
- });
454
-
455
- // 아이템 업데이트
456
- items = items.filter(item => {
457
- const dist = Math.hypot(item.x - player.x, item.y - player.y);
458
- if(dist < 30) {
459
- player.health = Math.min(player.health + 200, player.maxHealth);
460
- return false;
461
- }
462
- return true;
463
- });
464
-
465
- // 라운드 종료 체크
466
- if(enemies.length === 0) {
467
  if (!isBossStage) {
468
  if(currentRound < 10) {
469
  nextRoundBtn.style.display = 'block';
470
  showShop();
471
  } else {
472
- bossButton.style.display = 'block';
473
  }
474
  } else {
475
  gameOver = true;
476
  document.getElementById('winMessage').style.display = 'block';
477
  restartBtn.style.display = 'block';
478
- bgm.pause();
479
- const victorySound = new Audio('victory.ogg');
480
- victorySound.play();
481
  }
482
  }
483
  }
 
484
  }
485
- // 게임 렌더링 함수
486
- function drawGame() {
 
 
 
 
 
 
 
 
487
  ctx.clearRect(0, 0, canvas.width, canvas.height);
488
-
489
- // 배경 그리기
490
  const pattern = ctx.createPattern(backgroundImg, 'repeat');
491
  ctx.fillStyle = pattern;
492
  ctx.fillRect(0, 0, canvas.width, canvas.height);
@@ -498,7 +774,7 @@ function drawGame() {
498
  ctx.drawImage(playerImg, -player.width/2, -player.height/2, player.width, player.height);
499
  ctx.restore();
500
 
501
- // 체력바 그리기
502
  drawHealthBar(canvas.width/2, 30, player.health, player.maxHealth, 200, 20, 'green');
503
 
504
  // 적 그리기
@@ -511,34 +787,31 @@ function drawGame() {
511
  ctx.restore();
512
  drawHealthBar(enemy.x, enemy.y - 40, enemy.health, enemy.maxHealth, 60, 5, 'red');
513
  });
514
-
515
- // 지원 유닛 그리기
516
- supportUnits.forEach(unit => {
517
  ctx.save();
518
  ctx.translate(unit.x, unit.y);
519
  ctx.rotate(unit.angle);
520
  ctx.drawImage(unit.img, -unit.width/2, -unit.height/2, unit.width, unit.height);
521
  ctx.restore();
522
  });
523
-
524
- // 총알 그리기
525
- bullets.forEach(bullet => {
526
- if (bullet.isEnemy || !bullet.isAPCR) {
527
- ctx.beginPath();
528
- ctx.fillStyle = bullet.isEnemy ? 'red' : 'blue';
529
- ctx.arc(bullet.x, bullet.y, bullet.size, 0, Math.PI * 2);
530
- ctx.fill();
531
- } else {
532
- ctx.save();
533
- ctx.translate(bullet.x, bullet.y);
534
- ctx.rotate(bullet.angle);
535
- const width = currentWeapon === 'machinegun' ? 10 : 20;
536
- const height = currentWeapon === 'machinegun' ? 5 : 10;
537
- ctx.drawImage(bulletImg, -width/2, -height/2, width, height);
538
- ctx.restore();
539
- }
540
- });
541
-
542
  // 아이템 그리기
543
  items.forEach(item => {
544
  ctx.beginPath();
@@ -546,7 +819,7 @@ function drawGame() {
546
  ctx.arc(item.x, item.y, 10, 0, Math.PI * 2);
547
  ctx.fill();
548
  });
549
-
550
  // UI 그리기
551
  ctx.fillStyle = 'white';
552
  ctx.font = '24px Arial';
@@ -554,34 +827,31 @@ function drawGame() {
554
  ctx.fillText(`Gold: ${gold}`, 10, 60);
555
 
556
  // 이펙트 그리기
557
- effects = effects.filter(effect => !effect.isExpired());
558
- effects.forEach(effect => {
559
- effect.update();
560
- ctx.save();
561
- ctx.translate(effect.x, effect.y);
562
- if (effect.type === 'fire') ctx.rotate(effect.angle);
563
- const size = effect.type === 'death' ? 75 : 42;
564
- ctx.drawImage(effect.img, -size/2, -size/2, size, size);
565
- ctx.restore();
566
- });
 
567
 
568
- // 카운트다운 오버레이
569
- if (isCountingDown) {
570
  ctx.fillStyle = 'rgba(0, 0, 0, 0.5)';
571
  ctx.fillRect(0, 0, canvas.width, canvas.height);
572
  }
573
  }
574
 
575
- // 게임 루프
576
  function gameLoop() {
577
- if (!gameOver && gameStarted) {
578
- updateGame();
579
- drawGame();
580
- requestAnimationFrame(gameLoop);
581
- }
582
  }
583
 
584
- // 이벤트 리스너
585
  nextRoundBtn.addEventListener('click', () => {
586
  currentRound++;
587
  nextRoundBtn.style.display = 'none';
@@ -589,334 +859,45 @@ nextRoundBtn.addEventListener('click', () => {
589
  initRound();
590
  });
591
 
592
- restartBtn.addEventListener('click', () => {
593
- location.reload();
594
- });
595
 
596
- // 키보드 마우스 이벤트
597
- const keys = {};
598
- document.addEventListener('keydown', e => {
599
- keys[e.key] = true;
600
- if(e.key.toLowerCase() === 'c') {
601
- currentWeapon = currentWeapon === 'cannon' ? 'machinegun' : 'cannon';
602
- weaponInfo.textContent = `Current Weapon: ${currentWeapon.charAt(0).toUpperCase() + currentWeapon.slice(1)}`;
603
- } else if(e.key.toLowerCase() === 'r') {
604
- autoFire = !autoFire;
605
- }
606
- });
607
-
608
- document.addEventListener('keyup', e => keys[e.key] = false);
609
-
610
- canvas.addEventListener('mousemove', (e) => {
611
- player.angle = Math.atan2(e.clientY - player.y, e.clientX - player.x);
 
 
 
 
 
612
  });
613
 
614
- // 보스 버튼 이벤트
615
- bossButton.addEventListener('click', () => {
616
- startBossStage();
 
 
 
 
 
617
  });
618
 
619
- // 창 크기 조절 이벤트
620
  window.addEventListener('resize', () => {
621
  canvas.width = window.innerWidth;
622
  canvas.height = window.innerHeight;
623
  });
624
- // 총알 발사 함수
625
- function fireBullet() {
626
- if(isCountingDown) return;
627
- const weapon = weapons[currentWeapon];
628
- const now = Date.now();
629
- if ((keys[' '] || autoFire) && now - lastShot > weapon.fireRate) {
630
- weapon.sound.cloneNode().play();
631
- effects.push(new Effect(
632
- player.x + Math.cos(player.angle) * 30,
633
- player.y + Math.sin(player.angle) * 30,
634
- 500,
635
- 'fire',
636
- player.angle,
637
- player
638
- ));
639
-
640
- bullets.push({
641
- x: player.x + Math.cos(player.angle) * 30,
642
- y: player.y + Math.sin(player.angle) * 30,
643
- angle: player.angle,
644
- speed: hasAPCR ? 20 : 10,
645
- isEnemy: false,
646
- damage: weapon.damage,
647
- size: weapon.bulletSize,
648
- isAPCR: hasAPCR
649
- });
650
- lastShot = now;
651
- }
652
- }
653
-
654
- // Enemy 클래스
655
- class Enemy {
656
- constructor(isBoss = false) {
657
- this.x = Math.random() * canvas.width;
658
- this.y = Math.random() * canvas.height;
659
- this.health = isBoss ? 15000 : 1000;
660
- this.maxHealth = this.health;
661
- this.speed = isBoss ? 1 : 2;
662
- this.lastShot = 0;
663
- this.shootInterval = isBoss ? 1000 : 1000;
664
- this.angle = 0;
665
- this.width = 100;
666
- this.height = 45;
667
- this.moveTimer = 0;
668
- this.moveInterval = Math.random() * 2000 + 1000;
669
- this.moveAngle = Math.random() * Math.PI * 2;
670
- this.isBoss = isBoss;
671
-
672
- if (isBoss) {
673
- this.enemyImg = new Image();
674
- this.enemyImg.src = 'boss.png';
675
- } else if (currentRound >= 7) {
676
- this.enemyImg = new Image();
677
- this.enemyImg.src = 'enemy3.png';
678
- } else if (currentRound >= 4) {
679
- this.enemyImg = new Image();
680
- this.enemyImg.src = 'enemy2.png';
681
- }
682
- }
683
-
684
- update() {
685
- if(isCountingDown) return;
686
- const now = Date.now();
687
-
688
- if (now - this.moveTimer > this.moveInterval) {
689
- this.moveAngle = Math.random() * Math.PI * 2;
690
- this.moveTimer = now;
691
- }
692
- this.x += Math.cos(this.moveAngle) * this.speed;
693
- this.y += Math.sin(this.moveAngle) * this.speed;
694
- this.x = Math.max(this.width/2, Math.min(canvas.width - this.width/2, this.x));
695
- this.y = Math.max(this.height/2, Math.min(canvas.height - this.height/2, this.y));
696
- this.angle = Math.atan2(player.y - this.y, player.x - this.x);
697
-
698
- if (now - this.lastShot > this.shootInterval && !isCountingDown) {
699
- this.shoot();
700
- this.lastShot = now;
701
- }
702
- }
703
-
704
- shoot() {
705
- const sound = this.isBoss ? new Audio('firemn.ogg') : enemyFireSound.cloneNode();
706
- sound.play();
707
-
708
- effects.push(new Effect(
709
- this.x + Math.cos(this.angle) * 30,
710
- this.y + Math.sin(this.angle) * 30,
711
- 500,
712
- 'fire',
713
- this.angle,
714
- this
715
- ));
716
-
717
- bullets.push({
718
- x: this.x + Math.cos(this.angle) * 30,
719
- y: this.y + Math.sin(this.angle) * 30,
720
- angle: this.angle,
721
- speed: this.isBoss ? 10 : 5,
722
- isEnemy: true,
723
- size: this.isBoss ? 5 : 3,
724
- damage: this.isBoss ? 300 : 150
725
- });
726
- }
727
- }
728
-
729
- // Effect 클래스
730
- class Effect {
731
- constructor(x, y, duration, type, angle = 0, parent = null) {
732
- this.x = x;
733
- this.y = y;
734
- this.startTime = Date.now();
735
- this.duration = duration;
736
- this.type = type;
737
- this.angle = angle;
738
- this.parent = parent;
739
- this.offset = { x: Math.cos(angle) * 30, y: Math.sin(angle) * 30 };
740
- this.img = new Image();
741
- this.img.src = type === 'death' ? 'bang.png' : 'fire2.png';
742
- }
743
-
744
- update() {
745
- if(this.parent && this.type === 'fire') {
746
- this.x = this.parent.x + this.offset.x;
747
- this.y = this.parent.y + this.offset.y;
748
- this.angle = this.parent.angle;
749
- }
750
- }
751
-
752
- isExpired() {
753
- return Date.now() - this.startTime > this.duration;
754
- }
755
- }
756
- // SupportUnit 클래스
757
- class SupportUnit {
758
- constructor(yPosition) {
759
- this.x = 0;
760
- this.y = yPosition;
761
- this.speed = 5;
762
- this.lastShot = 0;
763
- this.width = 100;
764
- this.height = 100;
765
- this.angle = 0;
766
- this.img = new Image();
767
- this.img.src = 'bf109.png';
768
- this.hasPlayedSound = false;
769
- this.mgSound = null;
770
- }
771
-
772
- update() {
773
- this.x += this.speed;
774
-
775
- if (isCountingDown) {
776
- if (this.mgSound) {
777
- this.mgSound.pause();
778
- this.mgSound.currentTime = 0;
779
- }
780
- this.hasPlayedSound = false;
781
- }
782
-
783
- const now = Date.now();
784
- if (now - this.lastShot > 200 && !isCountingDown) {
785
- this.shoot();
786
- this.lastShot = now;
787
- }
788
- return this.x < canvas.width;
789
- }
790
-
791
- shoot() {
792
- if (!this.hasPlayedSound) {
793
- const firstSound = new Audio('bf109mg.ogg');
794
- firstSound.volume = 1.0;
795
- firstSound.play();
796
- this.hasPlayedSound = true;
797
- }
798
-
799
- if (!isCountingDown) {
800
- const shootSound = new Audio('bf109mgse.ogg');
801
- shootSound.volume = 0.5;
802
- shootSound.play();
803
- }
804
-
805
- bullets.push({
806
- x: this.x + Math.cos(this.angle) * 30,
807
- y: this.y + Math.sin(this.angle) * 30,
808
- angle: this.angle,
809
- speed: 10,
810
- isEnemy: false,
811
- damage: weapons.machinegun.damage,
812
- size: weapons.machinegun.bulletSize
813
- });
814
- }
815
- }
816
-
817
- // JU87 클래스
818
- class JU87 {
819
- constructor() {
820
- this.x = canvas.width;
821
- this.y = 50;
822
- this.speed = 5;
823
- this.width = 100;
824
- this.height = 100;
825
- this.angle = Math.PI;
826
- this.img = new Image();
827
- this.img.src = 'ju87.png';
828
- this.target = null;
829
- this.lastShot = 0;
830
- this.spawnTime = Date.now();
831
- this.hasPlayedSound = false;
832
- this.hasPlayedMGSound = false;
833
- this.isReturning = false;
834
- this.circleAngle = 0;
835
- this.returningToCenter = false;
836
- }
837
-
838
- selectTarget() {
839
- return enemies.length > 0 ?
840
- enemies[Math.floor(Math.random() * enemies.length)] : null;
841
- }
842
-
843
- shoot() {
844
- if (!this.hasPlayedMGSound && !isCountingDown) {
845
- const mgSound = new Audio('ju87mg.ogg');
846
- mgSound.volume = 1.0;
847
- mgSound.currentTime = 0;
848
- mgSound.play().catch(error => console.error('Audio play failed:', error));
849
- this.hasPlayedMGSound = true;
850
- }
851
-
852
- [[20, 50], [80, 50]].forEach(([x, y]) => {
853
- const offsetX = x - 50;
854
- const offsetY = y - 50;
855
-
856
- const rotatedX = this.x + (Math.cos(this.angle) * offsetX - Math.sin(this.angle) * offsetY);
857
- const rotatedY = this.y + (Math.sin(this.angle) * offsetX + Math.cos(this.angle) * offsetY);
858
-
859
- bullets.push({
860
- x: rotatedX,
861
- y: rotatedY,
862
- angle: this.angle,
863
- speed: 10,
864
- isEnemy: false,
865
- damage: weapons.machinegun.damage * 2,
866
- size: weapons.machinegun.bulletSize
867
- });
868
- });
869
- }
870
-
871
- update() {
872
- // JU87의 update 메서드 내용은 이전과 동일하게 유지
873
- // (길이 제한으로 인해 생략했지만, 이전 코드와 동일하게 구현해야 함)
874
- }
875
- }
876
-
877
- // 게임 초기화 및 시작
878
- document.addEventListener('DOMContentLoaded', () => {
879
- const titleScreen = document.getElementById('titleScreen');
880
- const instructions = document.getElementById('instructions');
881
- const weaponInfo = document.getElementById('weaponInfo');
882
- const gameCanvas = document.getElementById('gameCanvas');
883
-
884
- instructions.style.display = 'none';
885
- weaponInfo.style.display = 'none';
886
- gameCanvas.style.display = 'none';
887
-
888
- bgm.play().catch(err => console.error("Error playing title music:", err));
889
-
890
- // 이벤트 리스너 설정
891
- window.addEventListener('resize', () => {
892
- canvas.width = window.innerWidth;
893
- canvas.height = window.innerHeight;
894
- });
895
- });
896
-
897
- // 헬퍼 함수들
898
- function spawnHealthItem(x, y) {
899
- items.push({x, y});
900
- }
901
-
902
- function drawHealthBar(x, y, health, maxHealth, width, height, color) {
903
- ctx.fillStyle = '#333';
904
- ctx.fillRect(x - width/2, y - height/2, width, height);
905
- ctx.fillStyle = color;
906
- ctx.fillRect(x - width/2, y - height/2, width * (health/maxHealth), height);
907
- }
908
-
909
- function startBossStage() {
910
- isBossStage = true;
911
- enemies = [];
912
- enemies.push(new Enemy(true));
913
- player.health = player.maxHealth;
914
- bullets = [];
915
- items = [];
916
- document.getElementById('bossButton').style.display = 'none';
917
- bgm.src = 'BGM.ogg';
918
- startCountdown();
919
- }
920
- </script>
921
  </body>
922
- </html>
 
48
  display: none;
49
  z-index: 1000;
50
  }
51
+ #countdown {
52
  position: fixed;
53
+ top: 50%;
54
+ left: 50%;
55
+ transform: translate(-50%, -50%);
 
 
 
 
 
 
 
 
 
 
56
  font-size: 72px;
57
  color: white;
58
+ text-shadow: 2px 2px 4px rgba(0,0,0,0.5);
59
+ z-index: 1000;
60
+ display: none;
 
 
 
 
 
 
 
 
 
 
 
 
 
61
  }
62
+ #winMessage {
63
+ font-size: 72px;
64
+ background: none;
65
+ top: 30%; /* 화면의 위쪽으로 이동 */
66
+ left: 50%;
67
+ transform: translate(-50%, -50%);
68
+ z-index: 1001; /* 다음 라운드 버튼보다 위로 설정 */
69
+ }
70
+
71
+ #nextRound {
72
+ top: 80%; /* 화면의 아래쪽으로 이동 */
73
+ z-index: 1000; /* "You Win"보다 아래로 설정 */
74
+ }
75
+ #nextRound {
76
+ top: 80%; /* 아래쪽으로 이동 */
77
+ }
78
  </style>
 
 
 
 
 
 
 
 
 
 
79
  </head>
80
  <body>
81
  <div id="instructions">
 
91
  <button id="nextRound" class="button">Next Round</button>
92
  <button id="restart" class="button">Restart Game</button>
93
  <canvas id="gameCanvas"></canvas>
94
+
95
+ <div id="shop" style="display:none; position:fixed; top:50%; left:50%; transform:translate(-50%,-50%); background:rgba(0,0,0,0.9); padding:20px; border-radius:10px; color:white; z-index:1000;">
 
 
 
 
 
 
 
 
 
 
96
  <h2>Tank Shop</h2>
97
  <div style="display:flex; gap:20px;">
98
  <div id="tank1" style="text-align:center;">
 
110
  <p style="color: #ff6b6b;">-30% Speed</p>
111
  <button onclick="buyTank('player3.png', 500, 'tank2')">Buy</button>
112
  </div>
113
+ <div id="bf109" style="text-align:center;">
114
  <h3>BF-109</h3>
115
  <img src="bf109.png" width="100" height="100">
116
  <p>1000 Gold</p>
117
  <p style="color: #4CAF50;">Air support from BF-109</p>
118
  <button onclick="buyBF109()">Buy</button>
119
  </div>
120
+ <div id="ju87" style="text-align:center;">
121
  <h3>JU-87</h3>
122
  <img src="ju87.png" width="100" height="100">
123
  <p>1500 Gold</p>
124
  <p style="color: #4CAF50;">Get ju-87 air support</p>
125
  <button onclick="buyJU87()">Buy</button>
126
  </div>
127
+ <div id="apcr" style="text-align:center;">
128
+ <h3>APCR</h3>
129
+ <img src="apcr.png" width="80" height="20"> <!-- 여기를 80x20으로 수정 -->
130
+ <p>1000 Gold</p>
131
+ <p style="color: #4CAF50;">+100% Bullet Speed</p>
132
+ <button onclick="buyAPCR()">Buy</button>
133
+ </div>
134
  </div>
135
  </div>
136
+ <button id="bossButton" class="button">Fight Boss!</button>
137
+ <div id="winMessage" class="button" style="font-size: 72px; background: none;">You Win!</div>
138
+
139
+
140
+ <script>
141
+ const canvas = document.getElementById('gameCanvas');
142
+ const ctx = canvas.getContext('2d');
143
+ const nextRoundBtn = document.getElementById('nextRound');
144
+ const restartBtn = document.getElementById('restart');
145
+ const weaponInfo = document.getElementById('weaponInfo');
146
+ const countdownEl = document.getElementById('countdown');
147
+ const bossButton = document.getElementById('bossButton');
148
+ canvas.width = window.innerWidth;
149
+ canvas.height = window.innerHeight;
150
+ // Game state
151
+ let currentRound = 1;
152
+ let gameOver = false;
153
+ let currentWeapon = 'cannon';
154
+ let enemies = [];
155
+ let bullets = [];
156
+ let items = [];
157
+ let lastShot = 0;
158
+ let isCountingDown = true;
159
+ let countdownTime = 3;
160
+ let autoFire = false;
161
+ let gold = 0;
162
+ let isBossStage = false;
163
+ let effects = [];
164
+ let hasAPCR = false; // APCR 구매 여부
165
+ let hasBF109 = false; // BF-109 구매 여부
166
+ let hasJU87 = false; // JU-87 구매 여부
167
+ let lastJU87Spawn = 0; // 마지막 JU-87 생성 시간
168
+ let supportUnits = []; // 지원 유닛 배열
169
+ let lastSupportSpawn = 0; // 마지막 지원 유닛 생성 시간
170
+ // Load assets
171
+ const backgroundImg = new Image();
172
+ backgroundImg.src = 'city.png';
173
+ const playerImg = new Image();
174
+ playerImg.src = 'player.png';
175
+ const enemyImg = new Image();
176
+ enemyImg.src = 'enemy.png';
177
+ const bulletImg = new Image(); // APCR 총알 이미지
178
+ bulletImg.src = 'apcr2.png';
179
+ // Audio setup
180
+ const cannonSound = new Audio('firemn.ogg');
181
+ const machinegunSound = new Audio('firemg.ogg');
182
+ const enemyFireSound = new Audio('fireenemy.ogg');
183
+ const bgm = new Audio('BGM2.ogg');
184
+ const countSound = new Audio('count.ogg');
185
+ const deathSound = new Audio('death.ogg');
186
+ bgm.loop = true;
187
+ enemyFireSound.volume = 0.5;
188
+ const weapons = {
189
+ cannon: {
190
+ fireRate: 1000,
191
+ damage: 0.25,
192
+ bulletSize: 5,
193
+ sound: cannonSound
194
+ },
195
+ machinegun: {
196
+ fireRate: 200,
197
+ damage: 0.05,
198
+ bulletSize: 2,
199
+ sound: machinegunSound
200
+ }
201
+ };
202
+ // Player setup
203
+ const player = {
204
+ x: canvas.width/2,
205
+ y: canvas.height/2,
206
+ speed: 5,
207
+ angle: 0,
208
+ width: 100,
209
+ height: 45,
210
+ health: 1000,
211
+ maxHealth: 1000
212
+ };
213
+ function startCountdown() {
214
+ isCountingDown = true;
215
+ countdownTime = 3;
216
+ countdownEl.style.display = 'block';
217
+ countdownEl.textContent = countdownTime;
218
+ bgm.pause();
219
+ countSound.play();
220
+ const countInterval = setInterval(() => {
221
+ countdownTime--;
222
+ if(countdownTime <= 0) {
223
+ clearInterval(countInterval);
224
+ countdownEl.style.display = 'none';
225
+ isCountingDown = false;
226
+ bgm.play();
227
+ }
228
+ countdownEl.textContent = countdownTime > 0 ? countdownTime : 'GO!';
229
+ }, 1000);
230
+ }
231
+
232
+ class Effect {
233
+ constructor(x, y, duration, type, angle = 0, parent = null) {
234
+ this.x = x;
235
+ this.y = y;
236
+ this.startTime = Date.now();
237
+ this.duration = duration;
238
+ this.type = type;
239
+ this.angle = angle;
240
+ this.parent = parent; // 부모 유닛 (발사한 유닛)
241
+ this.offset = { x: Math.cos(angle) * 30, y: Math.sin(angle) * 30 }; // 부모로부터의 오프셋
242
+ this.img = new Image();
243
+ this.img.src = type === 'death' ? 'bang.png' : 'fire2.png';
244
  }
 
245
 
246
+ update() {
247
+ if(this.parent && this.type === 'fire') {
248
+ this.x = this.parent.x + this.offset.x;
249
+ this.y = this.parent.y + this.offset.y;
250
+ this.angle = this.parent.angle;
251
+ }
252
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
253
 
254
+ isExpired() {
255
+ return Date.now() - this.startTime > this.duration;
 
 
256
  }
257
  }
258
+ class SupportUnit {
259
+ constructor(yPosition) {
260
+ this.x = 0;
261
+ this.y = yPosition;
262
+ this.speed = 5;
263
+ this.lastShot = 0;
264
+ this.width = 100;
265
+ this.height = 100;
266
+ this.angle = 0;
267
+ this.img = new Image();
268
+ this.img.src = 'bf109.png';
269
+ this.hasPlayedSound = false; // 첫 등장 소리용
270
+ this.mgSound = null; // 기관총 소리 객체
271
+ }
272
+
273
+ update() {
274
+ // 이동
275
+ this.x += this.speed;
276
+
277
+ // 카운트다운 중이면 소리 정지 및 초기화
278
+ if (isCountingDown) {
279
+ if (this.mgSound) {
280
+ this.mgSound.pause();
281
+ this.mgSound.currentTime = 0;
282
+ }
283
+ this.hasPlayedSound = false;
284
  }
 
 
 
285
 
286
+ // 발사 (1초에 5발)
287
+ const now = Date.now();
288
+ if (now - this.lastShot > 200 && !isCountingDown) {
289
+ this.shoot();
290
+ this.lastShot = now;
291
+ }
292
+ return this.x < canvas.width;
293
  }
 
 
 
 
 
294
 
295
+ shoot() {
296
+ // 최초 등장시에만 bf109mg.ogg 재생
297
+ if (!this.hasPlayedSound) {
298
+ const firstSound = new Audio('bf109mg.ogg');
299
+ firstSound.volume = 1.0;
300
+ firstSound.play();
301
+ this.hasPlayedSound = true;
302
+ }
303
 
304
+ // 발사할 때마다 새로운 bf109mgse.ogg 재생
305
+ if (!isCountingDown) {
306
+ const shootSound = new Audio('bf109mgse.ogg');
307
+ shootSound.volume = 0.5; // 볼륨 낮춤
308
+ shootSound.play();
309
  }
310
+
311
+ bullets.push({
312
+ x: this.x + Math.cos(this.angle) * 30,
313
+ y: this.y + Math.sin(this.angle) * 30,
314
+ angle: this.angle,
315
+ speed: 10,
316
+ isEnemy: false,
317
+ damage: weapons.machinegun.damage,
318
+ size: weapons.machinegun.bulletSize
319
+ });
320
+ }
321
  }
322
+ class JU87 {
323
+ constructor() {
324
+ this.x = canvas.width;
325
+ this.y = 50;
326
+ this.speed = 5;
327
+ this.width = 100;
328
+ this.height = 100;
329
+ this.angle = Math.PI;
330
+ this.img = new Image();
331
+ this.img.src = 'ju87.png';
332
+ this.target = null;
333
+ this.lastShot = 0;
334
+ this.spawnTime = Date.now();
335
+ this.hasPlayedSound = false;
336
+ this.hasPlayedMGSound = false;
337
+ this.isReturning = false;
338
+ this.circleAngle = 0;
339
+ this.returningToCenter = false; // 중앙으로 돌아가는 상태 추가
340
+ }
341
+
342
+ selectTarget() {
343
+ return enemies.length > 0 ?
344
+ enemies[Math.floor(Math.random() * enemies.length)] : null;
345
+ }
346
+
347
+ shoot() {
348
+ // 카운트다운 중이 아닐 때만 소리 재생
349
+ if (!this.hasPlayedMGSound && !isCountingDown) {
350
+ const mgSound = new Audio('ju87mg.ogg');
351
+ mgSound.volume = 1.0;
352
+ mgSound.currentTime = 0;
353
+ mgSound.play().catch(error => console.error('Audio play failed:', error));
354
+ this.hasPlayedMGSound = true;
355
+ }
356
+
357
+ // 100x100 픽셀 기준으로 날개 위치 좌표 설정
358
+ [[20, 50], [80, 50]].forEach(([x, y]) => {
359
+ const offsetX = x - 50;
360
+ const offsetY = y - 50;
361
+
362
+ const rotatedX = this.x + (Math.cos(this.angle) * offsetX - Math.sin(this.angle) * offsetY);
363
+ const rotatedY = this.y + (Math.sin(this.angle) * offsetX + Math.cos(this.angle) * offsetY);
364
 
365
+ bullets.push({
366
+ x: rotatedX,
367
+ y: rotatedY,
368
+ angle: this.angle,
369
+ speed: 10,
370
+ isEnemy: false,
371
+ damage: weapons.machinegun.damage * 2,
372
+ size: weapons.machinegun.bulletSize
373
+ });
374
+ });
375
  }
376
+ update() {
377
+ if (!this.hasPlayedSound) {
378
+ const sirenSound = new Audio('ju87siren.ogg');
379
+ sirenSound.volume = 1.0;
380
+ sirenSound.play().catch(error => console.error('Audio play failed:', error));
381
+ this.hasPlayedSound = true;
382
+ }
383
 
384
+ const timeSinceSpawn = Date.now() - this.spawnTime;
385
+ const centerX = canvas.width / 2;
386
+ const centerY = canvas.height / 2;
387
+
388
+ if (timeSinceSpawn > 5000) {
389
+ if (!this.isReturning) {
390
+ this.isReturning = true;
391
+ this.target = null;
392
+ this.angle = Math.atan2(centerY - this.y, centerX - this.x);
393
+ } else {
394
+ this.angle = Math.PI;
395
+ this.x -= this.speed;
396
+ return this.x > 0;
397
+ }
398
+ } else if (this.returningToCenter) {
399
+ const distToCenter = Math.hypot(this.x - centerX, this.y - centerY);
400
+ if (distToCenter > 50) {
401
+ this.angle = Math.atan2(centerY - this.y, centerX - this.x);
402
+ } else {
403
+ this.returningToCenter = false;
404
+ this.target = this.selectTarget();
405
+ }
406
+ } else if (this.target) {
407
+ const distToTarget = Math.hypot(this.x - this.target.x, this.y - this.target.y);
408
+
409
+ if (!enemies.includes(this.target) || distToTarget < 30) {
410
+ this.returningToCenter = true;
411
+ this.target = null;
412
+ } else {
413
+ this.angle = Math.atan2(this.target.y - this.y, this.target.x - this.x);
414
+ }
415
+ } else {
416
+ this.target = this.selectTarget();
417
+
418
+ if (!this.target) {
419
+ this.circleAngle += 0.02;
420
+ const radius = Math.min(canvas.width, canvas.height) / 4;
421
+ const targetX = centerX + Math.cos(this.circleAngle) * radius;
422
+ const targetY = centerY + Math.sin(this.circleAngle) * radius;
423
+ this.angle = Math.atan2(targetY - this.y, targetX - this.x);
424
+ }
425
+ }
426
+
427
+ this.x += Math.cos(this.angle) * this.speed;
428
+ this.y += Math.sin(this.angle) * this.speed;
429
+
430
+ if (!this.returningToCenter && !this.isReturning && this.target &&
431
+ Date.now() - this.lastShot > 200) {
432
+ this.shoot();
433
+ this.lastShot = Date.now();
434
+ }
435
+
436
+ return true;
437
+ }
438
+ }
439
+
440
+ class Enemy {
441
+ constructor(isBoss = false) {
442
+ this.x = Math.random() * canvas.width;
443
+ this.y = Math.random() * canvas.height;
444
+ this.health = isBoss ? 15000 : 1000;
445
+ this.maxHealth = this.health;
446
+ this.speed = isBoss ? 1 : 2;
447
+ this.lastShot = 0;
448
+ this.shootInterval = isBoss ? 1000 : 1000;
449
+ this.angle = 0;
450
+ this.width = 100;
451
+ this.height = 45;
452
+ this.moveTimer = 0;
453
+ this.moveInterval = Math.random() * 2000 + 1000;
454
+ this.moveAngle = Math.random() * Math.PI * 2;
455
+ this.isBoss = isBoss;
456
+
457
+ if (isBoss) {
458
+ this.enemyImg = new Image();
459
+ this.enemyImg.src = 'boss.png';
460
+ } else if (currentRound >= 7) {
461
+ this.enemyImg = new Image();
462
+ this.enemyImg.src = 'enemy3.png';
463
+ } else if (currentRound >= 4) {
464
+ this.enemyImg = new Image();
465
+ this.enemyImg.src = 'enemy2.png';
466
+ }
467
+ }
468
+ update() {
469
+ if(isCountingDown) return;
470
+ const now = Date.now();
471
+
472
+ if (now - this.moveTimer > this.moveInterval) {
473
+ this.moveAngle = Math.random() * Math.PI * 2;
474
+ this.moveTimer = now;
475
+ }
476
+ this.x += Math.cos(this.moveAngle) * this.speed;
477
+ this.y += Math.sin(this.moveAngle) * this.speed;
478
+ this.x = Math.max(this.width/2, Math.min(canvas.width - this.width/2, this.x));
479
+ this.y = Math.max(this.height/2, Math.min(canvas.height - this.height/2, this.y));
480
+ this.angle = Math.atan2(player.y - this.y, player.x - this.x);
481
+
482
+ if (now - this.lastShot > this.shootInterval && !isCountingDown) {
483
+ this.shoot();
484
+ this.lastShot = now;
485
+ }
486
+ }
487
+ shoot() {
488
+ const sound = this.isBoss ? new Audio('firemn.ogg') : enemyFireSound.cloneNode();
489
+ sound.play();
490
+
491
+ // 발사 이펙트 추가
492
+ effects.push(new Effect(
493
+ this.x + Math.cos(this.angle) * 30,
494
+ this.y + Math.sin(this.angle) * 30,
495
+ 500,
496
+ 'fire',
497
+ this.angle,
498
+ this // 자신을 부모로 전달
499
+ ));
500
+
501
+ bullets.push({
502
+ x: this.x + Math.cos(this.angle) * 30,
503
+ y: this.y + Math.sin(this.angle) * 30,
504
+ angle: this.angle,
505
+ speed: this.isBoss ? 10 : 5,
506
+ isEnemy: true,
507
+ size: this.isBoss ? 5 : 3,
508
+ damage: this.isBoss ? 300 : 150
509
+ });
510
+ }
511
+ }
512
+ function showShop() {
513
+ document.getElementById('shop').style.display = 'block';
514
+ }
515
+ // 플레이어의 기본 상태를 저장
516
  const defaultPlayerStats = {
517
  maxHealth: 1000,
518
  speed: 5,
519
  width: 100,
520
  height: 45
521
  };
 
522
  function buyTank(tankImg, cost, tankId) {
523
  if (gold >= cost) {
524
  gold -= cost;
 
526
  document.getElementById(tankId).style.display = 'none';
527
  document.getElementById('shop').style.display = 'none';
528
 
529
+ if (tankId === 'tank1') { // PZ.IV
530
  player.maxHealth = 1500;
531
+ player.speed = defaultPlayerStats.speed; // 기본 이동속도로 복구
532
+ player.width = 90; // PZ.IV의 크기 설정
533
+ player.height = 50; // PZ.IV의 크기 설정
534
+ }
535
+ else if (tankId === 'tank2') { // TIGER
536
  player.maxHealth = 2000;
537
  player.speed = defaultPlayerStats.speed * 0.7;
538
+ player.width = 100; // TIGER는 기본 크기 유지
539
+ player.height = 45; // TIGER는 기본 크기 유지
540
  }
541
+
542
  player.health = player.maxHealth;
543
  }
544
  }
545
+ function buyAPCR() {
 
546
  if (gold >= 1000 && !hasAPCR) {
547
  gold -= 1000;
548
  hasAPCR = true;
 
550
  document.getElementById('shop').style.display = 'none';
551
  }
552
  }
553
+ function buyBF109() {
 
554
  if (gold >= 1000 && !hasBF109) {
555
  gold -= 1000;
556
  hasBF109 = true;
 
558
  document.getElementById('shop').style.display = 'none';
559
  }
560
  }
561
+ function buyJU87() {
 
562
  if (gold >= 1500 && !hasJU87) {
563
  gold -= 1500;
564
  hasJU87 = true;
565
  document.getElementById('ju87').style.display = 'none';
566
  document.getElementById('shop').style.display = 'none';
567
+ lastJU87Spawn = Date.now(); // 구매 즉시 스폰 타이머 초기화
568
  }
569
  }
570
+ function initRound() {
571
+ enemies = [];
572
+ for(let i = 0; i < 1 * currentRound; i++) {
573
+ enemies.push(new Enemy());
574
+ }
575
+ player.health = player.maxHealth;
576
+ bullets = [];
577
+ items = [];
578
+ supportUnits = [];
579
+ lastSupportSpawn = 0;
580
+
581
+ // 카운트다운 시작
582
+ startCountdown();
583
+
584
+ // 카운트다운이 끝나면 JU87 스폰
585
+ setTimeout(() => {
586
+ if (hasJU87) {
587
+ supportUnits.push(new JU87());
588
+ lastJU87Spawn = Date.now();
589
+ }
590
+ }, 3000); // 3초 후에 스폰
591
+ }
592
+ function startBossStage() {
593
+ isBossStage = true;
594
+ enemies = [];
595
+ enemies.push(new Enemy(true));
596
+ player.health = player.maxHealth;
597
+ bullets = [];
598
+ items = [];
599
+ document.getElementById('bossButton').style.display = 'none';
600
+ bgm.src = 'BGM.ogg'; // 보스전 BGM으로 변경
601
+ startCountdown();
602
+ }
603
+ canvas.addEventListener('mousemove', (e) => {
604
+ player.angle = Math.atan2(e.clientY - player.y, e.clientX - player.x);
605
+ });
606
+ const keys = {};
607
+ document.addEventListener('keydown', e => {
608
+ keys[e.key] = true;
609
+ if(e.key.toLowerCase() === 'c') {
610
+ currentWeapon = currentWeapon === 'cannon' ? 'machinegun' : 'cannon';
611
+ weaponInfo.textContent = `Current Weapon: ${currentWeapon.charAt(0).toUpperCase() + currentWeapon.slice(1)}`;
612
+ } else if(e.key.toLowerCase() === 'r') {
613
+ autoFire = !autoFire;
614
+ }
615
+ });
616
+
617
+ document.addEventListener('keyup', e => keys[e.key] = false);
618
+ function fireBullet() {
619
+ if(isCountingDown) return;
620
+ const weapon = weapons[currentWeapon];
621
+ const now = Date.now();
622
+ if ((keys[' '] || autoFire) && now - lastShot > weapon.fireRate) {
623
+ weapon.sound.cloneNode().play();
624
+ effects.push(new Effect(
625
+ player.x + Math.cos(player.angle) * 30,
626
+ player.y + Math.sin(player.angle) * 30,
627
+ 500,
628
+ 'fire',
629
+ player.angle,
630
+ player
631
+ ));
632
+
633
+ bullets.push({
634
+ x: player.x + Math.cos(player.angle) * 30,
635
+ y: player.y + Math.sin(player.angle) * 30,
636
+ angle: player.angle,
637
+ speed: hasAPCR ? 20 : 10, // APCR 적용 시 100% 증가
638
+ isEnemy: false,
639
+ damage: weapon.damage,
640
+ size: weapon.bulletSize,
641
+ isAPCR: hasAPCR
642
+ });
643
+ lastShot = now;
644
+ }
645
+ }
646
+ function updateGame() {
647
  if(gameOver) return;
648
  if(!isCountingDown) {
649
  // 플레이어 움직임
 
651
  if(keys['s']) player.y += player.speed;
652
  if(keys['a']) player.x -= player.speed;
653
  if(keys['d']) player.x += player.speed;
 
654
  player.x = Math.max(player.width/2, Math.min(canvas.width - player.width/2, player.x));
655
  player.y = Math.max(player.height/2, Math.min(canvas.height - player.height/2, player.y));
 
656
  fireBullet();
657
  }
658
 
659
+ // BF109 관련 코드
660
  if (hasBF109 && !isCountingDown) {
661
  const now = Date.now();
662
+ if (now - lastSupportSpawn > 10000) { // 10초마다
663
  supportUnits.push(
664
  new SupportUnit(canvas.height * 0.2),
665
  new SupportUnit(canvas.height * 0.5),
 
669
  }
670
  }
671
 
672
+ // JU87 관련 코드 추가
673
  if (hasJU87 && !isCountingDown) {
674
  const now = Date.now();
675
+ if (now - lastJU87Spawn > 15000) { // 15초마다
676
  supportUnits.push(new JU87());
677
  lastJU87Spawn = now;
678
  }
679
  }
680
 
681
+ // 모든 지원 유닛 업데이트 (BF109와 JU87 모두 처리)
682
  supportUnits = supportUnits.filter(unit => unit.update());
683
 
 
684
  enemies.forEach(enemy => enemy.update());
685
+ if(!isCountingDown) {
686
+ bullets = bullets.filter(bullet => {
687
+ bullet.x += Math.cos(bullet.angle) * bullet.speed;
688
+ bullet.y += Math.sin(bullet.angle) * bullet.speed;
689
+ if(!bullet.isEnemy) {
690
+ enemies = enemies.filter(enemy => {
691
+ const dist = Math.hypot(bullet.x - enemy.x, bullet.y - enemy.y);
692
+ if(dist < 30) {
693
+ let damage = currentWeapon === 'cannon' ? 250 : 50; // 고정 데미지로 변경
694
+ enemy.health -= damage;
695
+ if(enemy.health <= 0) {
696
+ spawnHealthItem(enemy.x, enemy.y);
697
+ gold += 100;
698
+ // 죽음 이펙트와 사운드 추가
699
+ effects.push(new Effect(enemy.x, enemy.y, 1000, 'death'));
700
+ deathSound.cloneNode().play();
701
+ return false;
702
+ }
703
+ if(player.health <= 0) {
704
+ gameOver = true;
705
+ restartBtn.style.display = 'block';
706
+ effects.push(new Effect(player.x, player.y, 1000, 'death'));
707
+ deathSound.cloneNode().play();
708
+ }
709
+ return true;
710
+ }
711
+ return true;
712
+ });
713
+ } else {
714
+ const dist = Math.hypot(bullet.x - player.x, bullet.y - player.y);
715
+ if(dist < 30) {
716
+ player.health -= bullet.damage || 100;
717
+ if(player.health <= 0) {
718
+ gameOver = true;
719
+ restartBtn.style.display = 'block';
720
+ }
721
  return false;
722
  }
 
723
  }
724
+ return bullet.x >= 0 && bullet.x <= canvas.width &&
725
+ bullet.y >= 0 && bullet.y <= canvas.height;
726
  });
727
+ items = items.filter(item => {
728
+ const dist = Math.hypot(item.x - player.x, item.y - player.y);
729
+ if(dist < 30) {
730
+ player.health = Math.min(player.health + 200, player.maxHealth);
731
+ return false;
 
 
 
 
732
  }
733
+ return true;
734
+ });
735
+ if(enemies.length === 0) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
736
  if (!isBossStage) {
737
  if(currentRound < 10) {
738
  nextRoundBtn.style.display = 'block';
739
  showShop();
740
  } else {
741
+ document.getElementById('bossButton').style.display = 'block';
742
  }
743
  } else {
744
  gameOver = true;
745
  document.getElementById('winMessage').style.display = 'block';
746
  restartBtn.style.display = 'block';
747
+ bgm.pause(); // 현재 BGM 정지
748
+ const victorySound = new Audio('victory.ogg'); // 승리 사운드 생성
749
+ victorySound.play(); // 승리 사운드 재생
750
  }
751
  }
752
  }
753
+ enemies.forEach(enemy => enemy.update());
754
  }
755
+ function spawnHealthItem(x, y) {
756
+ items.push({x, y});
757
+ }
758
+ function drawHealthBar(x, y, health, maxHealth, width, height, color) {
759
+ ctx.fillStyle = '#333';
760
+ ctx.fillRect(x - width/2, y - height/2, width, height);
761
+ ctx.fillStyle = color;
762
+ ctx.fillRect(x - width/2, y - height/2, width * (health/maxHealth), height);
763
+ }
764
+ function drawGame() {
765
  ctx.clearRect(0, 0, canvas.width, canvas.height);
 
 
766
  const pattern = ctx.createPattern(backgroundImg, 'repeat');
767
  ctx.fillStyle = pattern;
768
  ctx.fillRect(0, 0, canvas.width, canvas.height);
 
774
  ctx.drawImage(playerImg, -player.width/2, -player.height/2, player.width, player.height);
775
  ctx.restore();
776
 
777
+ // 체력바
778
  drawHealthBar(canvas.width/2, 30, player.health, player.maxHealth, 200, 20, 'green');
779
 
780
  // 적 그리기
 
787
  ctx.restore();
788
  drawHealthBar(enemy.x, enemy.y - 40, enemy.health, enemy.maxHealth, 60, 5, 'red');
789
  });
790
+ supportUnits.forEach(unit => {
 
 
791
  ctx.save();
792
  ctx.translate(unit.x, unit.y);
793
  ctx.rotate(unit.angle);
794
  ctx.drawImage(unit.img, -unit.width/2, -unit.height/2, unit.width, unit.height);
795
  ctx.restore();
796
  });
797
+ // 총알 그리기
798
+ bullets.forEach(bullet => {
799
+ if (bullet.isEnemy || !bullet.isAPCR) {
800
+ ctx.beginPath();
801
+ ctx.fillStyle = bullet.isEnemy ? 'red' : 'blue';
802
+ ctx.arc(bullet.x, bullet.y, bullet.size, 0, Math.PI * 2);
803
+ ctx.fill();
804
+ } else {
805
+ ctx.save();
806
+ ctx.translate(bullet.x, bullet.y);
807
+ ctx.rotate(bullet.angle);
808
+ // 기관총일 때 크기 50% 감소
809
+ const width = currentWeapon === 'machinegun' ? 10 : 20; // 기관총일 때 10, 캐논일 때 20
810
+ const height = currentWeapon === 'machinegun' ? 5 : 10; // 기관총일 때 5, 캐논일 때 10
811
+ ctx.drawImage(bulletImg, -width/2, -height/2, width, height);
812
+ ctx.restore();
813
+ }
814
+ });
 
815
  // 아이템 그리기
816
  items.forEach(item => {
817
  ctx.beginPath();
 
819
  ctx.arc(item.x, item.y, 10, 0, Math.PI * 2);
820
  ctx.fill();
821
  });
822
+
823
  // UI 그리기
824
  ctx.fillStyle = 'white';
825
  ctx.font = '24px Arial';
 
827
  ctx.fillText(`Gold: ${gold}`, 10, 60);
828
 
829
  // 이펙트 그리기
830
+ effects = effects.filter(effect => !effect.isExpired());
831
+ effects.forEach(effect => {
832
+ effect.update(); // 이펙트 위치 업데이트
833
+ ctx.save();
834
+ ctx.translate(effect.x, effect.y);
835
+ if(effect.type === 'fire') ctx.rotate(effect.angle);
836
+ // bang.png는 1.5배 크게
837
+ const size = effect.type === 'death' ? 75 : 42; // death는 75px (1.5배), fire는 42px
838
+ ctx.drawImage(effect.img, -size/2, -size/2, size, size);
839
+ ctx.restore();
840
+ });
841
 
842
+ if(isCountingDown) {
 
843
  ctx.fillStyle = 'rgba(0, 0, 0, 0.5)';
844
  ctx.fillRect(0, 0, canvas.width, canvas.height);
845
  }
846
  }
847
 
848
+ // drawGame 함수 밖으로 이동
849
  function gameLoop() {
850
+ updateGame();
851
+ drawGame();
852
+ requestAnimationFrame(gameLoop);
 
 
853
  }
854
 
 
855
  nextRoundBtn.addEventListener('click', () => {
856
  currentRound++;
857
  nextRoundBtn.style.display = 'none';
 
859
  initRound();
860
  });
861
 
862
+ bossButton.addEventListener('click', startBossStage);
 
 
863
 
864
+ restartBtn.addEventListener('click', () => {
865
+ currentRound = 1;
866
+ gameOver = false;
867
+ isBossStage = false;
868
+ gold = 0;
869
+ hasAPCR = false; // APCR 초기화
870
+ hasBF109 = false; // BF109 초기화
871
+ hasJU87 = false;
872
+ supportUnits = []; // 지원 유닛 배열 초기화
873
+
874
+ restartBtn.style.display = 'none';
875
+ document.getElementById('winMessage').style.display = 'none';
876
+ document.getElementById('tank1').style.display = 'block';
877
+ document.getElementById('tank2').style.display = 'block';
878
+ document.getElementById('apcr').style.display = 'block';
879
+ document.getElementById('bf109').style.display = 'block'; // BF109 상점 아이템 다시 표시
880
+
881
+ playerImg.src = 'player.png';
882
+ bgm.src = 'BGM2.ogg';
883
+ bgm.play();
884
+ initRound();
885
  });
886
 
887
+ Promise.all([
888
+ new Promise(resolve => backgroundImg.onload = resolve),
889
+ new Promise(resolve => playerImg.onload = resolve),
890
+ new Promise(resolve => enemyImg.onload = resolve)
891
+ ]).then(() => {
892
+ initRound();
893
+ gameLoop();
894
+ bgm.play();
895
  });
896
 
 
897
  window.addEventListener('resize', () => {
898
  canvas.width = window.innerWidth;
899
  canvas.height = window.innerHeight;
900
  });
901
+ </script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
902
  </body>
903
+ </html>