cutechicken commited on
Commit
cdc74c6
Β·
verified Β·
1 Parent(s): f781e48

Update game.js

Browse files
Files changed (1) hide show
  1. game.js +301 -259
game.js CHANGED
@@ -18,7 +18,9 @@ const GAME_CONSTANTS = {
18
  MAX_G_FORCE: 12.0,
19
  ENEMY_COUNT: 4,
20
  MISSILE_COUNT: 6,
21
- AMMO_COUNT: 300
 
 
22
  };
23
 
24
  // μ „νˆ¬κΈ° 클래슀
@@ -38,7 +40,7 @@ class Fighter {
38
  this.speed = 350; // 초기 속도 350kt
39
  this.altitude = 2000;
40
  this.gForce = 1.0;
41
- this.health = 100;
42
 
43
  // μ‘°μ’… μž…λ ₯ μ‹œμŠ€ν…œ
44
  this.pitchInput = 0;
@@ -59,6 +61,7 @@ class Fighter {
59
  this.ammo = GAME_CONSTANTS.AMMO_COUNT;
60
  this.bullets = [];
61
  this.lastShootTime = 0;
 
62
 
63
  // μŠ€ν†¨ νƒˆμΆœμ„ μœ„ν•œ Fν‚€ μƒνƒœ
64
  this.escapeKeyPressed = false;
@@ -276,32 +279,32 @@ class Fighter {
276
  }
277
 
278
  updateControls(keys, deltaTime) {
279
- // W/S: μŠ€λ‘œν‹€λ§Œ μ œμ–΄ (가속/감속)
280
- if (keys.w) {
281
- this.throttle = Math.min(1.0, this.throttle + deltaTime * 0.5); // 천천히 가속
282
- }
283
- if (keys.s) {
284
- this.throttle = Math.max(0.1, this.throttle - deltaTime * 0.5); // 천천히 감속
285
- }
286
-
287
- // A/D: 보쑰 μš” μ œμ–΄ (λŸ¬λ”) - λ°˜μ‘μ„± κ°œμ„ 
288
- if (keys.a) {
289
- this.targetYaw -= deltaTime * 1.2; // 0.4μ—μ„œ 1.2둜 증가 (3λ°°)
290
- }
291
- if (keys.d) {
292
- this.targetYaw += deltaTime * 1.2; // 0.4μ—μ„œ 1.2둜 증가 (3λ°°)
 
293
  }
294
- }
295
 
296
  updatePhysics(deltaTime) {
297
- if (!this.mesh) return;
298
-
299
- // λΆ€λ“œλŸ¬μš΄ νšŒμ „ 보간 - Yaw νšŒμ „ 속도 κ°œμ„ 
300
- const rotationSpeed = deltaTime * 2.0;
301
- const yawRotationSpeed = deltaTime * 3.0; // YawλŠ” 더 λΉ λ₯΄κ²Œ λ°˜μ‘ν•˜λ„λ‘ 별도 μ„€μ •
302
-
303
- this.rotation.x = THREE.MathUtils.lerp(this.rotation.x, this.targetPitch, rotationSpeed);
304
- this.rotation.y = THREE.MathUtils.lerp(this.rotation.y, this.targetYaw, yawRotationSpeed); // κ°œμ„ λœ 속도
305
 
306
  // λ‘€ μžλ™ 볡귀 μ‹œμŠ€ν…œ
307
  if (Math.abs(this.targetYaw - this.rotation.y) < 0.05) {
@@ -579,10 +582,9 @@ class Fighter {
579
  }
580
 
581
  shoot(scene) {
582
- const currentTime = Date.now();
583
- if (currentTime - this.lastShootTime < 100 || this.ammo <= 0) return;
584
 
585
- this.lastShootTime = currentTime;
586
  this.ammo--;
587
 
588
  const bulletGeometry = new THREE.SphereGeometry(0.2);
@@ -593,10 +595,14 @@ class Fighter {
593
  });
594
  const bullet = new THREE.Mesh(bulletGeometry, bulletMaterial);
595
 
 
596
  const muzzleOffset = new THREE.Vector3(0, 0, 8);
597
  muzzleOffset.applyEuler(this.rotation);
598
  bullet.position.copy(this.position).add(muzzleOffset);
599
 
 
 
 
600
  const bulletSpeed = 1000;
601
  const direction = new THREE.Vector3(0, 0, 1);
602
  direction.applyEuler(this.rotation);
@@ -605,8 +611,9 @@ class Fighter {
605
  scene.add(bullet);
606
  this.bullets.push(bullet);
607
 
 
608
  try {
609
- const audio = new Audio('sounds/gunfire.ogg');
610
  if (audio) {
611
  audio.volume = 0.3;
612
  audio.play().catch(e => console.log('Gunfire sound failed to play'));
@@ -619,7 +626,8 @@ class Fighter {
619
  const bullet = this.bullets[i];
620
  bullet.position.add(bullet.velocity.clone().multiplyScalar(deltaTime));
621
 
622
- if (bullet.position.distanceTo(this.position) > 8000 ||
 
623
  bullet.position.y < 0 ||
624
  bullet.position.y > GAME_CONSTANTS.MAX_ALTITUDE + 500) {
625
  scene.remove(bullet);
@@ -661,7 +669,7 @@ class EnemyFighter {
661
  this.position = position.clone();
662
  this.velocity = new THREE.Vector3(0, 0, 120);
663
  this.rotation = new THREE.Euler(0, 0, 0);
664
- this.health = 100;
665
  this.speed = 120;
666
  this.bullets = [];
667
  this.lastShootTime = 0;
@@ -857,6 +865,7 @@ class Game {
857
  this.lastTime = performance.now();
858
  this.gameTimer = null;
859
  this.animationFrameId = null;
 
860
 
861
  this.bgm = null;
862
  this.bgmPlaying = false;
@@ -1063,6 +1072,22 @@ class Game {
1063
  this.fighter.updateMouseInput(deltaX, deltaY);
1064
  });
1065
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1066
  window.addEventListener('resize', () => {
1067
  this.camera.aspect = window.innerWidth / window.innerHeight;
1068
  this.camera.updateProjectionMatrix();
@@ -1142,9 +1167,19 @@ class Game {
1142
  const ammoElement = document.getElementById('ammoDisplay');
1143
  const gameStatsElement = document.getElementById('gameStats');
1144
 
 
 
 
 
 
 
 
 
 
 
 
1145
  if (scoreElement) scoreElement.textContent = `Score: ${this.score}`;
1146
  if (timeElement) timeElement.textContent = `Time: ${this.gameTime}s`;
1147
- if (healthElement) healthElement.style.width = `${this.fighter.health}%`;
1148
  if (ammoElement) ammoElement.textContent = `AMMO: ${this.fighter.ammo}`;
1149
 
1150
  if (gameStatsElement) {
@@ -1287,242 +1322,242 @@ class Game {
1287
  }
1288
 
1289
  // Game 클래슀의 updateWarnings λ©”μ„œλ“œ μˆ˜μ •
1290
- updateWarnings() {
1291
- // κΈ°μ‘΄ κ²½κ³  λ©”μ‹œμ§€ 제거
1292
- const existingWarnings = document.querySelectorAll('.warning-message');
1293
- existingWarnings.forEach(w => w.remove());
1294
-
1295
- // μŠ€ν†¨ νƒˆμΆœ κ²½κ³  제거
1296
- const existingStallWarnings = document.querySelectorAll('.stall-escape-warning');
1297
- existingStallWarnings.forEach(w => w.remove());
1298
-
1299
- // 고도 κ²½κ³  μ™Έκ³½ 효과
1300
- let altitudeEdgeEffect = document.getElementById('altitudeEdgeEffect');
1301
- if (this.fighter.altitude < 500) {
1302
- if (!altitudeEdgeEffect) {
1303
- altitudeEdgeEffect = document.createElement('div');
1304
- altitudeEdgeEffect.id = 'altitudeEdgeEffect';
1305
- document.body.appendChild(altitudeEdgeEffect);
1306
- }
1307
-
1308
- let edgeIntensity;
1309
- if (this.fighter.altitude < 250) {
1310
- // PULL UP κ²½κ³  - κ°•ν•œ 뢉은 효과
1311
- edgeIntensity = 0.6;
1312
- altitudeEdgeEffect.style.cssText = `
1313
- position: fixed;
1314
- top: 0;
1315
- left: 0;
1316
- width: 100%;
1317
- height: 100%;
1318
- pointer-events: none;
1319
- z-index: 1300;
1320
- background: radial-gradient(ellipse at center,
1321
- transparent 40%,
1322
- rgba(255, 0, 0, ${edgeIntensity * 0.3}) 60%,
1323
- rgba(255, 0, 0, ${edgeIntensity}) 100%);
1324
- box-shadow: inset 0 0 150px rgba(255, 0, 0, ${edgeIntensity}),
1325
- inset 0 0 100px rgba(255, 0, 0, ${edgeIntensity * 0.8});
1326
- animation: pulse-red 0.5s infinite;
1327
- `;
1328
- } else {
1329
- // LOW ALTITUDE κ²½κ³  - μ•½ν•œ 뢉은 효과
1330
- edgeIntensity = 0.3;
1331
- altitudeEdgeEffect.style.cssText = `
1332
- position: fixed;
1333
- top: 0;
1334
- left: 0;
1335
- width: 100%;
1336
- height: 100%;
1337
- pointer-events: none;
1338
- z-index: 1300;
1339
- background: radial-gradient(ellipse at center,
1340
- transparent 50%,
1341
- rgba(255, 0, 0, ${edgeIntensity * 0.2}) 70%,
1342
- rgba(255, 0, 0, ${edgeIntensity}) 100%);
1343
- box-shadow: inset 0 0 100px rgba(255, 0, 0, ${edgeIntensity}),
1344
- inset 0 0 50px rgba(255, 0, 0, ${edgeIntensity * 0.5});
1345
- `;
1346
- }
1347
- } else {
1348
- // 고도가 μ•ˆμ „ν•˜λ©΄ 효과 제거
1349
- if (altitudeEdgeEffect) {
1350
- altitudeEdgeEffect.remove();
1351
- }
1352
- }
1353
-
1354
- if (this.fighter.warningBlinkState) {
1355
- const warningContainer = document.createElement('div');
1356
- warningContainer.className = 'warning-message';
1357
- warningContainer.style.cssText = `
1358
- position: fixed;
1359
- top: 30%;
1360
- left: 50%;
1361
- transform: translateX(-50%);
1362
- color: #ff0000;
1363
- font-size: 24px;
1364
- font-weight: bold;
1365
- text-shadow: 2px 2px 4px rgba(0,0,0,0.8);
1366
- z-index: 1500;
1367
- text-align: center;
1368
- `;
1369
-
1370
- let warningText = '';
1371
 
1372
- if (this.fighter.altitude < 250) {
1373
- warningText += 'PULL UP! PULL UP!\n';
1374
- } else if (this.fighter.altitude < 500) {
1375
- warningText += 'LOW ALTITUDE WARNING\n';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1376
  }
1377
 
1378
- if (this.fighter.altitudeWarning) {
1379
- warningText += 'ALTITUDE LIMIT\n';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1380
  }
1381
 
 
1382
  if (this.fighter.stallWarning) {
1383
- warningText += 'STALL WARNING\n';
1384
- }
1385
-
1386
- if (this.fighter.overG) {
1387
- warningText += 'OVER-G! OVER-G!\n';
1388
- }
1389
-
1390
- if (warningText) {
1391
- warningContainer.innerHTML = warningText.replace(/\n/g, '<br>');
1392
- document.body.appendChild(warningContainer);
1393
- }
1394
- }
1395
-
1396
- // μŠ€ν†¨ μƒνƒœμΌ λ•Œλ§Œ "Press F to Escape" κ²½κ³  ν‘œμ‹œ
1397
- if (this.fighter.stallWarning) {
1398
- const stallEscapeWarning = document.createElement('div');
1399
- stallEscapeWarning.className = 'stall-escape-warning';
1400
- stallEscapeWarning.style.cssText = `
1401
- position: fixed;
1402
- bottom: 100px;
1403
- left: 50%;
1404
- transform: translateX(-50%);
1405
- background: rgba(255, 0, 0, 0.8);
1406
- color: #ffffff;
1407
- font-size: 28px;
1408
- font-weight: bold;
1409
- padding: 15px 30px;
1410
- border: 3px solid #ff0000;
1411
- border-radius: 10px;
1412
- z-index: 1600;
1413
- text-align: center;
1414
- animation: blink 0.5s infinite;
1415
- `;
1416
- stallEscapeWarning.innerHTML = 'PRESS F TO ESCAPE';
1417
- document.body.appendChild(stallEscapeWarning);
1418
-
1419
- // μ• λ‹ˆλ©”μ΄μ…˜ μŠ€νƒ€μΌ μΆ”κ°€
1420
- if (!document.getElementById('blinkAnimation')) {
1421
- const style = document.createElement('style');
1422
- style.id = 'blinkAnimation';
1423
- style.innerHTML = `
1424
- @keyframes blink {
1425
- 0%, 50% { opacity: 1; }
1426
- 51%, 100% { opacity: 0.3; }
1427
- }
1428
- @keyframes pulse-green {
1429
- 0%, 100% {
1430
- opacity: 1;
1431
- transform: translateX(-50%) scale(1);
1432
- }
1433
- 50% {
1434
- opacity: 0.8;
1435
- transform: translateX(-50%) scale(1.1);
1436
- }
1437
- }
1438
- @keyframes box-pulse {
1439
- 0%, 100% {
1440
- background: rgba(255, 0, 0, 0.9);
1441
- box-shadow: 0 0 20px rgba(255, 0, 0, 0.8),
1442
- 0 0 40px rgba(255, 0, 0, 0.4);
1443
  }
1444
- 50% {
1445
- background: rgba(255, 50, 50, 1);
1446
- box-shadow: 0 0 30px rgba(255, 100, 100, 1),
1447
- 0 0 60px rgba(255, 0, 0, 0.8);
 
 
 
 
 
1448
  }
1449
- }
1450
- @keyframes pulse-red {
1451
- 0%, 100% {
1452
- opacity: 1;
 
 
 
 
 
 
 
1453
  }
1454
- 50% {
1455
- opacity: 0.7;
 
 
 
 
 
1456
  }
1457
- }
1458
- `;
1459
- document.head.appendChild(style);
1460
- }
1461
- }
1462
-
1463
- // Over-G μ‹œμ•Ό 효과 - μˆ˜μ •λœ λΆ€λΆ„
1464
- if (this.fighter.overG && this.fighter.overGTimer > 0) {
1465
- let blurEffect = document.getElementById('overGBlurEffect');
1466
- if (!blurEffect) {
1467
- blurEffect = document.createElement('div');
1468
- blurEffect.id = 'overGBlurEffect';
1469
- document.body.appendChild(blurEffect);
1470
- }
1471
-
1472
- // Over-G 지속 μ‹œκ°„μ— 따라 μ μ§„μ μœΌλ‘œ μ–΄λ‘μ›Œμ§
1473
- // 0초: 효과 μ—†μŒ
1474
- // 1초: 거의 μ™„μ „νžˆ μ–΄λ‘μ›Œμ§
1475
- const darknessFactor = Math.min(this.fighter.overGTimer, 1.0); // 0~1 λ²”μœ„λ‘œ μ œν•œ
1476
-
1477
- // μ‹œμ•Ό κ°€μž₯μžλ¦¬λΆ€ν„° μ–΄λ‘μ›Œμ§€λŠ” 효과
1478
- // 쀑앙은 μƒλŒ€μ μœΌλ‘œ 늦게 μ–΄λ‘μ›Œμ§
1479
- const centerTransparency = Math.max(0, 1 - darknessFactor * 1.2); // 쀑앙 투λͺ…도
1480
- const midTransparency = Math.max(0, 1 - darknessFactor * 0.8); // 쀑간 투λͺ…도
1481
- const edgeOpacity = Math.min(0.95, darknessFactor * 0.95); // κ°€μž₯자리 뢈투λͺ…도
1482
-
1483
- // 뢉은 색쑰 μΆ”κ°€ (ν˜ˆμ•‘ μˆœν™˜ 문제λ₯Ό μ‹œκ°ν™”) - μ•½ν™”λœ 버전
1484
- const redTint = Math.min(darknessFactor * 0.1, 0.1); // 0.3μ—μ„œ 0.1둜 κ°μ†Œ
1485
-
1486
- blurEffect.style.cssText = `
1487
- position: fixed;
1488
- top: 0;
1489
- left: 0;
1490
- width: 100%;
1491
- height: 100%;
1492
- background: radial-gradient(ellipse at center,
1493
- rgba(${Math.floor(255 * redTint)}, 0, 0, ${1 - centerTransparency}) 0%,
1494
- rgba(${Math.floor(150 * redTint)}, 0, 0, ${1 - midTransparency}) 40%,
1495
- rgba(0, 0, 0, ${edgeOpacity}) 70%,
1496
- rgba(0, 0, 0, ${Math.min(0.98, edgeOpacity + 0.05)}) 100%);
1497
- pointer-events: none;
1498
- z-index: 1400;
1499
- transition: background 0.1s ease-out;
1500
- `;
1501
-
1502
- // μ‹¬ν•œ Over-G μƒνƒœμ—μ„œ ν™”λ©΄ 흔듀림 효과
1503
- if (darknessFactor > 0.7) {
1504
- const shake = (1 - Math.random() * 2) * 2;
1505
- blurEffect.style.transform = `translate(${shake}px, ${shake}px)`;
1506
  }
1507
 
1508
- // 디버그 정보 (선택사항)
1509
- // console.log(`Over-G Timer: ${this.fighter.overGTimer.toFixed(2)}s, Darkness: ${(darknessFactor * 100).toFixed(0)}%`);
1510
-
1511
- } else {
1512
- // Over-G μƒνƒœκ°€ μ•„λ‹ˆλ©΄ 효과 제거
1513
- const blurEffect = document.getElementById('overGBlurEffect');
1514
- if (blurEffect) {
1515
- // λΆ€λ“œλŸ¬μš΄ 제거λ₯Ό μœ„ν•œ νŽ˜μ΄λ“œμ•„μ›ƒ
1516
- blurEffect.style.transition = 'opacity 0.5s ease-out';
1517
- blurEffect.style.opacity = '0';
1518
- setTimeout(() => {
1519
- if (blurEffect.parentNode) {
1520
- blurEffect.remove();
1521
- }
1522
- }, 500);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1523
  }
1524
  }
1525
- }
1526
 
1527
  updateRadar() {
1528
  const radar = document.getElementById('radar');
@@ -1570,7 +1605,7 @@ updateWarnings() {
1570
  this.scene.remove(bullet);
1571
  this.fighter.bullets.splice(i, 1);
1572
 
1573
- if (enemy.takeDamage(30)) {
1574
  enemy.destroy();
1575
  this.enemies.splice(j, 1);
1576
  this.score += 100;
@@ -1587,7 +1622,7 @@ updateWarnings() {
1587
  this.scene.remove(bullet);
1588
  enemy.bullets.splice(index, 1);
1589
 
1590
- if (this.fighter.takeDamage(20)) {
1591
  this.endGame(false);
1592
  }
1593
  }
@@ -1612,6 +1647,15 @@ updateWarnings() {
1612
  this.fighter.updatePhysics(deltaTime);
1613
  this.fighter.updateBullets(this.scene, deltaTime);
1614
 
 
 
 
 
 
 
 
 
 
1615
  if (this.isStarted) {
1616
  this.enemies.forEach(enemy => {
1617
  enemy.update(this.fighter.position, deltaTime);
@@ -1783,9 +1827,7 @@ document.addEventListener('click', (event) => {
1783
  console.log('κ²Œμž„ 쀑 클릭 - 포인터 락 μž¬μš”μ²­');
1784
  document.body.requestPointerLock();
1785
  }
1786
- else if (window.gameInstance.fighter.isLoaded) {
1787
- window.gameInstance.fighter.shoot(window.gameInstance.scene);
1788
- }
1789
  }
1790
  }, true);
1791
 
 
18
  MAX_G_FORCE: 12.0,
19
  ENEMY_COUNT: 4,
20
  MISSILE_COUNT: 6,
21
+ AMMO_COUNT: 940, // 940발둜 λ³€κ²½
22
+ BULLET_DAMAGE: 25, // λ°œλ‹Ή 25 데미지
23
+ MAX_HEALTH: 1000 // 체λ ₯ 1000
24
  };
25
 
26
  // μ „νˆ¬κΈ° 클래슀
 
40
  this.speed = 350; // 초기 속도 350kt
41
  this.altitude = 2000;
42
  this.gForce = 1.0;
43
+ this.health = GAME_CONSTANTS.MAX_HEALTH; // 체λ ₯ 1000
44
 
45
  // μ‘°μ’… μž…λ ₯ μ‹œμŠ€ν…œ
46
  this.pitchInput = 0;
 
61
  this.ammo = GAME_CONSTANTS.AMMO_COUNT;
62
  this.bullets = [];
63
  this.lastShootTime = 0;
64
+ this.isMouseDown = false; // 마우슀 λˆ„λ¦„ μƒνƒœ 좔적
65
 
66
  // μŠ€ν†¨ νƒˆμΆœμ„ μœ„ν•œ Fν‚€ μƒνƒœ
67
  this.escapeKeyPressed = false;
 
279
  }
280
 
281
  updateControls(keys, deltaTime) {
282
+ // W/S: μŠ€λ‘œν‹€λ§Œ μ œμ–΄ (가속/감속)
283
+ if (keys.w) {
284
+ this.throttle = Math.min(1.0, this.throttle + deltaTime * 0.5); // 천천히 가속
285
+ }
286
+ if (keys.s) {
287
+ this.throttle = Math.max(0.1, this.throttle - deltaTime * 0.5); // 천천히 감속
288
+ }
289
+
290
+ // A/D: 보쑰 μš” μ œμ–΄ (λŸ¬λ”) - λ°˜μ‘μ„± κ°œμ„ 
291
+ if (keys.a) {
292
+ this.targetYaw -= deltaTime * 1.2; // 0.4μ—μ„œ 1.2둜 증가 (3λ°°)
293
+ }
294
+ if (keys.d) {
295
+ this.targetYaw += deltaTime * 1.2; // 0.4μ—μ„œ 1.2둜 증가 (3λ°°)
296
+ }
297
  }
 
298
 
299
  updatePhysics(deltaTime) {
300
+ if (!this.mesh) return;
301
+
302
+ // λΆ€λ“œλŸ¬μš΄ νšŒμ „ 보간 - Yaw νšŒμ „ 속도 κ°œμ„ 
303
+ const rotationSpeed = deltaTime * 2.0;
304
+ const yawRotationSpeed = deltaTime * 3.0; // YawλŠ” 더 λΉ λ₯΄κ²Œ λ°˜μ‘ν•˜λ„λ‘ 별도 μ„€μ •
305
+
306
+ this.rotation.x = THREE.MathUtils.lerp(this.rotation.x, this.targetPitch, rotationSpeed);
307
+ this.rotation.y = THREE.MathUtils.lerp(this.rotation.y, this.targetYaw, yawRotationSpeed); // κ°œμ„ λœ 속도
308
 
309
  // λ‘€ μžλ™ 볡귀 μ‹œμŠ€ν…œ
310
  if (Math.abs(this.targetYaw - this.rotation.y) < 0.05) {
 
582
  }
583
 
584
  shoot(scene) {
585
+ // 탄약이 μ—†μœΌλ©΄ λ°œμ‚¬ν•˜μ§€ μ•ŠμŒ
586
+ if (this.ammo <= 0) return;
587
 
 
588
  this.ammo--;
589
 
590
  const bulletGeometry = new THREE.SphereGeometry(0.2);
 
595
  });
596
  const bullet = new THREE.Mesh(bulletGeometry, bulletMaterial);
597
 
598
+ // 기수 λμ—μ„œ λ°œμ‚¬
599
  const muzzleOffset = new THREE.Vector3(0, 0, 8);
600
  muzzleOffset.applyEuler(this.rotation);
601
  bullet.position.copy(this.position).add(muzzleOffset);
602
 
603
+ // νƒ„ν™˜ 초기 μœ„μΉ˜ μ €μž₯
604
+ bullet.startPosition = bullet.position.clone();
605
+
606
  const bulletSpeed = 1000;
607
  const direction = new THREE.Vector3(0, 0, 1);
608
  direction.applyEuler(this.rotation);
 
611
  scene.add(bullet);
612
  this.bullets.push(bullet);
613
 
614
+ // 30MM_M163.ogg μ†Œλ¦¬ μž¬μƒ
615
  try {
616
+ const audio = new Audio('sounds/30MM_M163.ogg');
617
  if (audio) {
618
  audio.volume = 0.3;
619
  audio.play().catch(e => console.log('Gunfire sound failed to play'));
 
626
  const bullet = this.bullets[i];
627
  bullet.position.add(bullet.velocity.clone().multiplyScalar(deltaTime));
628
 
629
+ // 1000m 이상 λ‚ μ•„κ°€λ©΄ 제거
630
+ if (bullet.position.distanceTo(bullet.startPosition) > 1000 ||
631
  bullet.position.y < 0 ||
632
  bullet.position.y > GAME_CONSTANTS.MAX_ALTITUDE + 500) {
633
  scene.remove(bullet);
 
669
  this.position = position.clone();
670
  this.velocity = new THREE.Vector3(0, 0, 120);
671
  this.rotation = new THREE.Euler(0, 0, 0);
672
+ this.health = GAME_CONSTANTS.MAX_HEALTH; // 체λ ₯ 1000
673
  this.speed = 120;
674
  this.bullets = [];
675
  this.lastShootTime = 0;
 
865
  this.lastTime = performance.now();
866
  this.gameTimer = null;
867
  this.animationFrameId = null;
868
+ this.lastShootTime = 0;
869
 
870
  this.bgm = null;
871
  this.bgmPlaying = false;
 
1072
  this.fighter.updateMouseInput(deltaX, deltaY);
1073
  });
1074
 
1075
+ // 마우슀 λ‹€μš΄/μ—… 이벀트 μΆ”κ°€
1076
+ document.addEventListener('mousedown', (event) => {
1077
+ if (!document.pointerLockElement || this.isGameOver || !gameStarted) return;
1078
+
1079
+ if (event.button === 0) { // μ™Όμͺ½ 마우슀 λ²„νŠΌ
1080
+ this.fighter.isMouseDown = true;
1081
+ this.lastShootTime = 0; // μ¦‰μ‹œ λ°œμ‚¬
1082
+ }
1083
+ });
1084
+
1085
+ document.addEventListener('mouseup', (event) => {
1086
+ if (event.button === 0) { // μ™Όμͺ½ 마우슀 λ²„νŠΌ
1087
+ this.fighter.isMouseDown = false;
1088
+ }
1089
+ });
1090
+
1091
  window.addEventListener('resize', () => {
1092
  this.camera.aspect = window.innerWidth / window.innerHeight;
1093
  this.camera.updateProjectionMatrix();
 
1167
  const ammoElement = document.getElementById('ammoDisplay');
1168
  const gameStatsElement = document.getElementById('gameStats');
1169
 
1170
+ // 체λ ₯바에 수치 ν‘œμ‹œ
1171
+ const healthBar = document.getElementById('healthBar');
1172
+ if (healthBar) {
1173
+ healthBar.innerHTML = `
1174
+ <div id="health" style="width: ${(this.fighter.health / GAME_CONSTANTS.MAX_HEALTH) * 100}%"></div>
1175
+ <div style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); color: white; font-weight: bold;">
1176
+ ${this.fighter.health}/${GAME_CONSTANTS.MAX_HEALTH}
1177
+ </div>
1178
+ `;
1179
+ }
1180
+
1181
  if (scoreElement) scoreElement.textContent = `Score: ${this.score}`;
1182
  if (timeElement) timeElement.textContent = `Time: ${this.gameTime}s`;
 
1183
  if (ammoElement) ammoElement.textContent = `AMMO: ${this.fighter.ammo}`;
1184
 
1185
  if (gameStatsElement) {
 
1322
  }
1323
 
1324
  // Game 클래슀의 updateWarnings λ©”μ„œλ“œ μˆ˜μ •
1325
+ updateWarnings() {
1326
+ // κΈ°μ‘΄ κ²½κ³  λ©”μ‹œμ§€ 제거
1327
+ const existingWarnings = document.querySelectorAll('.warning-message');
1328
+ existingWarnings.forEach(w => w.remove());
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1329
 
1330
+ // μŠ€ν†¨ νƒˆμΆœ κ²½κ³  제거
1331
+ const existingStallWarnings = document.querySelectorAll('.stall-escape-warning');
1332
+ existingStallWarnings.forEach(w => w.remove());
1333
+
1334
+ // 고도 κ²½κ³  μ™Έκ³½ 효과
1335
+ let altitudeEdgeEffect = document.getElementById('altitudeEdgeEffect');
1336
+ if (this.fighter.altitude < 500) {
1337
+ if (!altitudeEdgeEffect) {
1338
+ altitudeEdgeEffect = document.createElement('div');
1339
+ altitudeEdgeEffect.id = 'altitudeEdgeEffect';
1340
+ document.body.appendChild(altitudeEdgeEffect);
1341
+ }
1342
+
1343
+ let edgeIntensity;
1344
+ if (this.fighter.altitude < 250) {
1345
+ // PULL UP κ²½κ³  - κ°•ν•œ 뢉은 효과
1346
+ edgeIntensity = 0.6;
1347
+ altitudeEdgeEffect.style.cssText = `
1348
+ position: fixed;
1349
+ top: 0;
1350
+ left: 0;
1351
+ width: 100%;
1352
+ height: 100%;
1353
+ pointer-events: none;
1354
+ z-index: 1300;
1355
+ background: radial-gradient(ellipse at center,
1356
+ transparent 40%,
1357
+ rgba(255, 0, 0, ${edgeIntensity * 0.3}) 60%,
1358
+ rgba(255, 0, 0, ${edgeIntensity}) 100%);
1359
+ box-shadow: inset 0 0 150px rgba(255, 0, 0, ${edgeIntensity}),
1360
+ inset 0 0 100px rgba(255, 0, 0, ${edgeIntensity * 0.8});
1361
+ animation: pulse-red 0.5s infinite;
1362
+ `;
1363
+ } else {
1364
+ // LOW ALTITUDE κ²½κ³  - μ•½ν•œ 뢉은 효과
1365
+ edgeIntensity = 0.3;
1366
+ altitudeEdgeEffect.style.cssText = `
1367
+ position: fixed;
1368
+ top: 0;
1369
+ left: 0;
1370
+ width: 100%;
1371
+ height: 100%;
1372
+ pointer-events: none;
1373
+ z-index: 1300;
1374
+ background: radial-gradient(ellipse at center,
1375
+ transparent 50%,
1376
+ rgba(255, 0, 0, ${edgeIntensity * 0.2}) 70%,
1377
+ rgba(255, 0, 0, ${edgeIntensity}) 100%);
1378
+ box-shadow: inset 0 0 100px rgba(255, 0, 0, ${edgeIntensity}),
1379
+ inset 0 0 50px rgba(255, 0, 0, ${edgeIntensity * 0.5});
1380
+ `;
1381
+ }
1382
+ } else {
1383
+ // 고도가 μ•ˆμ „ν•˜λ©΄ 효과 제거
1384
+ if (altitudeEdgeEffect) {
1385
+ altitudeEdgeEffect.remove();
1386
+ }
1387
  }
1388
 
1389
+ if (this.fighter.warningBlinkState) {
1390
+ const warningContainer = document.createElement('div');
1391
+ warningContainer.className = 'warning-message';
1392
+ warningContainer.style.cssText = `
1393
+ position: fixed;
1394
+ top: 30%;
1395
+ left: 50%;
1396
+ transform: translateX(-50%);
1397
+ color: #ff0000;
1398
+ font-size: 24px;
1399
+ font-weight: bold;
1400
+ text-shadow: 2px 2px 4px rgba(0,0,0,0.8);
1401
+ z-index: 1500;
1402
+ text-align: center;
1403
+ `;
1404
+
1405
+ let warningText = '';
1406
+
1407
+ if (this.fighter.altitude < 250) {
1408
+ warningText += 'PULL UP! PULL UP!\n';
1409
+ } else if (this.fighter.altitude < 500) {
1410
+ warningText += 'LOW ALTITUDE WARNING\n';
1411
+ }
1412
+
1413
+ if (this.fighter.altitudeWarning) {
1414
+ warningText += 'ALTITUDE LIMIT\n';
1415
+ }
1416
+
1417
+ if (this.fighter.stallWarning) {
1418
+ warningText += 'STALL WARNING\n';
1419
+ }
1420
+
1421
+ if (this.fighter.overG) {
1422
+ warningText += 'OVER-G! OVER-G!\n';
1423
+ }
1424
+
1425
+ if (warningText) {
1426
+ warningContainer.innerHTML = warningText.replace(/\n/g, '<br>');
1427
+ document.body.appendChild(warningContainer);
1428
+ }
1429
  }
1430
 
1431
+ // μŠ€ν†¨ μƒνƒœμΌ λ•Œλ§Œ "Press F to Escape" κ²½κ³  ν‘œμ‹œ
1432
  if (this.fighter.stallWarning) {
1433
+ const stallEscapeWarning = document.createElement('div');
1434
+ stallEscapeWarning.className = 'stall-escape-warning';
1435
+ stallEscapeWarning.style.cssText = `
1436
+ position: fixed;
1437
+ bottom: 100px;
1438
+ left: 50%;
1439
+ transform: translateX(-50%);
1440
+ background: rgba(255, 0, 0, 0.8);
1441
+ color: #ffffff;
1442
+ font-size: 28px;
1443
+ font-weight: bold;
1444
+ padding: 15px 30px;
1445
+ border: 3px solid #ff0000;
1446
+ border-radius: 10px;
1447
+ z-index: 1600;
1448
+ text-align: center;
1449
+ animation: blink 0.5s infinite;
1450
+ `;
1451
+ stallEscapeWarning.innerHTML = 'PRESS F TO ESCAPE';
1452
+ document.body.appendChild(stallEscapeWarning);
1453
+
1454
+ // μ• λ‹ˆλ©”μ΄μ…˜ μŠ€νƒ€μΌ μΆ”κ°€
1455
+ if (!document.getElementById('blinkAnimation')) {
1456
+ const style = document.createElement('style');
1457
+ style.id = 'blinkAnimation';
1458
+ style.innerHTML = `
1459
+ @keyframes blink {
1460
+ 0%, 50% { opacity: 1; }
1461
+ 51%, 100% { opacity: 0.3; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1462
  }
1463
+ @keyframes pulse-green {
1464
+ 0%, 100% {
1465
+ opacity: 1;
1466
+ transform: translateX(-50%) scale(1);
1467
+ }
1468
+ 50% {
1469
+ opacity: 0.8;
1470
+ transform: translateX(-50%) scale(1.1);
1471
+ }
1472
  }
1473
+ @keyframes box-pulse {
1474
+ 0%, 100% {
1475
+ background: rgba(255, 0, 0, 0.9);
1476
+ box-shadow: 0 0 20px rgba(255, 0, 0, 0.8),
1477
+ 0 0 40px rgba(255, 0, 0, 0.4);
1478
+ }
1479
+ 50% {
1480
+ background: rgba(255, 50, 50, 1);
1481
+ box-shadow: 0 0 30px rgba(255, 100, 100, 1),
1482
+ 0 0 60px rgba(255, 0, 0, 0.8);
1483
+ }
1484
  }
1485
+ @keyframes pulse-red {
1486
+ 0%, 100% {
1487
+ opacity: 1;
1488
+ }
1489
+ 50% {
1490
+ opacity: 0.7;
1491
+ }
1492
  }
1493
+ `;
1494
+ document.head.appendChild(style);
1495
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1496
  }
1497
 
1498
+ // Over-G μ‹œμ•Ό 효과 - μˆ˜μ •λœ λΆ€λΆ„
1499
+ if (this.fighter.overG && this.fighter.overGTimer > 0) {
1500
+ let blurEffect = document.getElementById('overGBlurEffect');
1501
+ if (!blurEffect) {
1502
+ blurEffect = document.createElement('div');
1503
+ blurEffect.id = 'overGBlurEffect';
1504
+ document.body.appendChild(blurEffect);
1505
+ }
1506
+
1507
+ // Over-G 지속 μ‹œκ°„μ— 따라 μ μ§„μ μœΌλ‘œ μ–΄λ‘μ›Œμ§
1508
+ // 0초: 효과 μ—†μŒ
1509
+ // 1초: 거의 μ™„μ „νžˆ μ–΄λ‘μ›Œμ§
1510
+ const darknessFactor = Math.min(this.fighter.overGTimer, 1.0); // 0~1 λ²”μœ„λ‘œ μ œν•œ
1511
+
1512
+ // μ‹œμ•Ό κ°€μž₯μžλ¦¬λΆ€ν„° μ–΄λ‘μ›Œμ§€λŠ” 효과
1513
+ // 쀑앙은 μƒλŒ€μ μœΌλ‘œ 늦게 μ–΄λ‘μ›Œμ§
1514
+ const centerTransparency = Math.max(0, 1 - darknessFactor * 1.2); // 쀑앙 투λͺ…도
1515
+ const midTransparency = Math.max(0, 1 - darknessFactor * 0.8); // 쀑간 투λͺ…도
1516
+ const edgeOpacity = Math.min(0.95, darknessFactor * 0.95); // κ°€μž₯자리 뢈투λͺ…도
1517
+
1518
+ // λΆ‰οΏ½οΏ½ 색쑰 μΆ”κ°€ (ν˜ˆμ•‘ μˆœν™˜ 문제λ₯Ό μ‹œκ°ν™”) - μ•½ν™”λœ 버전
1519
+ const redTint = Math.min(darknessFactor * 0.1, 0.1); // 0.3μ—μ„œ 0.1둜 κ°μ†Œ
1520
+
1521
+ blurEffect.style.cssText = `
1522
+ position: fixed;
1523
+ top: 0;
1524
+ left: 0;
1525
+ width: 100%;
1526
+ height: 100%;
1527
+ background: radial-gradient(ellipse at center,
1528
+ rgba(${Math.floor(255 * redTint)}, 0, 0, ${1 - centerTransparency}) 0%,
1529
+ rgba(${Math.floor(150 * redTint)}, 0, 0, ${1 - midTransparency}) 40%,
1530
+ rgba(0, 0, 0, ${edgeOpacity}) 70%,
1531
+ rgba(0, 0, 0, ${Math.min(0.98, edgeOpacity + 0.05)}) 100%);
1532
+ pointer-events: none;
1533
+ z-index: 1400;
1534
+ transition: background 0.1s ease-out;
1535
+ `;
1536
+
1537
+ // μ‹¬ν•œ Over-G μƒνƒœμ—μ„œ ν™”λ©΄ 흔듀림 효과
1538
+ if (darknessFactor > 0.7) {
1539
+ const shake = (1 - Math.random() * 2) * 2;
1540
+ blurEffect.style.transform = `translate(${shake}px, ${shake}px)`;
1541
+ }
1542
+
1543
+ // 디버그 정보 (선택사항)
1544
+ // console.log(`Over-G Timer: ${this.fighter.overGTimer.toFixed(2)}s, Darkness: ${(darknessFactor * 100).toFixed(0)}%`);
1545
+
1546
+ } else {
1547
+ // Over-G μƒνƒœκ°€ μ•„λ‹ˆλ©΄ 효과 제거
1548
+ const blurEffect = document.getElementById('overGBlurEffect');
1549
+ if (blurEffect) {
1550
+ // λΆ€λ“œλŸ¬μš΄ 제거λ₯Ό μœ„ν•œ νŽ˜μ΄λ“œμ•„μ›ƒ
1551
+ blurEffect.style.transition = 'opacity 0.5s ease-out';
1552
+ blurEffect.style.opacity = '0';
1553
+ setTimeout(() => {
1554
+ if (blurEffect.parentNode) {
1555
+ blurEffect.remove();
1556
+ }
1557
+ }, 500);
1558
+ }
1559
  }
1560
  }
 
1561
 
1562
  updateRadar() {
1563
  const radar = document.getElementById('radar');
 
1605
  this.scene.remove(bullet);
1606
  this.fighter.bullets.splice(i, 1);
1607
 
1608
+ if (enemy.takeDamage(GAME_CONSTANTS.BULLET_DAMAGE)) { // 25 데미지
1609
  enemy.destroy();
1610
  this.enemies.splice(j, 1);
1611
  this.score += 100;
 
1622
  this.scene.remove(bullet);
1623
  enemy.bullets.splice(index, 1);
1624
 
1625
+ if (this.fighter.takeDamage(GAME_CONSTANTS.BULLET_DAMAGE)) { // 25 데미지
1626
  this.endGame(false);
1627
  }
1628
  }
 
1647
  this.fighter.updatePhysics(deltaTime);
1648
  this.fighter.updateBullets(this.scene, deltaTime);
1649
 
1650
+ // 마우슀 λˆ„λ¦„ μƒνƒœμΌ λ•Œ 연속 λ°œμ‚¬
1651
+ if (this.fighter.isMouseDown && this.isStarted) {
1652
+ const currentShootTime = Date.now();
1653
+ if (!this.lastShootTime || currentShootTime - this.lastShootTime >= 500) { // 0.5μ΄ˆλ§ˆλ‹€
1654
+ this.fighter.shoot(this.scene);
1655
+ this.lastShootTime = currentShootTime;
1656
+ }
1657
+ }
1658
+
1659
  if (this.isStarted) {
1660
  this.enemies.forEach(enemy => {
1661
  enemy.update(this.fighter.position, deltaTime);
 
1827
  console.log('κ²Œμž„ 쀑 클릭 - 포인터 락 μž¬μš”μ²­');
1828
  document.body.requestPointerLock();
1829
  }
1830
+ // 클릭으둜 λ‹¨λ°œ 사격 제거 (마우슀 λ‹€μš΄/μ—… 이벀트둜 처리)
 
 
1831
  }
1832
  }, true);
1833