3D-Airforce-Simulator / index.html
cutechicken's picture
Update index.html
36bd989 verified
<!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>