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

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +803 -658
index.html CHANGED
@@ -2,7 +2,7 @@
2
  <html>
3
  <head>
4
  <title>Tank Battle</title>
5
- <style>
6
  body {
7
  margin: 0;
8
  overflow: hidden;
@@ -48,6 +48,17 @@
48
  display: none;
49
  z-index: 1000;
50
  }
 
 
 
 
 
 
 
 
 
 
 
51
  #countdown {
52
  position: fixed;
53
  top: 50%;
@@ -59,22 +70,52 @@
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>
@@ -92,50 +133,59 @@
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;">
99
- <h3>PZ.IV</h3>
100
- <img src="player2.png" width="90" height="50">
101
- <p>300 Gold</p>
102
- <p style="color: #4CAF50;">+50% HP</p>
103
- <button onclick="buyTank('player2.png', 300, 'tank1')">Buy</button>
104
- </div>
105
- <div id="tank2" style="text-align:center;">
106
- <h3>TIGER</h3>
107
- <img src="player3.png" width="110" height="55">
108
- <p>500 Gold</p>
109
- <p style="color: #4CAF50;">+100% HP</p>
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');
@@ -147,6 +197,7 @@
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;
@@ -161,12 +212,14 @@
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';
@@ -174,17 +227,19 @@
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,
@@ -199,7 +254,7 @@
199
  sound: machinegunSound
200
  }
201
  };
202
- // Player setup
203
  const player = {
204
  x: canvas.width/2,
205
  y: canvas.height/2,
@@ -210,6 +265,28 @@
210
  health: 1000,
211
  maxHealth: 1000
212
  };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
213
  function startCountdown() {
214
  isCountingDown = true;
215
  countdownTime = 3;
@@ -217,6 +294,7 @@
217
  countdownEl.textContent = countdownTime;
218
  bgm.pause();
219
  countSound.play();
 
220
  const countInterval = setInterval(() => {
221
  countdownTime--;
222
  if(countdownTime <= 0) {
@@ -228,381 +306,379 @@
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;
525
- playerImg.src = tankImg;
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;
549
- document.getElementById('apcr').style.display = 'none';
550
- document.getElementById('shop').style.display = 'none';
551
- }
552
- }
553
- function buyBF109() {
554
- if (gold >= 1000 && !hasBF109) {
555
- gold -= 1000;
556
- hasBF109 = true;
557
- document.getElementById('bf109').style.display = 'none';
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;
@@ -613,9 +689,30 @@ function buyTank(tankImg, cost, tankId) {
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();
@@ -629,12 +726,12 @@ function buyTank(tankImg, cost, tankId) {
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,
@@ -643,261 +740,309 @@ function buyTank(tankImg, cost, tankId) {
643
  lastShot = now;
644
  }
645
  }
646
- function updateGame() {
647
- if(gameOver) return;
648
- if(!isCountingDown) {
649
- // 플레이어 움직임
650
- if(keys['w']) player.y -= player.speed;
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),
666
- new SupportUnit(canvas.height * 0.8)
667
- );
668
- lastSupportSpawn = now;
 
 
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);
769
-
770
- // 플레이어 그리기
771
- ctx.save();
772
- ctx.translate(player.x, player.y);
773
- ctx.rotate(player.angle);
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
- // 적 그리기
781
- enemies.forEach(enemy => {
782
- ctx.save();
783
- ctx.translate(enemy.x, enemy.y);
784
- ctx.rotate(enemy.angle);
785
- const img = enemy.isBoss ? enemy.enemyImg : (enemy.enemyImg || enemyImg);
786
- ctx.drawImage(img, -enemy.width/2, -enemy.height/2, enemy.width, enemy.height);
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();
818
- ctx.fillStyle = 'green';
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';
826
- ctx.fillText(`Round ${currentRound}/10`, 10, 30);
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';
858
- document.getElementById('shop').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>
 
2
  <html>
3
  <head>
4
  <title>Tank Battle</title>
5
+ <style>
6
  body {
7
  margin: 0;
8
  overflow: hidden;
 
48
  display: none;
49
  z-index: 1000;
50
  }
51
+ #nextRound {
52
+ top: 80% !important;
53
+ }
54
+ #restart {
55
+ top: 80% !important; /* 재시작 버튼 위치 추가 */
56
+ }
57
+ #winMessage {
58
+ top: 30% !important; /* 승리 메시지 위치 추가 */
59
+ font-size: 72px;
60
+ background: none;
61
+ }
62
  #countdown {
63
  position: fixed;
64
  top: 50%;
 
70
  z-index: 1000;
71
  display: none;
72
  }
73
+ #titleScreen {
74
+ position: fixed;
75
+ top: 0;
76
+ left: 0;
77
+ width: 100%;
78
+ height: 100%;
79
+ background: url('city2.png') no-repeat center center;
80
+ background-size: cover;
81
+ z-index: 2000;
82
+ display: flex;
83
+ flex-direction: column;
84
+ justify-content: center;
85
+ align-items: center;
86
+ }
87
+ #titleScreen h1 {
88
+ font-size: 72px;
89
+ color: white;
90
+ text-shadow: 2px 2px 5px black;
91
+ margin-bottom: 50px;
92
+ }
93
+ .stageButton {
94
+ padding: 15px 30px;
95
+ font-size: 24px;
96
+ background: #4CAF50;
97
+ color: white;
98
+ border: none;
99
+ border-radius: 5px;
100
+ cursor: pointer;
101
+ margin: 10px;
102
+ }
103
+ .stageButton:disabled {
104
+ background: #666;
105
+ cursor: not-allowed;
106
+ }
107
+ #shop {
108
+ position: fixed;
109
+ top: 30% !important;
110
+ left: 50%;
111
+ transform: translate(-50%, -50%);
112
+ background: rgba(0,0,0,0.9);
113
+ padding: 20px;
114
+ border-radius: 10px;
115
+ color: white;
116
+ z-index: 1000;
117
+ display: none;
118
+ }
119
  </style>
120
  </head>
121
  <body>
 
133
  <button id="restart" class="button">Restart Game</button>
134
  <canvas id="gameCanvas"></canvas>
135
 
136
+ <div id="titleScreen">
137
+ <h1>TANK WAR</h1>
138
+ <div id="stageSelect">
139
+ <button class="stageButton" onclick="startStage(1)">Stage 1</button>
140
+ <button class="stageButton" disabled>Stage 2</button>
141
+ <button class="stageButton" disabled>Stage 3</button>
142
+ <button class="stageButton" disabled>Stage 4</button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
143
  </div>
144
+ </div>
145
+
146
+ <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;">
147
+ <h2>Tank Shop</h2>
148
+ <div style="display:flex; gap:20px;">
149
+ <div id="tank1" style="text-align:center;">
150
+ <h3>PZ.IV</h3>
151
+ <img src="player2.png" width="90" height="50">
152
+ <p>300 Gold</p>
153
+ <p style="color: #4CAF50;">+50% HP</p>
154
+ <button onclick="buyTank('player2.png', 300, 'tank1')">Buy</button>
155
+ </div>
156
+ <div id="tank2" style="text-align:center;">
157
+ <h3>TIGER</h3>
158
+ <img src="player3.png" width="110" height="55">
159
+ <p>500 Gold</p>
160
+ <p style="color: #4CAF50;">+100% HP</p>
161
+ <p style="color: #ff6b6b;">-30% Speed</p>
162
+ <button onclick="buyTank('player3.png', 500, 'tank2')">Buy</button>
163
+ </div>
164
+ <div id="bf109" style="text-align:center;">
165
+ <h3>BF-109</h3>
166
+ <img src="bf109.png" width="100" height="100">
167
+ <p>1000 Gold</p>
168
+ <p style="color: #4CAF50;">Air support from BF-109</p>
169
+ <button onclick="buyBF109()">Buy</button>
170
+ </div>
171
+ <div id="ju87" style="text-align:center;">
172
+ <h3>JU-87</h3>
173
+ <img src="ju87.png" width="100" height="100">
174
+ <p>1500 Gold</p>
175
+ <p style="color: #4CAF50;">Get ju-87 air support</p>
176
+ <button onclick="buyJU87()">Buy</button>
177
+ </div>
178
+ <div id="apcr" style="text-align:center;">
179
+ <h3>APCR</h3>
180
+ <img src="apcr.png" width="80" height="20">
181
+ <p>1000 Gold</p>
182
+ <p style="color: #4CAF50;">+100% Bullet Speed</p>
183
+ <button onclick="buyAPCR()">Buy</button>
184
+ </div>
185
  </div>
 
 
 
 
 
 
 
186
  </div>
 
187
  <button id="bossButton" class="button">Fight Boss!</button>
188
  <div id="winMessage" class="button" style="font-size: 72px; background: none;">You Win!</div>
 
189
 
190
  <script>
191
  const canvas = document.getElementById('gameCanvas');
 
197
  const bossButton = document.getElementById('bossButton');
198
  canvas.width = window.innerWidth;
199
  canvas.height = window.innerHeight;
200
+
201
  // Game state
202
  let currentRound = 1;
203
  let gameOver = false;
 
212
  let gold = 0;
213
  let isBossStage = false;
214
  let effects = [];
215
+ let hasAPCR = false;
216
+ let hasBF109 = false;
217
+ let hasJU87 = false;
218
+ let lastJU87Spawn = 0;
219
+ let supportUnits = [];
220
+ let lastSupportSpawn = 0;
221
+ let gameStarted = false;
222
+
223
  // Load assets
224
  const backgroundImg = new Image();
225
  backgroundImg.src = 'city.png';
 
227
  playerImg.src = 'player.png';
228
  const enemyImg = new Image();
229
  enemyImg.src = 'enemy.png';
230
+ const bulletImg = new Image();
231
  bulletImg.src = 'apcr2.png';
232
+
233
  // Audio setup
234
  const cannonSound = new Audio('firemn.ogg');
235
  const machinegunSound = new Audio('firemg.ogg');
236
  const enemyFireSound = new Audio('fireenemy.ogg');
237
+ let bgm = new Audio('title.ogg');
238
  const countSound = new Audio('count.ogg');
239
  const deathSound = new Audio('death.ogg');
240
  bgm.loop = true;
241
  enemyFireSound.volume = 0.5;
242
+
243
  const weapons = {
244
  cannon: {
245
  fireRate: 1000,
 
254
  sound: machinegunSound
255
  }
256
  };
257
+
258
  const player = {
259
  x: canvas.width/2,
260
  y: canvas.height/2,
 
265
  health: 1000,
266
  maxHealth: 1000
267
  };
268
+
269
+ function startStage(stageNumber) {
270
+ console.log("Starting stage:", stageNumber);
271
+ const titleScreen = document.getElementById('titleScreen');
272
+ if (stageNumber === 1) {
273
+ titleScreen.style.display = 'none';
274
+ document.getElementById('instructions').style.display = 'block';
275
+ document.getElementById('weaponInfo').style.display = 'block';
276
+ document.getElementById('gameCanvas').style.display = 'block';
277
+
278
+ bgm.pause();
279
+ bgm = new Audio('BGM2.ogg');
280
+ bgm.loop = true;
281
+ bgm.play().catch(err => console.error("Error playing game music:", err));
282
+
283
+ gameStarted = true;
284
+ currentRound = 1;
285
+ initRound();
286
+ gameLoop();
287
+ }
288
+ }
289
+
290
  function startCountdown() {
291
  isCountingDown = true;
292
  countdownTime = 3;
 
294
  countdownEl.textContent = countdownTime;
295
  bgm.pause();
296
  countSound.play();
297
+
298
  const countInterval = setInterval(() => {
299
  countdownTime--;
300
  if(countdownTime <= 0) {
 
306
  countdownEl.textContent = countdownTime > 0 ? countdownTime : 'GO!';
307
  }, 1000);
308
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
309
 
310
+ function initRound() {
311
+ console.log(`Initializing round ${currentRound}`);
312
+
313
+ // 버튼 상태 초기화
314
+ nextRoundBtn.style.display = 'none';
315
+ document.getElementById('bossButton').style.display = 'none';
316
+ document.getElementById('shop').style.display = 'none';
317
+ document.getElementById('winMessage').style.display = 'none';
318
 
319
+ // 적 생성
320
+ enemies = [];
321
+ const enemyCount = currentRound;
322
+ for(let i = 0; i < enemyCount; i++) {
323
+ let x, y;
324
+ const edge = Math.floor(Math.random() * 4);
325
+
326
+ switch(edge) {
327
+ case 0: x = Math.random() * canvas.width; y = 0; break;
328
+ case 1: x = canvas.width; y = Math.random() * canvas.height; break;
329
+ case 2: x = Math.random() * canvas.width; y = canvas.height; break;
330
+ case 3: x = 0; y = Math.random() * canvas.height; break;
331
+ }
332
+
333
+ const enemy = new Enemy();
334
+ enemy.x = x;
335
+ enemy.y = y;
336
+ enemies.push(enemy);
337
+ }
338
+
339
+ // 게임 상태 초기화
340
+ player.health = player.maxHealth;
341
+ bullets = [];
342
+ items = [];
343
+ supportUnits = [];
344
+ lastSupportSpawn = 0;
345
 
346
+ console.log(`Round ${currentRound} initialized with ${enemies.length} enemies`);
347
+
348
+ // 카운트다운 시작
349
+ startCountdown();
350
 
351
+ // JU87 스폰 설정
352
+ if (hasJU87) {
353
+ setTimeout(() => {
354
+ supportUnits.push(new JU87());
355
+ lastJU87Spawn = Date.now();
356
+ }, 3000);
357
  }
 
358
  }
359
 
360
+ function checkRoundClear() {
361
+ if(enemies.length === 0) {
362
+ console.log(`Checking round clear: Current round ${currentRound}, Boss stage: ${isBossStage}`);
363
+
364
+ if (!isBossStage) {
365
+ if(currentRound < 10) {
366
+ console.log('Normal round clear - showing next round button and shop');
367
+ nextRoundBtn.style.display = 'block';
368
+ document.getElementById('bossButton').style.display = 'none';
369
+ showShop();
370
+ } else {
371
+ console.log('Final round clear - showing boss button');
372
+ nextRoundBtn.style.display = 'none';
373
+ document.getElementById('bossButton').style.display = 'block';
374
+ document.getElementById('shop').style.display = 'none';
375
+ }
376
+ } else {
377
+ console.log('Boss clear - showing victory message');
378
+ gameOver = true;
379
+ document.getElementById('winMessage').style.display = 'block';
380
+ document.getElementById('bossButton').style.display = 'none';
381
+ nextRoundBtn.style.display = 'none';
382
+ document.getElementById('shop').style.display = 'none';
383
+ restartBtn.style.display = 'block';
384
+ bgm.pause();
385
+ const victorySound = new Audio('victory.ogg');
386
+ victorySound.play();
387
+ }
388
+ }
389
  }
390
 
391
+ function showShop() {
392
+ document.getElementById('shop').style.display = 'block';
 
 
 
393
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
394
 
395
+ const defaultPlayerStats = {
396
+ maxHealth: 1000,
397
+ speed: 5,
398
+ width: 100,
399
+ height: 45
400
+ };
 
 
 
401
 
402
+ // player 부분 수정 (buyTank 함수)
403
+ function buyTank(tankImg, cost, tankId) {
404
+ if (gold >= cost) {
405
+ gold -= cost;
406
+ playerImg.src = tankImg;
407
+ document.getElementById(tankId).style.display = 'none';
408
+ document.getElementById('shop').style.display = 'none';
409
 
410
+ if (tankId === 'tank1') {
411
+ player.maxHealth = 1500;
412
+ player.speed = defaultPlayerStats.speed;
413
+ player.width = 90;
414
+ player.height = 50;
415
+ } else if (tankId === 'tank2') {
416
+ player.maxHealth = 2000;
417
+ player.speed = defaultPlayerStats.speed * 0.7;
418
+ player.width = 100;
419
+ player.height = 45;
420
+ }
421
+
422
+ player.health = player.maxHealth;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
423
  }
424
+ }
425
+
426
+ function buyAPCR() {
427
+ if (gold >= 1000 && !hasAPCR) {
428
+ gold -= 1000;
429
+ hasAPCR = true;
430
+ document.getElementById('apcr').style.display = 'none';
431
+ document.getElementById('shop').style.display = 'none';
 
432
  }
433
  }
434
 
435
+ function buyBF109() {
436
+ if (gold >= 1000 && !hasBF109) {
437
+ gold -= 1000;
438
+ hasBF109 = true;
439
+ document.getElementById('bf109').style.display = 'none';
440
+ document.getElementById('shop').style.display = 'none';
441
+ }
442
+ }
443
 
444
+ function buyJU87() {
445
+ if (gold >= 1500 && !hasJU87) {
446
+ gold -= 1500;
447
+ hasJU87 = true;
448
+ document.getElementById('ju87').style.display = 'none';
449
+ document.getElementById('shop').style.display = 'none';
450
+ lastJU87Spawn = Date.now();
451
+ }
452
  }
453
 
454
+ function updateGame() {
455
+ if(gameOver) return;
456
+ if(!isCountingDown) {
457
+ // 플레이어 움직임
458
+ if(keys['w']) player.y -= player.speed;
459
+ if(keys['s']) player.y += player.speed;
460
+ if(keys['a']) player.x -= player.speed;
461
+ if(keys['d']) player.x += player.speed;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
462
 
463
+ player.x = Math.max(player.width/2, Math.min(canvas.width - player.width/2, player.x));
464
+ player.y = Math.max(player.height/2, Math.min(canvas.height - player.height/2, player.y));
 
 
 
 
 
 
 
465
 
466
+ fireBullet();
467
+ }
468
+
469
+ // BF109 관련 코드
470
+ if (hasBF109 && !isCountingDown) {
471
+ const now = Date.now();
472
+ if (now - lastSupportSpawn > 10000) { // 10초마다
473
+ supportUnits.push(
474
+ new SupportUnit(canvas.height * 0.2),
475
+ new SupportUnit(canvas.height * 0.5),
476
+ new SupportUnit(canvas.height * 0.8)
477
+ );
478
+ lastSupportSpawn = now;
479
  }
480
  }
481
+
482
+ // JU87 관련 코드
483
+ if (hasJU87 && !isCountingDown) {
484
+ const now = Date.now();
485
+ if (now - lastJU87Spawn > 15000) { // 15초마다
486
+ supportUnits.push(new JU87());
487
+ lastJU87Spawn = now;
488
+ }
489
+ }
490
+
491
+ // 지원 유닛 업데이트
492
+ supportUnits = supportUnits.filter(unit => unit.update());
493
+
494
+ // 적 업데이트 - 한 번만 실행
495
+ enemies.forEach(enemy => enemy.update());
496
+
497
+ if(!isCountingDown) {
498
+ // 총알 처리
499
+ bullets = bullets.filter(bullet => {
500
+ bullet.x += Math.cos(bullet.angle) * bullet.speed;
501
+ bullet.y += Math.sin(bullet.angle) * bullet.speed;
502
+
503
+ if(!bullet.isEnemy) {
504
+ enemies = enemies.filter(enemy => {
505
+ const dist = Math.hypot(bullet.x - enemy.x, bullet.y - enemy.y);
506
+ if(dist < 30) {
507
+ let damage = currentWeapon === 'cannon' ? 250 : 50;
508
+ enemy.health -= damage;
509
+ if(enemy.health <= 0) {
510
+ spawnHealthItem(enemy.x, enemy.y);
511
+ gold += 100;
512
+ effects.push(new Effect(enemy.x, enemy.y, 1000, 'death'));
513
+ deathSound.cloneNode().play();
514
+ return false;
515
+ }
516
+ return true;
517
+ }
518
+ return true;
519
+ });
520
+ } else {
521
+ const dist = Math.hypot(bullet.x - player.x, bullet.y - player.y);
522
+ if(dist < 30) {
523
+ player.health -= bullet.damage || 100;
524
+ if(player.health <= 0) {
525
+ gameOver = true;
526
+ restartBtn.style.display = 'block';
527
+ effects.push(new Effect(player.x, player.y, 1000, 'death'));
528
+ deathSound.cloneNode().play();
529
+ }
530
+ return false;
531
+ }
532
+ }
533
+ return bullet.x >= 0 && bullet.x <= canvas.width &&
534
+ bullet.y >= 0 && bullet.y <= canvas.height;
535
+ });
536
+
537
+ // 아이템 처리
538
+ items = items.filter(item => {
539
+ const dist = Math.hypot(item.x - player.x, item.y - player.y);
540
+ if(dist < 30) {
541
+ player.health = Math.min(player.health + 200, player.maxHealth);
542
+ return false;
543
+ }
544
+ return true;
545
+ });
546
+
547
+ // 라운드 클리어 체크
548
+ checkRoundClear();
549
+ }
550
  }
551
+
552
+ function drawGame() {
553
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
554
+
555
+ // 배경 그리기
556
+ const pattern = ctx.createPattern(backgroundImg, 'repeat');
557
+ ctx.fillStyle = pattern;
558
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
559
+
560
+ // 플레이어 그리기
561
+ ctx.save();
562
+ ctx.translate(player.x, player.y);
563
+ ctx.rotate(player.angle);
564
+ ctx.drawImage(playerImg, -player.width/2, -player.height/2, player.width, player.height);
565
+ ctx.restore();
566
+
567
+ // 체력바 그리기
568
+ drawHealthBar(canvas.width/2, 30, player.health, player.maxHealth, 200, 20, 'green');
569
+
570
+ // 적 그리기
571
+ enemies.forEach(enemy => {
572
+ ctx.save();
573
+ ctx.translate(enemy.x, enemy.y);
574
+ ctx.rotate(enemy.angle);
575
+ const img = enemy.isBoss ? enemy.enemyImg : (enemy.enemyImg || enemyImg);
576
+ ctx.drawImage(img, -enemy.width/2, -enemy.height/2, enemy.width, enemy.height);
577
+ ctx.restore();
578
+ drawHealthBar(enemy.x, enemy.y - 40, enemy.health, enemy.maxHealth, 60, 5, 'red');
579
+ });
580
+
581
+ // 지원 유닛 그리기
582
+ supportUnits.forEach(unit => {
583
+ ctx.save();
584
+ ctx.translate(unit.x, unit.y);
585
+ ctx.rotate(unit.angle);
586
+ ctx.drawImage(unit.img, -unit.width/2, -unit.height/2, unit.width, unit.height);
587
+ ctx.restore();
588
+ });
589
+
590
+ // 총알 그리기
591
+ bullets.forEach(bullet => {
592
+ if (bullet.isEnemy || !bullet.isAPCR) {
593
+ ctx.beginPath();
594
+ ctx.fillStyle = bullet.isEnemy ? 'red' : 'blue';
595
+ ctx.arc(bullet.x, bullet.y, bullet.size, 0, Math.PI * 2);
596
+ ctx.fill();
597
+ } else {
598
+ ctx.save();
599
+ ctx.translate(bullet.x, bullet.y);
600
+ ctx.rotate(bullet.angle);
601
+ const width = currentWeapon === 'machinegun' ? 10 : 20;
602
+ const height = currentWeapon === 'machinegun' ? 5 : 10;
603
+ ctx.drawImage(bulletImg, -width/2, -height/2, width, height);
604
+ ctx.restore();
605
+ }
606
+ });
607
+
608
+ // 아이템 그리기
609
+ items.forEach(item => {
610
+ ctx.beginPath();
611
+ ctx.fillStyle = 'green';
612
+ ctx.arc(item.x, item.y, 10, 0, Math.PI * 2);
613
+ ctx.fill();
614
+ });
615
+
616
+ // UI 그리기
617
+ ctx.fillStyle = 'white';
618
+ ctx.font = '24px Arial';
619
+ ctx.fillText(`Round ${currentRound}/10`, 10, 30);
620
+ ctx.fillText(`Gold: ${gold}`, 10, 60);
621
+
622
+ // 이펙트 그리기
623
+ effects = effects.filter(effect => !effect.isExpired());
624
+ effects.forEach(effect => {
625
+ effect.update();
626
+ ctx.save();
627
+ ctx.translate(effect.x, effect.y);
628
+ if (effect.type === 'fire') ctx.rotate(effect.angle);
629
+ const size = effect.type === 'death' ? 75 : 42;
630
+ ctx.drawImage(effect.img, -size/2, -size/2, size, size);
631
+ ctx.restore();
632
+ });
633
+
634
+ // 카운트다운 오버레이
635
+ if (isCountingDown) {
636
+ ctx.fillStyle = 'rgba(0, 0, 0, 0.5)';
637
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
638
+ }
639
  }
640
+
641
+ function gameLoop() {
642
+ if (!gameOver && gameStarted) {
643
+ updateGame();
644
+ drawGame();
645
+ requestAnimationFrame(gameLoop);
646
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
647
  }
648
+
649
+ // 이벤트 리스너
650
+ document.addEventListener('DOMContentLoaded', () => {
651
+ const titleScreen = document.getElementById('titleScreen');
652
+ const instructions = document.getElementById('instructions');
653
+ const weaponInfo = document.getElementById('weaponInfo');
654
+ const gameCanvas = document.getElementById('gameCanvas');
655
+
656
+ instructions.style.display = 'none';
657
+ weaponInfo.style.display = 'none';
658
+ gameCanvas.style.display = 'none';
659
+
660
+ bgm.play().catch(err => console.error("Error playing title music:", err));
661
+
662
+ // 다음 라운드 버튼 클릭 이벤트
663
+ nextRoundBtn.addEventListener('click', () => {
664
+ currentRound++;
665
+ nextRoundBtn.style.display = 'none';
666
+ document.getElementById('shop').style.display = 'none';
667
+ initRound();
668
+ });
669
+
670
+ // 재시작 버튼 클릭 이벤트
671
+ restartBtn.addEventListener('click', () => {
672
+ location.reload();
673
+ });
674
+
675
+ // 보스 버튼 클릭 이벤트
676
+ document.getElementById('bossButton').addEventListener('click', () => {
677
+ startBossStage();
678
+ });
679
  });
680
+
681
+ // 키보드 및 마우스 이벤트
682
  const keys = {};
683
  document.addEventListener('keydown', e => {
684
  keys[e.key] = true;
 
689
  autoFire = !autoFire;
690
  }
691
  });
692
+
693
  document.addEventListener('keyup', e => keys[e.key] = false);
694
+
695
+ canvas.addEventListener('mousemove', (e) => {
696
+ player.angle = Math.atan2(e.clientY - player.y, e.clientX - player.x);
697
+ });
698
+
699
+ window.addEventListener('resize', () => {
700
+ canvas.width = window.innerWidth;
701
+ canvas.height = window.innerHeight;
702
+ });
703
+
704
+ function spawnHealthItem(x, y) {
705
+ items.push({x, y});
706
+ }
707
+
708
+ function drawHealthBar(x, y, health, maxHealth, width, height, color) {
709
+ ctx.fillStyle = '#333';
710
+ ctx.fillRect(x - width/2, y - height/2, width, height);
711
+ ctx.fillStyle = color;
712
+ ctx.fillRect(x - width/2, y - height/2, width * (health/maxHealth), height);
713
+ }
714
+
715
+ function fireBullet() {
716
  if(isCountingDown) return;
717
  const weapon = weapons[currentWeapon];
718
  const now = Date.now();
 
726
  player.angle,
727
  player
728
  ));
729
+
730
  bullets.push({
731
  x: player.x + Math.cos(player.angle) * 30,
732
  y: player.y + Math.sin(player.angle) * 30,
733
  angle: player.angle,
734
+ speed: hasAPCR ? 20 : 10,
735
  isEnemy: false,
736
  damage: weapon.damage,
737
  size: weapon.bulletSize,
 
740
  lastShot = now;
741
  }
742
  }
 
 
 
 
 
 
 
 
 
 
 
 
743
 
744
+ function startBossStage() {
745
+ isBossStage = true;
746
+ enemies = [];
747
+ enemies.push(new Enemy(true));
748
+ player.health = player.maxHealth;
749
+ bullets = [];
750
+ items = [];
751
+ document.getElementById('bossButton').style.display = 'none';
752
+ bgm.src = 'BGM.ogg';
753
+ bgm.loop = true;
754
+ bgm.play();
755
+ startCountdown();
756
  }
 
757
 
758
+ // Enemy 클래스
759
+ class Enemy {
760
+ constructor(isBoss = false) {
761
+ this.x = Math.random() * canvas.width;
762
+ this.y = Math.random() * canvas.height;
763
+ this.health = isBoss ? 15000 : 1000;
764
+ this.maxHealth = this.health;
765
+ this.speed = isBoss ? 1 : 2;
766
+ this.lastShot = 0;
767
+ this.shootInterval = isBoss ? 1000 : 1000;
768
+ this.angle = 0;
769
+ this.width = 100;
770
+ this.height = 45;
771
+ this.moveTimer = 0;
772
+ this.moveInterval = Math.random() * 2000 + 1000;
773
+ this.moveAngle = Math.random() * Math.PI * 2;
774
+ this.isBoss = isBoss;
775
+
776
+ if (isBoss) {
777
+ this.enemyImg = new Image();
778
+ this.enemyImg.src = 'boss.png';
779
+ } else if (currentRound >= 7) {
780
+ this.enemyImg = new Image();
781
+ this.enemyImg.src = 'enemy3.png';
782
+ } else if (currentRound >= 4) {
783
+ this.enemyImg = new Image();
784
+ this.enemyImg.src = 'enemy2.png';
785
+ }
786
+ }
787
+
788
+ update() {
789
+ if(isCountingDown) return;
790
+ const now = Date.now();
791
+
792
+ if (now - this.moveTimer > this.moveInterval) {
793
+ this.moveAngle = Math.random() * Math.PI * 2;
794
+ this.moveTimer = now;
795
+ }
796
+
797
+ // 적 움직임 업데이트
798
+ this.x += Math.cos(this.moveAngle) * this.speed;
799
+ this.y += Math.sin(this.moveAngle) * this.speed;
800
+
801
+ // 화면 경계 처리
802
+ this.x = Math.max(this.width/2, Math.min(canvas.width - this.width/2, this.x));
803
+ this.y = Math.max(this.height/2, Math.min(canvas.height - this.height/2, this.y));
804
+
805
+ // 플레이어를 향해 각도 조정
806
+ this.angle = Math.atan2(player.y - this.y, player.x - this.x);
807
+
808
+ // 발사 타이밍 체크
809
+ if (now - this.lastShot > this.shootInterval && !isCountingDown) {
810
+ this.shoot();
811
+ this.lastShot = now;
812
+ }
813
+ }
814
+
815
+ shoot() {
816
+ const sound = this.isBoss ? new Audio('firemn.ogg') : enemyFireSound.cloneNode();
817
+ sound.play();
818
+
819
+ // 발사 이펙트 추가
820
+ effects.push(new Effect(
821
+ this.x + Math.cos(this.angle) * 30,
822
+ this.y + Math.sin(this.angle) * 30,
823
+ 500,
824
+ 'fire',
825
+ this.angle,
826
+ this
827
+ ));
828
+
829
+ // 총알 생성
830
+ bullets.push({
831
+ x: this.x + Math.cos(this.angle) * 30,
832
+ y: this.y + Math.sin(this.angle) * 30,
833
+ angle: this.angle,
834
+ speed: this.isBoss ? 10 : 5,
835
+ isEnemy: true,
836
+ size: this.isBoss ? 5 : 3,
837
+ damage: this.isBoss ? 300 : 150
838
+ });
839
  }
840
  }
841
+ // Effect 클래스
842
+ class Effect {
843
+ constructor(x, y, duration, type, angle = 0, parent = null) {
844
+ this.x = x;
845
+ this.y = y;
846
+ this.startTime = Date.now();
847
+ this.duration = duration;
848
+ this.type = type;
849
+ this.angle = angle;
850
+ this.parent = parent;
851
+ this.offset = { x: Math.cos(angle) * 30, y: Math.sin(angle) * 30 };
852
+ this.img = new Image();
853
+ this.img.src = type === 'death' ? 'bang.png' : 'fire2.png';
854
+ }
855
 
856
+ update() {
857
+ if(this.parent && this.type === 'fire') {
858
+ this.x = this.parent.x + this.offset.x;
859
+ this.y = this.parent.y + this.offset.y;
860
+ this.angle = this.parent.angle;
861
+ }
862
+ }
863
 
864
+ isExpired() {
865
+ return Date.now() - this.startTime > this.duration;
866
+ }
867
+ }
868
+
869
+ // SupportUnit 클래스
870
+ class SupportUnit {
871
+ constructor(yPosition) {
872
+ this.x = 0;
873
+ this.y = yPosition;
874
+ this.speed = 5;
875
+ this.lastShot = 0;
876
+ this.width = 100;
877
+ this.height = 100;
878
+ this.angle = 0;
879
+ this.img = new Image();
880
+ this.img.src = 'bf109.png';
881
+ this.hasPlayedSound = false;
882
+ this.mgSound = null;
883
+ }
884
+
885
+ update() {
886
+ this.x += this.speed;
887
+
888
+ if (isCountingDown) {
889
+ if (this.mgSound) {
890
+ this.mgSound.pause();
891
+ this.mgSound.currentTime = 0;
 
 
 
 
 
 
 
 
 
 
 
892
  }
893
+ this.hasPlayedSound = false;
894
+ }
895
+
896
+ const now = Date.now();
897
+ if (now - this.lastShot > 200 && !isCountingDown) {
898
+ this.shoot();
899
+ this.lastShot = now;
900
+ }
901
+ return this.x < canvas.width;
902
+ }
903
+
904
+ shoot() {
905
+ if (!this.hasPlayedSound) {
906
+ const firstSound = new Audio('bf109mg.ogg');
907
+ firstSound.volume = 1.0;
908
+ firstSound.play();
909
+ this.hasPlayedSound = true;
910
+ }
911
+
912
+ if (!isCountingDown) {
913
+ const shootSound = new Audio('bf109mgse.ogg');
914
+ shootSound.volume = 0.5;
915
+ shootSound.play();
916
+ }
917
+
918
+ bullets.push({
919
+ x: this.x + Math.cos(this.angle) * 30,
920
+ y: this.y + Math.sin(this.angle) * 30,
921
+ angle: this.angle,
922
+ speed: 10,
923
+ isEnemy: false,
924
+ damage: weapons.machinegun.damage,
925
+ size: weapons.machinegun.bulletSize
926
  });
927
+ }
928
+ }
929
+
930
+ // JU87 클래스
931
+ class JU87 {
932
+ constructor() {
933
+ this.x = canvas.width;
934
+ this.y = 50;
935
+ this.speed = 5;
936
+ this.width = 100;
937
+ this.height = 100;
938
+ this.angle = Math.PI;
939
+ this.img = new Image();
940
+ this.img.src = 'ju87.png';
941
+ this.target = null;
942
+ this.lastShot = 0;
943
+ this.spawnTime = Date.now();
944
+ this.hasPlayedSound = false;
945
+ this.hasPlayedMGSound = false;
946
+ this.isReturning = false;
947
+ this.circleAngle = 0;
948
+ this.returningToCenter = false;
949
+ }
950
+
951
+ selectTarget() {
952
+ return enemies.length > 0 ?
953
+ enemies[Math.floor(Math.random() * enemies.length)] : null;
954
+ }
955
+
956
+ update() {
957
+ if (!this.hasPlayedSound) {
958
+ const sirenSound = new Audio('ju87siren.ogg');
959
+ sirenSound.volume = 1.0;
960
+ sirenSound.play();
961
+ this.hasPlayedSound = true;
962
+ }
963
+
964
+ const timeSinceSpawn = Date.now() - this.spawnTime;
965
+ const centerX = canvas.width / 2;
966
+ const centerY = canvas.height / 2;
967
+
968
+ if (timeSinceSpawn > 5000) {
969
+ if (!this.isReturning) {
970
+ this.isReturning = true;
971
+ this.target = null;
972
+ this.angle = Math.atan2(centerY - this.y, centerX - this.x);
973
+ } else {
974
+ this.angle = Math.PI;
975
+ this.x -= this.speed;
976
+ return this.x > 0;
977
+ }
978
+ } else if (this.returningToCenter) {
979
+ const distToCenter = Math.hypot(this.x - centerX, this.y - centerY);
980
+ if (distToCenter > 50) {
981
+ this.angle = Math.atan2(centerY - this.y, centerX - this.x);
982
+ } else {
983
+ this.returningToCenter = false;
984
+ this.target = this.selectTarget();
985
+ }
986
+ } else if (this.target) {
987
+ const distToTarget = Math.hypot(this.x - this.target.x, this.y - this.target.y);
988
+
989
+ if (!enemies.includes(this.target) || distToTarget < 30) {
990
+ this.returningToCenter = true;
991
+ this.target = null;
992
+ } else {
993
+ this.angle = Math.atan2(this.target.y - this.y, this.target.x - this.x);
994
  }
 
 
 
 
 
 
 
995
  } else {
996
+ this.target = this.selectTarget();
997
+
998
+ if (!this.target) {
999
+ this.circleAngle += 0.02;
1000
+ const radius = Math.min(canvas.width, canvas.height) / 4;
1001
+ const targetX = centerX + Math.cos(this.circleAngle) * radius;
1002
+ const targetY = centerY + Math.sin(this.circleAngle) * radius;
1003
+ this.angle = Math.atan2(targetY - this.y, targetX - this.x);
1004
+ }
1005
+ }
1006
+
1007
+ this.x += Math.cos(this.angle) * this.speed;
1008
+ this.y += Math.sin(this.angle) * this.speed;
1009
+
1010
+ if (!this.returningToCenter && !this.isReturning && this.target &&
1011
+ Date.now() - this.lastShot > 200) {
1012
+ this.shoot();
1013
+ this.lastShot = Date.now();
1014
  }
1015
+
1016
+ return true;
 
 
 
 
 
1017
  }
1018
+
1019
+ shoot() {
1020
+ if (!this.hasPlayedMGSound && !isCountingDown) {
1021
+ const mgSound = new Audio('ju87mg.ogg');
1022
+ mgSound.volume = 1.0;
1023
+ mgSound.play();
1024
+ this.hasPlayedMGSound = true;
1025
+ }
1026
+
1027
+ [[20, 50], [80, 50]].forEach(([x, y]) => {
1028
+ const offsetX = x - 50;
1029
+ const offsetY = y - 50;
1030
+
1031
+ const rotatedX = this.x + (Math.cos(this.angle) * offsetX - Math.sin(this.angle) * offsetY);
1032
+ const rotatedY = this.y + (Math.sin(this.angle) * offsetX + Math.cos(this.angle) * offsetY);
1033
+
1034
+ bullets.push({
1035
+ x: rotatedX,
1036
+ y: rotatedY,
1037
+ angle: this.angle,
1038
+ speed: 10,
1039
+ isEnemy: false,
1040
+ damage: weapons.machinegun.damage * 2,
1041
+ size: weapons.machinegun.bulletSize
1042
+ });
1043
+ });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1044
  }
1045
+ }
1046
+ </script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1047
  </body>
1048
  </html>