3D-Airforce-Simulator / index.html
cutechicken's picture
Update index.html
36bd989 verified
Raw History Blame Contribute Delete
44.6 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; } #weaponDisplay { position: absolute; bottom: 20px; right: 20px; background: rgba(0,20,0,0.7); padding: 10px; z-index: 1001; border: 1px solid #0f0; border-radius: 5px; } #ammoDisplay { color: #0f0; font-size: 20px; } #radar { position: absolute; bottom: 60px; left: 20px; width: 200px; height: 200px; background: rgba(30, 30, 30, 0.9); /* 더 μ–΄λ‘μš΄ νšŒμƒ‰ λ°°κ²½ */ border: 2px solid #0f0; border-radius: 50%; z-index: 1001; overflow: hidden; } /* RWR λ””μŠ€ν”Œλ ˆμ΄ μŠ€νƒ€μΌ */ .rwr-display { position: relative; width: 100%; height: 100%; } /* RWR 쀑앙 항곡기 심볼 */ .rwr-center { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 15px; /* 더 μž‘κ²Œ */ height: 15px; /* 더 μž‘κ²Œ */ z-index: 10; } .rwr-aircraft-symbol { width: 100%; height: 100%; position: relative; display: flex; align-items: center; justify-content: center; } .rwr-aircraft-symbol img { width: 100%; height: 100%; object-fit: contain; filter: drop-shadow(0 0 2px #00ff00); /* μ΄ˆλ‘μƒ‰ λΉ›λ‚˜λŠ” 효과 */ } /* RWR 거리 링 */ .rwr-range-ring { position: absolute; border: 1px solid rgba(0, 255, 0, 0.4); border-radius: 50%; top: 50%; left: 50%; transform: translate(-50%, -50%); } .rwr-ring-inner { width: 60px; height: 60px; } .rwr-ring-middle { width: 120px; height: 120px; } .rwr-ring-outer { width: 180px; height: 180px; } /* RWR 쀑앙에 μž‘μ€ 원 μΆ”κ°€ */ .rwr-center-dot { position: absolute; top: calc(50% + 4px); /* 4px μ•„λž˜λ‘œ 이동 */ left: 50%; transform: translate(-50%, -50%); width: 4px; height: 4px; background: #00ff00; border-radius: 50%; z-index: 11; } /* RWR μœ„ν˜‘ ν‘œμ‹œ */ .rwr-threat { position: absolute; width: 15px; height: 15px; transform: translate(-50%, -50%); text-align: center; font-size: 10px; font-weight: bold; z-index: 5; } /* RWR 락온 λ°©ν–₯μ„  */ .rwr-lock-line { position: absolute; background: #ff0000; height: 2px; transform-origin: left center; z-index: 4; animation: lock-pulse 0.5s infinite; } @keyframes lock-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.3; } } /* 미사일 κ²½κ³  νŒ¨λ„ */ .missile-warning-panel { position: fixed; top: 50%; right: 20px; transform: translateY(-50%); background: rgba(255, 0, 0, 0.2); border: 2px solid #ff0000; border-radius: 5px; padding: 10px; z-index: 1500; min-width: 200px; } .missile-warning-item { color: #ff0000; font-size: 16px; font-weight: bold; margin: 5px 0; text-shadow: 0 0 5px #ff0000; animation: warning-blink 0.3s infinite; } @keyframes warning-blink { 0%, 50% { opacity: 1; } 51%, 100% { opacity: 0.7; } } /* 락온 κ²½κ³  ν™”μ‚΄ν‘œ */ .lock-warning-arrow { position: fixed; width: 40px; height: 40px; z-index: 1400; pointer-events: none; } .lock-warning-arrow::before { content: 'β–²'; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); font-size: 30px; color: #ffff00; text-shadow: 0 0 10px #ffff00, 0 0 20px #ffff00; animation: arrow-blink 0.5s infinite; } @keyframes arrow-blink { 0%, 50% { opacity: 1; } 51%, 100% { opacity: 0.3; } } .rwr-direction-text { position: absolute; color: #0f0; font-size: 12px; font-weight: bold; transform: translate(-50%, -50%); text-shadow: 0 0 2px #0f0; } .rwr-north { top: 15px; left: 50%; } .rwr-east { top: 50%; right: 15px; left: auto; transform: translateY(-50%); } .rwr-south { bottom: 15px; left: 50%; top: auto; transform: translateX(-50%); } .rwr-west { top: 50%; left: 15px; } /* RWR μœ„ν˜‘ ν‘œμ‹œ */ .rwr-threat { position: absolute; width: 15px; height: 15px; transform: translate(-50%, -50%); text-align: center; font-size: 10px; font-weight: bold; z-index: 5; } /* μœ„ν˜‘ λ ˆλ²¨λ³„ 색상 */ .rwr-threat.level-low { color: #ffff00; /* λ…Έλž€μƒ‰ */ text-shadow: 0 0 3px #ffff00; } .rwr-threat.level-medium { color: #ff8800; /* 주황색 */ text-shadow: 0 0 3px #ff8800; } .rwr-threat.level-high { color: #ff0000; /* 빨간색 */ text-shadow: 0 0 3px #ff0000; } .rwr-threat.missile-lock { animation: rwr-flash 0.3s infinite; } @keyframes rwr-flash { 0%, 50% { opacity: 1; } 51%, 100% { opacity: 0.3; } } #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 - Deploy Flares</p> <p>G - 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 class="hud-roll-indicator" id="rollIndicator" style=" position: absolute; top: -90px; left: 50%; transform: translateX(-50%); width: 300px; height: 35px; "> <!-- λ‘€ 각도 ν‘œμ‹œ --> <div style="position: relative; width: 100%; height: 100%;"> <!-- 쀑앙 마컀 --> <div style=" position: absolute; top: 0; left: 50%; transform: translateX(-50%); width: 0; height: 0; border-left: 12px solid transparent; border-right: 12px solid transparent; border-top: 18px solid #00ff00; z-index: 2; "></div> <!-- λ‘€ μŠ€μΌ€μΌ λ°°κ²½ --> <div style=" position: absolute; bottom: 5px; left: 0; right: 0; height: 15px; background: rgba(0, 0, 0, 0.3); border: 1px solid rgba(0, 255, 0, 0.3); border-radius: 3px; "></div> <!-- μœ„ν—˜ ꡬ역 ν‘œμ‹œ (Β±30도 이상) --> <div style=" position: absolute; bottom: 5px; left: 0; width: 25%; height: 15px; background: rgba(255, 0, 0, 0.2); border-radius: 3px 0 0 3px; "></div> <div style=" position: absolute; bottom: 5px; right: 0; width: 25%; height: 15px; background: rgba(255, 0, 0, 0.2); border-radius: 0 3px 3px 0; "></div> <!-- λ‘€ μŠ€μΌ€μΌ --> <div style=" position: absolute; bottom: 22px; width: 100%; display: flex; justify-content: space-between; align-items: flex-end; padding: 0 10px; box-sizing: border-box; "> <div style="color: rgba(255, 0, 0, 0.7); font-size: 10px; font-weight: bold;">-60</div> <div style="position: absolute; left: 25%; transform: translateX(-50%); width: 1px; height: 10px; background: rgba(255, 255, 0, 0.6);"></div> <div style="position: absolute; left: 25%; transform: translateX(-50%); color: rgba(255, 255, 0, 0.7); font-size: 10px; top: -15px;">-30</div> <div style="position: absolute; left: 50%; transform: translateX(-50%); width: 2px; height: 12px; background: rgba(0, 255, 0, 0.8);"></div> <div style="position: absolute; left: 50%; transform: translateX(-50%); color: rgba(0, 255, 0, 1); font-size: 11px; font-weight: bold; top: -15px;">0</div> <div style="position: absolute; left: 75%; transform: translateX(-50%); width: 1px; height: 10px; background: rgba(255, 255, 0, 0.6);"></div> <div style="position: absolute; left: 75%; transform: translateX(-50%); color: rgba(255, 255, 0, 0.7); font-size: 10px; top: -15px;">30</div> <div style="color: rgba(255, 0, 0, 0.7); font-size: 10px; font-weight: bold;">60</div> </div> <!-- ν˜„μž¬ λ‘€ ν‘œμ‹œ --> <div id="currentRollMarker" style=" position: absolute; bottom: 5px; left: 50%; transform: translateX(-50%); width: 3px; height: 15px; background: #00ff00; transition: left 0.1s; box-shadow: 0 0 5px #00ff00; "></div> </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="weaponDisplay"> <div style="color: #0f0; font-size: 14px; margin-bottom: 5px;">R: WEAPON CHANGE</div> <div id="ammoDisplay">20MM MG: 940</div> </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" id="rwrSymbol"> <!-- μ΄λ―Έμ§€λŠ” JavaScript둜 λ‘œλ“œ --> </div> </div> <!-- 쀑앙 점 --> <div class="rwr-center-dot"></div> <!-- μœ„ν˜‘ ν‘œμ‹œ μ˜μ—­ --> <div id="rwrThreats"></div> </div> <!-- κΈ°μ‘΄ λ ˆμ΄λ” μš”μ†Œ (μˆ¨κΉ€) --> <div id="radarLine" style="display: none;"></div> </div> </div> <script type="importmap"> { "imports": { "three": "https://unpkg.com/three@0.157.0/build/three.module.js", "three/addons/": "https://unpkg.com/three@0.157.0/examples/jsm/" } } </script> <!-- ν”ΌμΉ˜ λž˜λ” 및 HUD μ—…λ°μ΄νŠΈ 슀크립트 --> <script> // μ „μ—­ λ³€μˆ˜ let gameStarted = false; // κ²Œμž„ μ€€λΉ„ μ™„λ£Œ μ‹œ HUD μ—…λ°μ΄νŠΈ μ‹œμž‘ window.addEventListener('gameReady', function() { console.log('Game ready - starting HUD updates'); // RWR 심볼 이미지 λ‘œλ“œ const rwrSymbol = document.getElementById('rwrSymbol'); if (rwrSymbol) { // 방법 1: img νƒœκ·Έλ‘œ μ‹œλ„ const img = new Image(); img.src = 'effects/symbol.png'; img.style.width = '100%'; img.style.height = '100%'; img.style.objectFit = 'contain'; img.style.filter = 'drop-shadow(0 0 2px #00ff00)'; // μ΄ˆλ‘μƒ‰μœΌλ‘œ λ³€κ²½ img.onload = function() { console.log('RWR symbol image loaded successfully'); rwrSymbol.innerHTML = ''; rwrSymbol.appendChild(img); }; img.onerror = function() { console.log('RWR symbol image failed to load, using fallback'); // 이미지 λ‘œλ“œ μ‹€νŒ¨μ‹œ μž‘μ€ μ΄ˆλ‘μƒ‰ 점으둜 λŒ€μ²΄ rwrSymbol.innerHTML = ` <div style="width: 100%; height: 100%; display: flex; align-items: center; justify-content: center;"> <div style="width: 8px; height: 8px; background: #00ff00; border-radius: 50%; box-shadow: 0 0 4px #00ff00;"></div> </div> `; }; } // ν”ΌμΉ˜ λž˜λ” 초기 μœ„μΉ˜ κ°•μ œ μ„€μ • 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.getHeadingDegrees()); // 쀑앙 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)`; } // λ‘€ 인디케이터 μ—…λ°μ΄νŠΈ const currentRollMarker = document.getElementById('currentRollMarker'); const rollIndicator = document.getElementById('rollIndicator'); if (currentRollMarker && fighter.rotation) { const rollDegrees = fighter.rotation.z * (180 / Math.PI); // -60λ„μ—μ„œ +60도 λ²”μœ„λ₯Ό 전체 λ„ˆλΉ„μ˜ λ°±λΆ„μœ¨λ‘œ λ§€ν•‘ // 300px λ„ˆλΉ„μ—μ„œ μ–‘μͺ½ 10px νŒ¨λ”©μ„ λΊ€ 280px μ‚¬μš© const normalizedRoll = (rollDegrees + 60) / 120; // 0~1 λ²”μœ„λ‘œ μ •κ·œν™” const markerPosition = 10 + (normalizedRoll * 280); // 10px ~ 290px currentRollMarker.style.left = `${markerPosition}px`; currentRollMarker.style.transform = 'translateX(-50%)'; // 둀이 Β±30도λ₯Ό μ΄ˆκ³Όν•˜λ©΄ 색상 λ³€κ²½ if (Math.abs(rollDegrees) > 30) { currentRollMarker.style.background = '#ff0000'; currentRollMarker.style.boxShadow = '0 0 8px #ff0000'; currentRollMarker.style.width = '4px'; } else { currentRollMarker.style.background = '#00ff00'; currentRollMarker.style.boxShadow = '0 0 5px #00ff00'; currentRollMarker.style.width = '3px'; } } // 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); // 락온 쀑인 적이면 RWR에 λ°©ν–₯μ„  ν‘œμ‹œ if (enemy.isLocking && enemy.lockTarget === fighter) { const lockLine = document.createElement('div'); lockLine.className = 'rwr-lock-line'; // μ€‘μ•™μ—μ„œ μ κΉŒμ§€μ˜ μ„  const lineLength = Math.sqrt(Math.pow(x - 100, 2) + Math.pow(y - 100, 2)); const lineAngle = Math.atan2(y - 100, x - 100); lockLine.style.width = `${lineLength}px`; lockLine.style.left = '100px'; lockLine.style.top = '100px'; lockLine.style.transform = `rotate(${lineAngle}rad)`; rwrThreats.appendChild(lockLine); } } }); } // 락온 κ²½κ³  ν™”μ‚΄ν‘œ μ—…λ°μ΄νŠΈ updateLockWarningArrows(fighter); // 미사일 거리 κ²½κ³  μ—…λ°μ΄νŠΈ updateMissileWarnings(fighter); } // 락온 κ²½κ³  ν™”μ‚΄ν‘œ ν‘œμ‹œ function updateLockWarningArrows(fighter) { // κΈ°μ‘΄ ν™”μ‚΄ν‘œ 제거 const existingArrows = document.querySelectorAll('.lock-warning-arrow'); existingArrows.forEach(arrow => arrow.remove()); if (!fighter.beingLockedBy || fighter.beingLockedBy.length === 0) return; fighter.beingLockedBy.forEach(enemy => { if (!enemy.position) return; // ν™”λ©΄ 밖에 μžˆλŠ” 적만 ν™”μ‚΄ν‘œ ν‘œμ‹œ const screenPos = getScreenPosition(enemy.position); if (screenPos) return; // ν™”λ©΄ μ•ˆμ— 있으면 ν™”μ‚΄ν‘œ λΆˆν•„μš” // μƒλŒ€ μœ„μΉ˜ 계산 const relativePos = enemy.position.clone().sub(fighter.position); const angle = Math.atan2(relativePos.x, relativePos.z) - fighter.rotation.y; const arrow = document.createElement('div'); arrow.className = 'lock-warning-arrow'; // ν™”λ©΄ κ°€μž₯μžλ¦¬μ— ν™”μ‚΄ν‘œ 배치 const screenCenterX = window.innerWidth / 2; const screenCenterY = window.innerHeight / 2; const edgeDistance = Math.min(screenCenterX - 100, screenCenterY - 100); const arrowX = screenCenterX + Math.sin(angle) * edgeDistance; const arrowY = screenCenterY - Math.cos(angle) * edgeDistance; arrow.style.left = `${arrowX}px`; arrow.style.top = `${arrowY}px`; // ν™”μ‚΄ν‘œ νšŒμ „ const rotationAngle = angle * (180 / Math.PI); arrow.style.transform = `translate(-50%, -50%) rotate(${rotationAngle}deg)`; document.body.appendChild(arrow); }); } // 미사일 거리 κ²½κ³  ν‘œμ‹œ function updateMissileWarnings(fighter) { // κΈ°μ‘΄ κ²½κ³  νŒ¨λ„ 제거 const existingPanel = document.querySelector('.missile-warning-panel'); if (existingPanel) existingPanel.remove(); if (!fighter.incomingMissiles || fighter.incomingMissiles.length === 0) return; const panel = document.createElement('div'); panel.className = 'missile-warning-panel'; fighter.incomingMissiles.forEach((missile, index) => { if (!missile.position) return; const distance = Math.round(fighter.position.distanceTo(missile.position)); const warningItem = document.createElement('div'); warningItem.className = 'missile-warning-item'; warningItem.textContent = `MISSILE ${index + 1}: ${distance}m`; // 거리에 λ”°λ₯Έ 색상 κ°•μ‘° if (distance < 500) { warningItem.style.fontSize = '20px'; warningItem.style.color = '#ff0000'; warningItem.style.textShadow = '0 0 10px #ff0000, 0 0 20px #ff0000'; } else if (distance < 1000) { warningItem.style.fontSize = '18px'; } panel.appendChild(warningItem); }); document.body.appendChild(panel); } // 3D μœ„μΉ˜λ₯Ό ν™”λ©΄ μ’Œν‘œλ‘œ λ³€ν™˜ν•˜λŠ” 헬퍼 ν•¨μˆ˜ function getScreenPosition(worldPosition) { if (!window.gameInstance || !window.gameInstance.camera) return null; const vector = worldPosition.clone(); vector.project(window.gameInstance.camera); // 카메라 뒀에 μžˆλŠ” κ°μ²΄λŠ” null λ°˜ν™˜ if (vector.z > 1) return null; const x = (vector.x * 0.5 + 0.5) * window.innerWidth; const y = (-vector.y * 0.5 + 0.5) * window.innerHeight; // ν™”λ©΄ 밖에 있으면 null λ°˜ν™˜ if (x < 0 || x > window.innerWidth || y < 0 || y > window.innerHeight) { return null; } return { x, y }; } // 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>
<!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%;
}
/* μˆ˜ν‰μ„  */