Spaces:
Running
Running
Update game.js
Browse files
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:
|
|
|
|
|
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 =
|
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 |
-
|
280 |
-
|
281 |
-
|
282 |
-
|
283 |
-
|
284 |
-
|
285 |
-
|
286 |
-
|
287 |
-
|
288 |
-
|
289 |
-
|
290 |
-
|
291 |
-
|
292 |
-
|
|
|
293 |
}
|
294 |
-
}
|
295 |
|
296 |
updatePhysics(deltaTime) {
|
297 |
-
|
298 |
-
|
299 |
-
|
300 |
-
|
301 |
-
|
302 |
-
|
303 |
-
|
304 |
-
|
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 |
-
|
583 |
-
if (
|
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/
|
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 |
-
|
|
|
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 =
|
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 |
-
|
1293 |
-
|
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 |
-
|
1373 |
-
|
1374 |
-
|
1375 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1376 |
}
|
1377 |
|
1378 |
-
if (this.fighter.
|
1379 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1380 |
}
|
1381 |
|
|
|
1382 |
if (this.fighter.stallWarning) {
|
1383 |
-
|
1384 |
-
|
1385 |
-
|
1386 |
-
|
1387 |
-
|
1388 |
-
|
1389 |
-
|
1390 |
-
|
1391 |
-
|
1392 |
-
|
1393 |
-
|
1394 |
-
|
1395 |
-
|
1396 |
-
|
1397 |
-
|
1398 |
-
|
1399 |
-
|
1400 |
-
|
1401 |
-
|
1402 |
-
|
1403 |
-
|
1404 |
-
|
1405 |
-
|
1406 |
-
|
1407 |
-
|
1408 |
-
|
1409 |
-
|
1410 |
-
|
1411 |
-
|
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 |
-
|
1445 |
-
|
1446 |
-
|
1447 |
-
|
|
|
|
|
|
|
|
|
|
|
1448 |
}
|
1449 |
-
|
1450 |
-
|
1451 |
-
|
1452 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1453 |
}
|
1454 |
-
|
1455 |
-
|
|
|
|
|
|
|
|
|
|
|
1456 |
}
|
1457 |
-
|
1458 |
-
|
1459 |
-
|
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 |
-
|
1510 |
-
|
1511 |
-
|
1512 |
-
|
1513 |
-
|
1514 |
-
|
1515 |
-
|
1516 |
-
|
1517 |
-
|
1518 |
-
|
1519 |
-
|
1520 |
-
|
1521 |
-
|
1522 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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(
|
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(
|
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 |
-
|
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 |
|