3D-Airforce-Simulator / index.html
cutechicken's picture
Update index.html
b73b992 verified
raw
history blame
24.8 kB
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>JET FIGHT SIMULATER - FPS Mode</title>
<style>
body {
margin: 0;
overflow: hidden;
background: #000;
font-family: 'Courier New', monospace;
}
#loading {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: rgba(0,0,0,0.8);
padding: 20px;
border-radius: 10px;
z-index: 2000;
text-align: center;
}
.loading-spinner {
width: 50px;
height: 50px;
border: 5px solid #0f0;
border-top: 5px solid transparent;
border-radius: 50%;
animation: spin 1s linear infinite;
margin: 0 auto 20px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.loading-text {
color: #0f0;
font-size: 24px;
text-align: center;
}
#gameContainer {
position: relative;
width: 100vw;
height: 100vh;
cursor: none;
}
/* HUD 전체 μ»¨ν…Œμ΄λ„ˆ */
#hudContainer {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 1000;
}
/* 쀑앙 HUD */
#hudCrosshair {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 400px;
height: 400px;
}
/* μ‘°μ€€ 원 */
.hud-aiming-circle {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 150px;
height: 150px;
border: 2px solid rgba(0, 255, 0, 0.5);
border-radius: 50%;
}
/* 쀑앙점 */
.hud-center-dot {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 4px;
height: 4px;
background: #00ff00;
border-radius: 50%;
}
/* μˆ˜ν‰μ„  */
.hud-horizon-line {
position: absolute;
top: 50%;
left: 20%;
right: 20%;
height: 1px;
background: rgba(0, 255, 0, 0.4);
}
/* ν”ΌμΉ˜ λž˜λ” μ»¨ν…Œμ΄λ„ˆ */
.hud-pitch-ladder {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 200px;
height: 400px;
}
.pitch-line {
position: absolute;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.pitch-line-bar {
width: 60px;
height: 1px;
background: rgba(0, 255, 0, 0.4);
}
.pitch-line-text {
position: absolute;
color: rgba(0, 255, 0, 0.6);
font-size: 10px;
left: -25px;
}
/* 쀑앙 HUD 정보 ν‘œμ‹œ - μƒˆλ‘œμš΄ μŠ€νƒ€μΌ */
.hud-central-info {
position: absolute;
color: #00ff00;
font-size: 11px;
font-family: 'Courier New', monospace;
text-shadow: 0 0 3px rgba(0, 0, 0, 0.8);
background: rgba(0, 0, 0, 0.3);
padding: 2px 5px;
border-radius: 2px;
}
/* 쀑앙 HUD λ‚΄ 속도 ν‘œμ‹œ */
#hudSpeedCentral {
left: -120px;
top: 50%;
transform: translateY(-50%);
}
/* 쀑앙 HUD λ‚΄ 고도 ν‘œμ‹œ */
#hudAltitudeCentral {
right: -120px;
top: 50%;
transform: translateY(-50%);
}
/* 쀑앙 HUD λ‚΄ ν—€λ”© ν‘œμ‹œ */
#hudHeadingCentral {
top: -30px;
left: 50%;
transform: translateX(-50%);
}
/* 쀑앙 HUD λ‚΄ G-Force ν‘œμ‹œ */
#hudGForceCentral {
bottom: -30px;
left: 50%;
transform: translateX(-50%);
}
/* 쀑앙 HUD λ‚΄ μŠ€λ‘œν‹€ ν‘œμ‹œ */
#hudThrottleCentral {
left: -120px;
top: calc(50% + 25px);
}
/* νƒ€κ²Ÿ 마컀 */
.target-marker {
position: absolute;
width: 30px;
height: 30px;
border: 2px solid transparent;
transform: translate(-50%, -50%);
}
.target-marker.in-crosshair {
border: 2px solid #ffff00;
animation: target-pulse 0.5s infinite;
}
.target-marker.locked {
border: 2px solid #ff0000;
box-shadow: 0 0 10px #ff0000;
}
.target-marker .target-box {
position: absolute;
top: -5px;
left: -5px;
right: -5px;
bottom: -5px;
border: 1px solid currentColor;
}
@keyframes target-pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
.target-info {
position: absolute;
top: 100%;
left: 50%;
transform: translateX(-50%);
color: #00ff00;
font-size: 10px;
white-space: nowrap;
margin-top: 5px;
}
#healthBar {
position: absolute;
bottom: 20px;
left: 20px;
width: 200px;
height: 20px;
background: rgba(0,20,0,0.7);
border: 2px solid #0f0;
z-index: 1001;
border-radius: 10px;
overflow: hidden;
}
#health {
width: 100%;
height: 100%;
background: linear-gradient(90deg, #0f0, #00ff00);
transition: width 0.3s;
}
#gameTitle {
position: absolute;
top: 60px;
left: 50%;
transform: translateX(-50%);
color: #0f0;
background: rgba(0,20,0,0.7);
padding: 10px 20px;
font-size: 20px;
z-index: 1001;
border: 1px solid #0f0;
border-radius: 5px;
text-transform: uppercase;
letter-spacing: 2px;
}
#ammoDisplay {
position: absolute;
bottom: 20px;
right: 20px;
color: #0f0;
background: rgba(0,20,0,0.7);
padding: 10px;
font-size: 20px;
z-index: 1001;
border: 1px solid #0f0;
border-radius: 5px;
}
#radar {
position: absolute;
bottom: 60px;
left: 20px;
width: 200px;
height: 200px;
background: rgba(0,20,0,0.3);
border: 2px solid #0f0;
border-radius: 50%;
z-index: 1001;
overflow: hidden;
}
#mission {
position: absolute;
top: 10px;
left: 10px;
color: #0f0;
background: rgba(0,20,0,0.7);
padding: 10px;
font-size: 16px;
z-index: 1001;
border: 1px solid #0f0;
border-radius: 5px;
}
#radarLine {
position: absolute;
top: 50%;
left: 50%;
width: 50%;
height: 2px;
background: #0f0;
transform-origin: left center;
animation: radar-sweep 4s infinite linear;
}
.enemy-dot {
position: absolute;
width: 6px;
height: 6px;
background: #ff0000;
border-radius: 50%;
transform: translate(-50%, -50%);
}
@keyframes radar-sweep {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
#gameStats {
position: absolute;
top: 10px;
right: 20px;
color: #0f0;
background: rgba(0,20,0,0.7);
padding: 10px;
font-size: 16px;
z-index: 1001;
border: 1px solid #0f0;
border-radius: 5px;
text-align: right;
}
.start-screen {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.8);
display: none;
justify-content: center;
align-items: center;
flex-direction: column;
z-index: 2000;
}
.start-button {
padding: 15px 30px;
font-size: 24px;
background: #0f0;
color: #000;
border: none;
border-radius: 5px;
cursor: pointer;
margin-top: 20px;
transition: transform 0.2s;
}
.start-button:hover {
transform: scale(1.1);
}
/* κΈ°μ‘΄ κ°œλ³„ HUD 정보듀은 μˆ¨κΉ€ */
#hudSpeed, #hudAltitude, #hudHeading, #hudPitch, #hudRoll, #hudTurnRate {
display: none;
}
</style>
</head>
<body>
<!-- λ‘œλ”© ν™”λ©΄ -->
<div id="loading">
<div class="loading-spinner"></div>
<div class="loading-text">Loading Fighter Resources...</div>
<div style="color: #0f0; font-size: 16px; margin-top: 10px;">
<p>Loading Aircraft Models...</p>
<p>Loading Audio Assets...</p>
<p>Preparing Game Environment...</p>
</div>
</div>
<!-- κ²Œμž„ μ‹œμž‘ ν™”λ©΄ -->
<div class="start-screen" id="startScreen">
<h1 style="color: #0f0; font-size: 48px; margin-bottom: 20px;">JET FIGHT SIMULATER</h1>
<button class="start-button" onclick="startGame()">Start Game</button>
<div style="color: #0f0; margin-top: 20px; text-align: center;">
<p>Controls:</p>
<p>W/S - Throttle Control</p>
<p>A/D - Rudder Control</p>
<p>Mouse - Aircraft Control</p>
<p>Left Click - Fire</p>
<p>F - Escape Stall</p>
</div>
</div>
<!-- κ²Œμž„ ν™”λ©΄ -->
<div id="gameContainer">
<!-- κ²Œμž„ 타이틀 (κ²Œμž„ μ‹œμž‘ μ‹œ 숨겨짐) -->
<div id="gameTitle" style="display: none;">JET FIGHT SIMULATER</div>
<div id="mission">MISSION: DESTROY ENEMY JET</div>
<div id="gameStats">
<div id="score">Score: 0</div>
<div id="time">Time: 180s</div>
</div>
<!-- HUD μ»¨ν…Œμ΄λ„ˆ -->
<div id="hudContainer">
<!-- 쀑앙 HUD -->
<div id="hudCrosshair">
<div class="hud-aiming-circle"></div>
<div class="hud-center-dot"></div>
<div class="hud-horizon-line"></div>
<!-- 쀑앙 HUD 정보 ν‘œμ‹œ -->
<div class="hud-central-info" id="hudSpeedCentral">SPD: 0 KT</div>
<div class="hud-central-info" id="hudAltitudeCentral">ALT: 0 M</div>
<div class="hud-central-info" id="hudHeadingCentral">HDG: 000Β°</div>
<div class="hud-central-info" id="hudGForceCentral">G: 1.0</div>
<div class="hud-central-info" id="hudThrottleCentral">THR: 60%</div>
<!-- ν”ΌμΉ˜ λž˜λ” -->
<div class="hud-pitch-ladder" id="pitchLadder">
<!-- 10도당 20ν”½μ…€ 간격 -->
<div class="pitch-line" style="top: calc(50% - 80px);">
<div class="pitch-line-bar"></div>
<span class="pitch-line-text">40</span>
</div>
<div class="pitch-line" style="top: calc(50% - 60px);">
<div class="pitch-line-bar"></div>
<span class="pitch-line-text">30</span>
</div>
<div class="pitch-line" style="top: calc(50% - 40px);">
<div class="pitch-line-bar"></div>
<span class="pitch-line-text">20</span>
</div>
<div class="pitch-line" style="top: calc(50% - 20px);">
<div class="pitch-line-bar"></div>
<span class="pitch-line-text">10</span>
</div>
<!-- 0도 라인 - μ •ν™•νžˆ 쀑앙 -->
<div class="pitch-line" style="top: 50%;">
<div class="pitch-line-bar" style="width: 100px; height: 2px; background: rgba(0, 255, 0, 0.8);"></div>
<span class="pitch-line-text" style="color: rgba(0, 255, 0, 0.8); font-weight: bold;">0</span>
</div>
<div class="pitch-line" style="top: calc(50% + 20px);">
<div class="pitch-line-bar"></div>
<span class="pitch-line-text">-10</span>
</div>
<div class="pitch-line" style="top: calc(50% + 40px);">
<div class="pitch-line-bar"></div>
<span class="pitch-line-text">-20</span>
</div>
<div class="pitch-line" style="top: calc(50% + 60px);">
<div class="pitch-line-bar"></div>
<span class="pitch-line-text">-30</span>
</div>
<div class="pitch-line" style="top: calc(50% + 80px);">
<div class="pitch-line-bar"></div>
<span class="pitch-line-text">-40</span>
</div>
</div>
</div>
<!-- κΈ°μ‘΄ λΉ„ν–‰ 정보 (μˆ¨κΉ€) -->
<div class="hud-info" id="hudSpeed">SPD: 0 KT</div>
<div class="hud-info" id="hudAltitude">ALT: 0 M</div>
<div class="hud-info" id="hudHeading">HDG: 000Β°</div>
<div class="hud-info" id="hudPitch">PITCH: 0Β°</div>
<div class="hud-info" id="hudRoll">ROLL: 0Β°</div>
<div class="hud-info" id="hudTurnRate">TURN: 0Β°/s</div>
<!-- νƒ€κ²Ÿ 마컀 λ ˆμ΄μ–΄ -->
<div id="targetMarkers"></div>
</div>
<!-- 체λ ₯λ°” -->
<div id="healthBar">
<div id="health"></div>
</div>
<!-- 탄약 ν‘œμ‹œ -->
<div id="ammoDisplay">AMMO: 300</div>
<!-- λ ˆμ΄λ” (RWR) -->
<div id="radar">
<div class="rwr-display">
<!-- RWR 거리 링 -->
<div class="rwr-range-ring rwr-ring-inner"></div>
<div class="rwr-range-ring rwr-ring-middle"></div>
<div class="rwr-range-ring rwr-ring-outer"></div>
<!-- λ°©ν–₯ ν‘œμ‹œ -->
<div class="rwr-direction-marks">
<div class="rwr-direction-text rwr-north">N</div>
<div class="rwr-direction-text rwr-east">E</div>
<div class="rwr-direction-text rwr-south">S</div>
<div class="rwr-direction-text rwr-west">W</div>
</div>
<!-- 쀑앙 항곡기 심볼 -->
<div class="rwr-center">
<div class="rwr-aircraft-symbol">
<div class="rwr-aircraft-body"></div>
<div class="rwr-aircraft-wing"></div>
</div>
</div>
<!-- μœ„ν˜‘ ν‘œμ‹œ μ˜μ—­ -->
<div id="rwrThreats"></div>
</div>
<!-- κΈ°μ‘΄ λ ˆμ΄λ” μš”μ†Œ (μˆ¨κΉ€) -->
<div id="radarLine" style="display: none;"></div>
</div>
</div>
<script type="importmap">
{
"imports": {
"three": "https://unpkg.com/[email protected]/build/three.module.js",
"three/addons/": "https://unpkg.com/[email protected]/examples/jsm/"
}
}
</script>
<!-- ν”ΌμΉ˜ λž˜λ” 및 HUD μ—…λ°μ΄νŠΈ 슀크립트 -->
<script>
// μ „μ—­ λ³€μˆ˜
let gameStarted = false;
// κ²Œμž„ μ€€λΉ„ μ™„λ£Œ μ‹œ HUD μ—…λ°μ΄νŠΈ μ‹œμž‘
window.addEventListener('gameReady', function() {
console.log('Game ready - starting HUD updates');
// ν”ΌμΉ˜ λž˜λ” 초기 μœ„μΉ˜ κ°•μ œ μ„€μ •
setTimeout(function() {
const pitchLadder = document.getElementById('pitchLadder');
if (pitchLadder) {
// CSS둜 직접 μœ„μΉ˜ μ‘°μ •
pitchLadder.style.position = 'absolute';
pitchLadder.style.top = 'calc(50% - 200px)'; // 200px μœ„λ‘œ 올림
pitchLadder.style.left = '50%';
pitchLadder.style.transform = 'translate(-50%, 0)';
console.log('Pitch ladder initial position set');
}
}, 100);
// 쀑앙 HUD 정보 μ—…λ°μ΄νŠΈλ₯Ό μœ„ν•œ μΈν„°λ²Œ
setInterval(function() {
if (window.gameInstance && window.gameInstance.fighter && window.gameInstance.fighter.isLoaded) {
const fighter = window.gameInstance.fighter;
// λΉ„ν–‰ 정보 계산
const speedKnots = Math.round(fighter.speed * 1.94384);
const altitudeMeters = Math.round(fighter.altitude);
const throttlePercent = Math.round(fighter.throttle * 100);
const gForce = fighter.gForce.toFixed(1);
const headingDegrees = Math.round(((fighter.rotation.y * (180 / Math.PI)) + 360) % 360);
// 쀑앙 HUD 정보 μ—…λ°μ΄νŠΈ
const hudSpeedCentral = document.getElementById('hudSpeedCentral');
const hudAltitudeCentral = document.getElementById('hudAltitudeCentral');
const hudHeadingCentral = document.getElementById('hudHeadingCentral');
const hudGForceCentral = document.getElementById('hudGForceCentral');
const hudThrottleCentral = document.getElementById('hudThrottleCentral');
if (hudSpeedCentral) hudSpeedCentral.textContent = `SPD: ${speedKnots} KT`;
if (hudAltitudeCentral) hudAltitudeCentral.textContent = `ALT: ${altitudeMeters} M`;
if (hudHeadingCentral) hudHeadingCentral.textContent = `HDG: ${String(headingDegrees).padStart(3, '0')}Β°`;
if (hudGForceCentral) {
hudGForceCentral.textContent = `G: ${gForce}`;
// G-Force에 λ”°λ₯Έ 색상 λ³€κ²½
if (fighter.overG) {
hudGForceCentral.style.color = '#ff0000';
} else if (parseFloat(gForce) > 7) {
hudGForceCentral.style.color = '#ffff00';
} else {
hudGForceCentral.style.color = '#00ff00';
}
}
if (hudThrottleCentral) hudThrottleCentral.textContent = `THR: ${throttlePercent}%`;
// ν”ΌμΉ˜ λž˜λ” 동적 μ—…λ°μ΄νŠΈ - κΈ°λ³Έ μœ„μΉ˜μ—μ„œ ν”ΌμΉ˜ κ°λ„λ§ŒνΌ 이동
const pitchLadder = document.getElementById('pitchLadder');
if (pitchLadder) {
const pitchDegrees = fighter.rotation.x * (180 / Math.PI);
// 10도당 20ν”½μ…€, 음수둜 λ°˜λŒ€ λ°©ν–₯
const pitchOffset = -200 + (-pitchDegrees * 2);
pitchLadder.style.top = `calc(50% + ${pitchOffset}px)`;
}
// HUD ν¬λ‘œμŠ€ν—€μ–΄ νšŒμ „ (λ‘€ 각도에 따라)
const hudCrosshair = document.getElementById('hudCrosshair');
if (hudCrosshair && fighter.rotation) {
const rollDegrees = fighter.rotation.z * (180 / Math.PI);
hudCrosshair.style.transform = `translate(-50%, -50%) rotate(${-rollDegrees}deg)`;
}
// RWR μ—…λ°μ΄νŠΈ
updateRWR(fighter);
}
}, 16); // μ•½ 60fps
});
// RWR μ—…λ°μ΄νŠΈ ν•¨μˆ˜
function updateRWR(fighter) {
const rwrThreats = document.getElementById('rwrThreats');
if (!rwrThreats || !window.gameInstance) return;
// κΈ°μ‘΄ μœ„ν˜‘ ν‘œμ‹œ 제거
rwrThreats.innerHTML = '';
// 적 항곡기 ν‘œμ‹œ
if (window.gameInstance.enemies) {
window.gameInstance.enemies.forEach((enemy, index) => {
if (!enemy.mesh || !enemy.isLoaded) return;
const distance = fighter.position.distanceTo(enemy.position);
// 10km μ΄λ‚΄μ˜ 적만 RWR에 ν‘œμ‹œ
if (distance <= 10000) {
// μƒλŒ€ μœ„μΉ˜ 계산
const relativePos = enemy.position.clone().sub(fighter.position);
// μ „νˆ¬κΈ°μ˜ heading을 κ³ λ €ν•œ 각도 계산
const angle = Math.atan2(relativePos.x, relativePos.z) - fighter.rotation.y;
// RWR μƒμ˜ μœ„μΉ˜ 계산 (μ΅œλŒ€ 반경 90px)
const maxRadius = 90;
const relativeDistance = Math.min(distance / 10000, 1) * maxRadius;
const x = Math.sin(angle) * relativeDistance + 100; // 쀑앙이 100px
const y = -Math.cos(angle) * relativeDistance + 100;
// μœ„ν˜‘ 심볼 생성
const threat = document.createElement('div');
threat.className = 'rwr-threat';
// 거리에 λ”°λ₯Έ μœ„ν˜‘ 레벨
if (distance < 2000) {
threat.classList.add('level-high');
threat.classList.add('missile-lock');
threat.textContent = 'β—†';
} else if (distance < 5000) {
threat.classList.add('level-medium');
threat.textContent = 'β—†';
} else {
threat.classList.add('level-low');
threat.textContent = 'β—†';
}
threat.style.left = `${x}px`;
threat.style.top = `${y}px`;
rwrThreats.appendChild(threat);
}
});
}
}
// startGame ν•¨μˆ˜ μ •μ˜
window.startGame = function() {
if (!window.gameInstance || !window.gameInstance.isLoaded || !window.gameInstance.isBGMReady) {
console.log('κ²Œμž„μ΄ 아직 μ€€λΉ„λ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€...');
return;
}
gameStarted = true;
document.getElementById('startScreen').style.display = 'none';
// κ²Œμž„ 타이틀 숨기기
const gameTitle = document.getElementById('gameTitle');
if (gameTitle) {
gameTitle.style.display = 'none';
}
document.body.requestPointerLock();
window.gameInstance.startBGM();
window.gameInstance.startGame();
}
</script>
<script src="game.js" type="module"></script>
</body>
</html>