cutechicken commited on
Commit
cfe5b2c
ยท
verified ยท
1 Parent(s): 91d385a

Rename index.js to game.js

Browse files
Files changed (2) hide show
  1. game.js +972 -0
  2. index.js +0 -76
game.js ADDED
@@ -0,0 +1,972 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="ko">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>JET FIGHT SIMULATER</title>
7
+ <style>
8
+ body {
9
+ margin: 0;
10
+ padding: 0;
11
+ overflow: hidden;
12
+ font-family: 'Courier New', monospace;
13
+ background: #000;
14
+ cursor: none;
15
+ }
16
+
17
+ #gameContainer {
18
+ position: relative;
19
+ width: 100vw;
20
+ height: 100vh;
21
+ }
22
+
23
+ /* HUD ์Šคํƒ€์ผ */
24
+ .hud {
25
+ position: absolute;
26
+ color: #00ff00;
27
+ font-family: 'Courier New', monospace;
28
+ z-index: 1000;
29
+ text-shadow: 0 0 10px #00ff00;
30
+ }
31
+
32
+ #startScreen {
33
+ position: absolute;
34
+ top: 0;
35
+ left: 0;
36
+ width: 100%;
37
+ height: 100%;
38
+ background: linear-gradient(45deg, #001122, #003366);
39
+ display: flex;
40
+ flex-direction: column;
41
+ justify-content: center;
42
+ align-items: center;
43
+ z-index: 10000;
44
+ color: #00ffff;
45
+ text-align: center;
46
+ }
47
+
48
+ #startScreen h1 {
49
+ font-size: 4rem;
50
+ margin-bottom: 2rem;
51
+ text-shadow: 0 0 20px #00ffff;
52
+ }
53
+
54
+ #startButton {
55
+ font-size: 2rem;
56
+ padding: 1rem 2rem;
57
+ background: transparent;
58
+ border: 2px solid #00ffff;
59
+ color: #00ffff;
60
+ cursor: pointer;
61
+ transition: all 0.3s;
62
+ }
63
+
64
+ #startButton:hover {
65
+ background: #00ffff;
66
+ color: #001122;
67
+ box-shadow: 0 0 20px #00ffff;
68
+ }
69
+
70
+ #loading {
71
+ position: absolute;
72
+ top: 50%;
73
+ left: 50%;
74
+ transform: translate(-50%, -50%);
75
+ color: #00ff00;
76
+ font-size: 2rem;
77
+ z-index: 9999;
78
+ display: none;
79
+ }
80
+
81
+ /* ์†๋„๊ณ„ */
82
+ #speedometer {
83
+ position: absolute;
84
+ bottom: 50px;
85
+ left: 50px;
86
+ width: 150px;
87
+ height: 150px;
88
+ border: 2px solid #00ff00;
89
+ border-radius: 50%;
90
+ display: flex;
91
+ align-items: center;
92
+ justify-content: center;
93
+ background: rgba(0, 0, 0, 0.5);
94
+ }
95
+
96
+ #speedValue {
97
+ font-size: 1.5rem;
98
+ color: #00ff00;
99
+ }
100
+
101
+ /* ๊ณ ๋„๊ณ„ */
102
+ #altimeter {
103
+ position: absolute;
104
+ bottom: 50px;
105
+ right: 50px;
106
+ width: 150px;
107
+ height: 150px;
108
+ border: 2px solid #00ff00;
109
+ border-radius: 50%;
110
+ display: flex;
111
+ align-items: center;
112
+ justify-content: center;
113
+ background: rgba(0, 0, 0, 0.5);
114
+ }
115
+
116
+ #altitudeValue {
117
+ font-size: 1.5rem;
118
+ color: #00ff00;
119
+ }
120
+
121
+ /* ํฌ๋กœ์Šคํ—ค์–ด */
122
+ #crosshair {
123
+ position: absolute;
124
+ top: 50%;
125
+ left: 50%;
126
+ transform: translate(-50%, -50%);
127
+ width: 40px;
128
+ height: 40px;
129
+ border: 2px solid #00ff00;
130
+ border-radius: 50%;
131
+ z-index: 1001;
132
+ pointer-events: none;
133
+ }
134
+
135
+ #crosshair::before {
136
+ content: '';
137
+ position: absolute;
138
+ top: 50%;
139
+ left: 50%;
140
+ transform: translate(-50%, -50%);
141
+ width: 2px;
142
+ height: 20px;
143
+ background: #00ff00;
144
+ }
145
+
146
+ #crosshair::after {
147
+ content: '';
148
+ position: absolute;
149
+ top: 50%;
150
+ left: 50%;
151
+ transform: translate(-50%, -50%);
152
+ width: 20px;
153
+ height: 2px;
154
+ background: #00ff00;
155
+ }
156
+
157
+ .target-detected {
158
+ border-color: #ff0000 !important;
159
+ box-shadow: 0 0 20px #ff0000;
160
+ }
161
+
162
+ .target-detected::before,
163
+ .target-detected::after {
164
+ background: #ff0000 !important;
165
+ }
166
+
167
+ /* ์Šค๋กœํ‹€ ๊ฒŒ์ด์ง€ */
168
+ #throttleGauge {
169
+ position: absolute;
170
+ left: 30px;
171
+ top: 50%;
172
+ transform: translateY(-50%);
173
+ width: 30px;
174
+ height: 200px;
175
+ border: 2px solid #00ff00;
176
+ background: rgba(0, 0, 0, 0.5);
177
+ }
178
+
179
+ #throttleBar {
180
+ position: absolute;
181
+ bottom: 0;
182
+ width: 100%;
183
+ background: #00ff00;
184
+ transition: height 0.1s;
185
+ }
186
+
187
+ /* ์ƒํƒœ ์ •๋ณด */
188
+ #statusInfo {
189
+ position: absolute;
190
+ top: 30px;
191
+ left: 30px;
192
+ color: #00ff00;
193
+ font-size: 1.2rem;
194
+ line-height: 1.5;
195
+ }
196
+
197
+ /* ๋ ˆ์ด๋” */
198
+ #radar {
199
+ position: absolute;
200
+ top: 50px;
201
+ right: 200px;
202
+ width: 120px;
203
+ height: 120px;
204
+ border: 2px solid #00ff00;
205
+ border-radius: 50%;
206
+ background: rgba(0, 20, 0, 0.3);
207
+ overflow: hidden;
208
+ }
209
+
210
+ .enemy-dot {
211
+ position: absolute;
212
+ width: 4px;
213
+ height: 4px;
214
+ background: #ff0000;
215
+ border-radius: 50%;
216
+ transform: translate(-50%, -50%);
217
+ }
218
+
219
+ /* ๋ฌด๊ธฐ ์ •๋ณด */
220
+ #weaponInfo {
221
+ position: absolute;
222
+ bottom: 30px;
223
+ left: 50%;
224
+ transform: translateX(-50%);
225
+ color: #00ff00;
226
+ font-size: 1.2rem;
227
+ text-align: center;
228
+ }
229
+
230
+ /* G-Force ํ‘œ์‹œ */
231
+ #gForce {
232
+ position: absolute;
233
+ top: 30px;
234
+ right: 30px;
235
+ color: #ffff00;
236
+ font-size: 1.5rem;
237
+ }
238
+ </style>
239
+ </head>
240
+ <body>
241
+ <!-- ์‹œ์ž‘ ํ™”๋ฉด -->
242
+ <div id="startScreen">
243
+ <h1>FIGHTER ACE</h1>
244
+ <p>๊ณต์ค‘์ „ ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ</p>
245
+ <button id="startButton" onclick="startGame()">TAKE OFF</button>
246
+ <div style="margin-top: 2rem; font-size: 1rem;">
247
+ <p>MOUSE: CONTROL | WASD: THROTLE/RUDER | CLICK: FIRE</p>
248
+ </div>
249
+ </div>
250
+
251
+ <!-- ๋กœ๋”ฉ ํ™”๋ฉด -->
252
+ <div id="loading">
253
+ <div class="loading-text">LOADING...</div>
254
+ </div>
255
+
256
+ <!-- ๊ฒŒ์ž„ ์ปจํ…Œ์ด๋„ˆ -->
257
+ <div id="gameContainer">
258
+ <!-- HUD ์š”์†Œ๋“ค -->
259
+ <div id="crosshair"></div>
260
+
261
+ <div id="speedometer" class="hud">
262
+ <div id="speedValue">0 KT</div>
263
+ </div>
264
+
265
+ <div id="altimeter" class="hud">
266
+ <div id="altitudeValue">0 FT</div>
267
+ </div>
268
+
269
+ <div id="throttleGauge" class="hud">
270
+ <div id="throttleBar" style="height: 0%;"></div>
271
+ </div>
272
+
273
+ <div id="statusInfo" class="hud">
274
+ <div>Mission Time: <span id="missionTime">00:00</span></div>
275
+ <div>Targets: <span id="targetCount">0</span></div>
276
+ <div>Score: <span id="score">0</span></div>
277
+ </div>
278
+
279
+ <div id="radar" class="hud"></div>
280
+
281
+ <div id="weaponInfo" class="hud">
282
+ <div>Missiles: <span id="missileCount">4</span></div>
283
+ <div>Ammo: <span id="ammoCount">200</span></div>
284
+ </div>
285
+
286
+ <div id="gForce" class="hud">
287
+ G: <span id="gValue">1.0</span>
288
+ </div>
289
+ </div>
290
+
291
+ <script type="module">
292
+ import * as THREE from 'https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js';
293
+ import { GLTFLoader } from 'https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js';
294
+
295
+ // ๊ฒŒ์ž„ ์ƒ์ˆ˜
296
+ const GAME_CONSTANTS = {
297
+ MISSION_DURATION: 300, // 5๋ถ„
298
+ MAP_SIZE: 10000,
299
+ MAX_ALTITUDE: 8000,
300
+ MIN_ALTITUDE: 100,
301
+ MAX_SPEED: 500,
302
+ STALL_SPEED: 80,
303
+ GRAVITY: 9.8,
304
+ AIR_DENSITY: 1.225,
305
+ MOUSE_SENSITIVITY: 0.002,
306
+ MAX_G_FORCE: 9.0
307
+ };
308
+
309
+ // ์ „ํˆฌ๊ธฐ ํด๋ž˜์Šค
310
+ class Fighter {
311
+ constructor() {
312
+ this.mesh = null;
313
+ this.isLoaded = false;
314
+
315
+ // ๋ฌผ๋ฆฌ ์†์„ฑ
316
+ this.position = new THREE.Vector3(0, 1000, 0);
317
+ this.velocity = new THREE.Vector3(0, 0, 0);
318
+ this.acceleration = new THREE.Vector3(0, 0, 0);
319
+ this.rotation = new THREE.Euler(0, 0, 0);
320
+ this.angularVelocity = new THREE.Vector3(0, 0, 0);
321
+
322
+ // ๋น„ํ–‰ ์†์„ฑ
323
+ this.throttle = 0.3; // 0-1
324
+ this.speed = 0;
325
+ this.altitude = 1000;
326
+ this.gForce = 1.0;
327
+ this.stallWarning = false;
328
+
329
+ // ๋งˆ์šฐ์Šค ์ž…๋ ฅ
330
+ this.mouseInput = { x: 0, y: 0 };
331
+ this.targetPitch = 0;
332
+ this.targetYaw = 0;
333
+ this.targetRoll = 0;
334
+
335
+ // ๋ฌด๊ธฐ
336
+ this.missiles = 4;
337
+ this.ammo = 200;
338
+ this.bullets = [];
339
+
340
+ // ์นด๋ฉ”๋ผ ์˜คํ”„์…‹
341
+ this.cameraOffset = new THREE.Vector3(0, 3, -15);
342
+ }
343
+
344
+ async initialize(scene, loader) {
345
+ try {
346
+ const result = await loader.loadAsync('/models/f-15.glb');
347
+ this.mesh = result.scene;
348
+ this.mesh.position.copy(this.position);
349
+ this.mesh.scale.set(1, 1, 1);
350
+
351
+ // ๊ทธ๋ฆผ์ž ์„ค์ •
352
+ this.mesh.traverse((child) => {
353
+ if (child.isMesh) {
354
+ child.castShadow = true;
355
+ child.receiveShadow = true;
356
+ }
357
+ });
358
+
359
+ scene.add(this.mesh);
360
+ this.isLoaded = true;
361
+ } catch (error) {
362
+ console.error('์ „ํˆฌ๊ธฐ ๋ชจ๋ธ ๋กœ๋”ฉ ์‹คํŒจ:', error);
363
+ this.isLoaded = false;
364
+ }
365
+ }
366
+
367
+ updateMouseInput(deltaX, deltaY) {
368
+ // ๋งˆ์šฐ์Šค ์›€์ง์ž„์„ ์กฐ์ข…๊ฐ„ ์ž…๋ ฅ์œผ๋กœ ๋ณ€ํ™˜
369
+ this.mouseInput.x += deltaX * GAME_CONSTANTS.MOUSE_SENSITIVITY;
370
+ this.mouseInput.y += deltaY * GAME_CONSTANTS.MOUSE_SENSITIVITY;
371
+
372
+ // ์ œํ•œ
373
+ this.mouseInput.x = Math.max(-1, Math.min(1, this.mouseInput.x));
374
+ this.mouseInput.y = Math.max(-1, Math.min(1, this.mouseInput.y));
375
+ }
376
+
377
+ updateControls(keys, deltaTime) {
378
+ // ์Šค๋กœํ‹€ ์กฐ์ ˆ (W/S)
379
+ if (keys.w) this.throttle = Math.min(1, this.throttle + deltaTime * 0.5);
380
+ if (keys.s) this.throttle = Math.max(0, this.throttle - deltaTime * 0.5);
381
+
382
+ // ๋Ÿฌ๋” (A/D)
383
+ let rudderInput = 0;
384
+ if (keys.a) rudderInput = -1;
385
+ if (keys.d) rudderInput = 1;
386
+
387
+ // ๋ชฉํ‘œ ํšŒ์ „๊ฐ ๊ณ„์‚ฐ
388
+ this.targetPitch = -this.mouseInput.y * Math.PI / 6; // ยฑ30๋„
389
+ this.targetYaw += rudderInput * deltaTime * 2; // ๋Ÿฌ๋”
390
+ this.targetRoll = -this.mouseInput.x * Math.PI / 4; // ยฑ45๋„
391
+
392
+ // ๋งˆ์šฐ์Šค ์ž…๋ ฅ ๊ฐ์‡ 
393
+ this.mouseInput.x *= 0.95;
394
+ this.mouseInput.y *= 0.95;
395
+ }
396
+
397
+ updatePhysics(deltaTime) {
398
+ if (!this.mesh) return;
399
+
400
+ // ํ˜„์žฌ ํšŒ์ „์„ ๋ชฉํ‘œ ํšŒ์ „์œผ๋กœ ๋ถ€๋“œ๋Ÿฝ๊ฒŒ ์ด๋™
401
+ const lerpFactor = deltaTime * 2;
402
+ this.rotation.x = THREE.MathUtils.lerp(this.rotation.x, this.targetPitch, lerpFactor);
403
+ this.rotation.y = THREE.MathUtils.lerp(this.rotation.y, this.targetYaw, lerpFactor);
404
+ this.rotation.z = THREE.MathUtils.lerp(this.rotation.z, this.targetRoll, lerpFactor);
405
+
406
+ // ์—”์ง„ ์ถ”๋ ฅ ๊ณ„์‚ฐ
407
+ const thrust = this.throttle * 50000; // ๋‰ดํ„ด
408
+ const mass = 20000; // kg
409
+
410
+ // ์ „์ง„ ๋ฐฉํ–ฅ ๋ฒกํ„ฐ
411
+ const forward = new THREE.Vector3(0, 0, 1);
412
+ forward.applyEuler(this.rotation);
413
+
414
+ // ์ถ”๋ ฅ ๊ฐ€์†๋„
415
+ const thrustAccel = forward.multiplyScalar(thrust / mass);
416
+
417
+ // ์ค‘๋ ฅ
418
+ const gravity = new THREE.Vector3(0, -GAME_CONSTANTS.GRAVITY, 0);
419
+
420
+ // ํ•ญ๋ ฅ (๋‹จ์ˆœํ™”)
421
+ const drag = this.velocity.clone().multiplyScalar(-0.1 * this.velocity.length());
422
+
423
+ // ์ด ๊ฐ€์†๋„
424
+ this.acceleration = thrustAccel.add(gravity).add(drag);
425
+
426
+ // ์†๋„ ์—…๋ฐ์ดํŠธ
427
+ this.velocity.add(this.acceleration.clone().multiplyScalar(deltaTime));
428
+
429
+ // ์œ„์น˜ ์—…๋ฐ์ดํŠธ
430
+ this.position.add(this.velocity.clone().multiplyScalar(deltaTime));
431
+
432
+ // ๊ณ ๋„ ์ œํ•œ
433
+ if (this.position.y < GAME_CONSTANTS.MIN_ALTITUDE) {
434
+ this.position.y = GAME_CONSTANTS.MIN_ALTITUDE;
435
+ this.velocity.y = Math.max(0, this.velocity.y);
436
+ }
437
+
438
+ // G-Force ๊ณ„์‚ฐ
439
+ const acceleration_magnitude = this.acceleration.length();
440
+ this.gForce = acceleration_magnitude / GAME_CONSTANTS.์‹ค์ œ ์ค‘๋ ฅ;
441
+
442
+ // ๋ฉ”์‹œ ์œ„์น˜์™€ ํšŒ์ „ ์—…๋ฐ์ดํŠธ
443
+ this.mesh.position.copy(this.position);
444
+ this.mesh.rotation.copy(this.rotation);
445
+
446
+ // ์†๋„์™€ ๊ณ ๋„ ๊ณ„์‚ฐ
447
+ this.speed = this.velocity.length() * 1.94384; // m/s to knots
448
+ this.altitude = this.position.y * 3.28084; // m to feet
449
+
450
+ // ์‹ค์† ๊ฒฝ๊ณ 
451
+ this.stallWarning = this.speed < GAME_CONSTANTS.STALL_SPEED;
452
+ }
453
+
454
+ shoot(scene) {
455
+ if (this.ammo <= 0) return;
456
+
457
+ this.ammo--;
458
+
459
+ // ์ด์•Œ ์ƒ์„ฑ
460
+ const bulletGeometry = new THREE.SphereGeometry(0.1);
461
+ const bulletMaterial = new THREE.MeshBasicMaterial({ color: 0xffff00 });
462
+ const bullet = new THREE.Mesh(bulletGeometry, bulletMaterial);
463
+
464
+ // ์ด๊ตฌ ์œ„์น˜ ๊ณ„์‚ฐ
465
+ const muzzleOffset = new THREE.Vector3(0, 0, 5);
466
+ muzzleOffset.applyEuler(this.rotation);
467
+ bullet.position.copy(this.position).add(muzzleOffset);
468
+
469
+ // ์ด์•Œ ์†๋„
470
+ const bulletSpeed = 1000; // m/s
471
+ const direction = new THREE.Vector3(0, 0, 1);
472
+ direction.applyEuler(this.rotation);
473
+ bullet.velocity = direction.multiplyScalar(bulletSpeed).add(this.velocity);
474
+
475
+ scene.add(bullet);
476
+ this.bullets.push(bullet);
477
+
478
+ // ๋ฐœ์‚ฌ ์‚ฌ์šด๋“œ (์˜ˆ์‹œ)
479
+ // const audio = new Audio('sounds/gunfire.ogg');
480
+ // audio.play();
481
+ }
482
+
483
+ updateBullets(scene, deltaTime) {
484
+ for (let i = this.bullets.length - 1; i >= 0; i--) {
485
+ const bullet = this.bullets[i];
486
+ bullet.position.add(bullet.velocity.clone().multiplyScalar(deltaTime));
487
+
488
+ // ์ด์•Œ ์ œ๊ฑฐ ์กฐ๊ฑด
489
+ if (bullet.position.distanceTo(this.position) > 5000 || bullet.position.y < 0) {
490
+ scene.remove(bullet);
491
+ this.bullets.splice(i, 1);
492
+ }
493
+ }
494
+ }
495
+
496
+ getCameraPosition() {
497
+ // ์ „ํˆฌ๊ธฐ ๋’ค์ชฝ ์นด๋ฉ”๋ผ ์œ„์น˜ ๊ณ„์‚ฐ
498
+ const offset = this.cameraOffset.clone();
499
+ offset.applyEuler(this.rotation);
500
+ return this.position.clone().add(offset);
501
+ }
502
+
503
+ getCameraTarget() {
504
+ // ์นด๋ฉ”๋ผ๊ฐ€ ๋ฐ”๋ผ๋ณผ ์œ„์น˜ (์ „ํˆฌ๊ธฐ ์•ฝ๊ฐ„ ์•ž)
505
+ const target = new THREE.Vector3(0, 0, 5);
506
+ target.applyEuler(this.rotation);
507
+ return this.position.clone().add(target);
508
+ }
509
+ }
510
+
511
+ // ์  ์ „ํˆฌ๊ธฐ ํด๋ž˜์Šค
512
+ class EnemyFighter {
513
+ constructor(scene, position) {
514
+ this.mesh = null;
515
+ this.isLoaded = false;
516
+ this.scene = scene;
517
+ this.position = position.clone();
518
+ this.velocity = new THREE.Vector3(0, 0, 0);
519
+ this.rotation = new THREE.Euler(0, 0, 0);
520
+ this.health = 100;
521
+ this.speed = 200; // knots
522
+ this.bullets = [];
523
+ this.lastShootTime = 0;
524
+
525
+ // AI ์ƒํƒœ
526
+ this.aiState = 'patrol';
527
+ this.target = null;
528
+ this.lastStateChange = 0;
529
+ }
530
+
531
+ async initialize(loader) {
532
+ try {
533
+ const result = await loader.loadAsync('/models/mig-29.glb');
534
+ this.mesh = result.scene;
535
+ this.mesh.position.copy(this.position);
536
+ this.mesh.scale.set(1, 1, 1);
537
+
538
+ this.mesh.traverse((child) => {
539
+ if (child.isMesh) {
540
+ child.castShadow = true;
541
+ child.receiveShadow = true;
542
+ }
543
+ });
544
+
545
+ this.scene.add(this.mesh);
546
+ this.isLoaded = true;
547
+ } catch (error) {
548
+ console.error('์  ์ „ํˆฌ๊ธฐ ๋ชจ๋ธ ๋กœ๋”ฉ ์‹คํŒจ:', error);
549
+ this.isLoaded = false;
550
+ }
551
+ }
552
+
553
+ update(playerPosition, deltaTime) {
554
+ if (!this.mesh || !this.isLoaded) return;
555
+
556
+ // ๊ฐ„๋‹จํ•œ AI ๋กœ์ง
557
+ const distanceToPlayer = this.position.distanceTo(playerPosition);
558
+
559
+ if (distanceToPlayer < 2000) {
560
+ // ํ”Œ๋ ˆ์ด์–ด ์ถ”์ 
561
+ const direction = new THREE.Vector3()
562
+ .subVectors(playerPosition, this.position)
563
+ .normalize();
564
+
565
+ this.velocity.copy(direction).multiplyScalar(this.speed * 0.5144); // knots to m/s
566
+
567
+ // ํšŒ์ „ ๊ณ„์‚ฐ
568
+ this.rotation.y = Math.atan2(direction.x, direction.z);
569
+ this.rotation.x = Math.atan2(-direction.y, Math.sqrt(direction.x * direction.x + direction.z * direction.z));
570
+ }
571
+
572
+ // ์œ„์น˜ ์—…๋ฐ์ดํŠธ
573
+ this.position.add(this.velocity.clone().multiplyScalar(deltaTime));
574
+ this.mesh.position.copy(this.position);
575
+ this.mesh.rotation.copy(this.rotation);
576
+
577
+ // ๊ณต๊ฒฉ
578
+ if (distanceToPlayer < 1000 && Date.now() - this.lastShootTime > 2000) {
579
+ this.shoot();
580
+ }
581
+
582
+ // ์ด์•Œ ์—…๋ฐ์ดํŠธ
583
+ this.updateBullets(deltaTime);
584
+ }
585
+
586
+ shoot() {
587
+ this.lastShootTime = Date.now();
588
+
589
+ const bulletGeometry = new THREE.SphereGeometry(0.1);
590
+ const bulletMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000 });
591
+ const bullet = new THREE.Mesh(bulletGeometry, bulletMaterial);
592
+
593
+ const muzzleOffset = new THREE.Vector3(0, 0, 3);
594
+ muzzleOffset.applyEuler(this.rotation);
595
+ bullet.position.copy(this.position).add(muzzleOffset);
596
+
597
+ const direction = new THREE.Vector3(0, 0, 1);
598
+ direction.applyEuler(this.rotation);
599
+ bullet.velocity = direction.multiplyScalar(800);
600
+
601
+ this.scene.add(bullet);
602
+ this.bullets.push(bullet);
603
+ }
604
+
605
+ updateBullets(deltaTime) {
606
+ for (let i = this.bullets.length - 1; i >= 0; i--) {
607
+ const bullet = this.bullets[i];
608
+ bullet.position.add(bullet.velocity.clone().multiplyScalar(deltaTime));
609
+
610
+ if (bullet.position.distanceTo(this.position) > 3000 || bullet.position.y < 0) {
611
+ this.scene.remove(bullet);
612
+ this.bullets.splice(i, 1);
613
+ }
614
+ }
615
+ }
616
+
617
+ takeDamage(damage) {
618
+ this.health -= damage;
619
+ return this.health <= 0;
620
+ }
621
+
622
+ destroy() {
623
+ if (this.mesh) {
624
+ this.scene.remove(this.mesh);
625
+ this.bullets.forEach(bullet => this.scene.remove(bullet));
626
+ this.bullets = [];
627
+ this.isLoaded = false;
628
+ }
629
+ }
630
+ }
631
+
632
+ // ๊ฒŒ์ž„ ํด๋ž˜์Šค
633
+ class Game {
634
+ constructor() {
635
+ this.scene = new THREE.Scene();
636
+ this.camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 20000);
637
+ this.renderer = new THREE.WebGLRenderer({ antialias: true });
638
+ this.renderer.setSize(window.innerWidth, window.innerHeight);
639
+ this.renderer.shadowMap.enabled = true;
640
+ this.renderer.shadowMap.type = THREE.PCFSoftShadowMap;
641
+ document.getElementById('gameContainer').appendChild(this.renderer.domElement);
642
+
643
+ this.loader = new GLTFLoader();
644
+ this.fighter = new Fighter();
645
+ this.enemies = [];
646
+ this.isLoaded = false;
647
+ this.isGameOver = false;
648
+ this.missionTime = 0;
649
+ this.score = 0;
650
+ this.lastTime = performance.now();
651
+
652
+ // ์ž…๋ ฅ ์ƒํƒœ
653
+ this.keys = { w: false, a: false, s: false, d: false };
654
+ this.mouse = { x: 0, y: 0 };
655
+
656
+ this.setupEventListeners();
657
+ }
658
+
659
+ async initialize() {
660
+ try {
661
+ document.getElementById('loading').style.display = 'block';
662
+
663
+ // ์”ฌ ์„ค์ •
664
+ this.setupScene();
665
+
666
+ // ์ „ํˆฌ๊ธฐ ์ดˆ๊ธฐํ™”
667
+ await this.fighter.initialize(this.scene, this.loader);
668
+
669
+ if (!this.fighter.isLoaded) {
670
+ throw new Error('์ „ํˆฌ๊ธฐ ๋กœ๋”ฉ ์‹คํŒจ');
671
+ }
672
+
673
+ // ์  ์ƒ์„ฑ
674
+ this.spawnEnemies();
675
+
676
+ this.isLoaded = true;
677
+ document.getElementById('loading').style.display = 'none';
678
+
679
+ // ๊ฒŒ์ž„ ์‹œ์ž‘
680
+ this.animate();
681
+ this.startMissionTimer();
682
+
683
+ } catch (error) {
684
+ console.error('๊ฒŒ์ž„ ์ดˆ๊ธฐํ™” ์‹คํŒจ:', error);
685
+ this.handleLoadingError();
686
+ }
687
+ }
688
+
689
+ setupScene() {
690
+ // ํ•˜๋Š˜ ๊ทธ๋ผ๋ฐ์ด์…˜
691
+ this.scene.background = new THREE.Color(0x87CEEB);
692
+
693
+ // ์•ˆ๊ฐœ
694
+ this.scene.fog = new THREE.Fog(0x87CEEB, 1000, 15000);
695
+
696
+ // ์กฐ๋ช…
697
+ const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
698
+ this.scene.add(ambientLight);
699
+
700
+ const directionalLight = new THREE.DirectionalLight(0xffffff, 1.0);
701
+ directionalLight.position.set(1000, 2000, 1000);
702
+ directionalLight.castShadow = true;
703
+ directionalLight.shadow.mapSize.width = 2048;
704
+ directionalLight.shadow.mapSize.height = 2048;
705
+ directionalLight.shadow.camera.near = 0.5;
706
+ directionalLight.shadow.camera.far = 10000;
707
+ directionalLight.shadow.camera.left = -5000;
708
+ directionalLight.shadow.camera.right = 5000;
709
+ directionalLight.shadow.camera.top = 5000;
710
+ directionalLight.shadow.camera.bottom = -5000;
711
+ this.scene.add(directionalLight);
712
+
713
+ // ์ง€ํ˜• (๊ฐ„๋‹จํ•œ ํ‰๋ฉด)
714
+ const groundGeometry = new THREE.PlaneGeometry(20000, 20000);
715
+ const groundMaterial = new THREE.MeshLambertMaterial({ color: 0x8B4513 });
716
+ const ground = new THREE.Mesh(groundGeometry, groundMaterial);
717
+ ground.rotation.x = -Math.PI / 2;
718
+ ground.receiveShadow = true;
719
+ this.scene.add(ground);
720
+
721
+ // ๊ตฌ๋ฆ„ (๊ฐ„๋‹จํ•œ ์˜ˆ์‹œ)
722
+ this.addClouds();
723
+ }
724
+
725
+ addClouds() {
726
+ const cloudGeometry = new THREE.SphereGeometry(100, 8, 6);
727
+ const cloudMaterial = new THREE.MeshLambertMaterial({
728
+ color: 0xffffff,
729
+ transparent: true,
730
+ opacity: 0.7
731
+ });
732
+
733
+ for (let i = 0; i < 50; i++) {
734
+ const cloud = new THREE.Mesh(cloudGeometry, cloudMaterial);
735
+ cloud.position.set(
736
+ (Math.random() - 0.5) * 10000,
737
+ Math.random() * 2000 + 500,
738
+ (Math.random() - 0.5) * 10000
739
+ );
740
+ cloud.scale.set(
741
+ Math.random() * 2 + 1,
742
+ Math.random() * 2 + 1,
743
+ Math.random() * 2 + 1
744
+ );
745
+ this.scene.add(cloud);
746
+ }
747
+ }
748
+
749
+ spawnEnemies() {
750
+ for (let i = 0; i < 3; i++) {
751
+ const position = new THREE.Vector3(
752
+ (Math.random() - 0.5) * 5000,
753
+ Math.random() * 2000 + 1000,
754
+ (Math.random() - 0.5) * 5000
755
+ );
756
+
757
+ const enemy = new EnemyFighter(this.scene, position);
758
+ enemy.initialize(this.loader);
759
+ this.enemies.push(enemy);
760
+ }
761
+ }
762
+
763
+ setupEventListeners() {
764
+ // ํ‚ค๋ณด๋“œ ์ด๋ฒคํŠธ
765
+ document.addEventListener('keydown', (event) => {
766
+ if (this.isGameOver) return;
767
+ switch(event.code) {
768
+ case 'KeyW': this.keys.w = true; break;
769
+ case 'KeyA': this.keys.a = true; break;
770
+ case 'KeyS': this.keys.s = true; break;
771
+ case 'KeyD': this.keys.d = true; break;
772
+ }
773
+ });
774
+
775
+ document.addEventListener('keyup', (event) => {
776
+ switch(event.code) {
777
+ case 'KeyW': this.keys.w = false; break;
778
+ case 'KeyA': this.keys.a = false; break;
779
+ case 'KeyS': this.keys.s = false; break;
780
+ case 'KeyD': this.keys.d = false; break;
781
+ }
782
+ });
783
+
784
+ // ๋งˆ์šฐ์Šค ์ด๋ฒคํŠธ
785
+ document.addEventListener('mousemove', (event) => {
786
+ if (!document.pointerLockElement || this.isGameOver) return;
787
+
788
+ const deltaX = event.movementX || 0;
789
+ const deltaY = event.movementY || 0;
790
+
791
+ this.fighter.updateMouseInput(deltaX, deltaY);
792
+ });
793
+
794
+ document.addEventListener('click', () => {
795
+ if (!document.pointerLockElement) {
796
+ document.body.requestPointerLock();
797
+ } else if (!this.isGameOver && this.fighter.isLoaded) {
798
+ this.fighter.shoot(this.scene);
799
+ }
800
+ });
801
+
802
+ // ์œˆ๋„์šฐ ๋ฆฌ์‚ฌ์ด์ฆˆ
803
+ window.addEventListener('resize', () => {
804
+ this.camera.aspect = window.innerWidth / window.innerHeight;
805
+ this.camera.updateProjectionMatrix();
806
+ this.renderer.setSize(window.innerWidth, window.innerHeight);
807
+ });
808
+ }
809
+
810
+ startMissionTimer() {
811
+ setInterval(() => {
812
+ if (!this.isGameOver) {
813
+ this.missionTime++;
814
+ const minutes = Math.floor(this.missionTime / 60);
815
+ const seconds = this.missionTime % 60;
816
+ document.getElementById('missionTime').textContent =
817
+ `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
818
+ }
819
+ }, 1000);
820
+ }
821
+
822
+ updateUI() {
823
+ if (this.fighter.isLoaded) {
824
+ document.getElementById('speedValue').textContent = `${Math.round(this.fighter.speed)} KT`;
825
+ document.getElementById('altitudeValue').textContent = `${Math.round(this.fighter.altitude)} FT`;
826
+ document.getElementById('throttleBar').style.height = `${this.fighter.throttle * 100}%`;
827
+ document.getElementById('gValue').textContent = this.fighter.gForce.toFixed(1);
828
+ document.getElementById('ammoCount').textContent = this.fighter.ammo;
829
+ document.getElementById('missileCount').textContent = this.fighter.missiles;
830
+ }
831
+
832
+ document.getElementById('targetCount').textContent = this.enemies.length;
833
+ document.getElementById('score').textContent = this.score;
834
+ }
835
+
836
+ checkCollisions() {
837
+ // ํ”Œ๋ ˆ์ด์–ด ์ด์•Œ๊ณผ ์  ์ถฉ๋Œ
838
+ for (let i = this.fighter.bullets.length - 1; i >= 0; i--) {
839
+ const bullet = this.fighter.bullets[i];
840
+
841
+ for (let j = this.enemies.length - 1; j >= 0; j--) {
842
+ const enemy = this.enemies[j];
843
+ if (!enemy.mesh || !enemy.isLoaded) continue;
844
+
845
+ const distance = bullet.position.distanceTo(enemy.position);
846
+ if (distance < 10) {
847
+ // ๋ช…์ค‘!
848
+ this.scene.remove(bullet);
849
+ this.fighter.bullets.splice(i, 1);
850
+
851
+ if (enemy.takeDamage(25)) {
852
+ enemy.destroy();
853
+ this.enemies.splice(j, 1);
854
+ this.score += 100;
855
+ }
856
+ break;
857
+ }
858
+ }
859
+ }
860
+
861
+ // ์  ์ด์•Œ๊ณผ ํ”Œ๋ ˆ์ด์–ด ์ถฉ๋Œ
862
+ this.enemies.forEach(enemy => {
863
+ enemy.bullets.forEach((bullet, index) => {
864
+ const distance = bullet.position.distanceTo(this.fighter.position);
865
+ if (distance < 15) {
866
+ // ํ”ผ๊ฒฉ!
867
+ this.scene.remove(bullet);
868
+ enemy.bullets.splice(index, 1);
869
+ this.endGame(false);
870
+ }
871
+ });
872
+ });
873
+ }
874
+
875
+ animate() {
876
+ if (this.isGameOver) return;
877
+
878
+ requestAnimationFrame(() => this.animate());
879
+
880
+ const currentTime = performance.now();
881
+ const deltaTime = Math.min((currentTime - this.lastTime) / 1000, 0.1);
882
+ this.lastTime = currentTime;
883
+
884
+ if (this.isLoaded && this.fighter.isLoaded) {
885
+ // ์ „ํˆฌ๊ธฐ ์—…๋ฐ์ดํŠธ
886
+ this.fighter.updateControls(this.keys, deltaTime);
887
+ this.fighter.updatePhysics(deltaTime);
888
+ this.fighter.updateBullets(this.scene, deltaTime);
889
+
890
+ // ์นด๋ฉ”๋ผ ์—…๋ฐ์ดํŠธ
891
+ const cameraPos = this.fighter.getCameraPosition();
892
+ const cameraTarget = this.fighter.getCameraTarget();
893
+ this.camera.position.copy(cameraPos);
894
+ this.camera.lookAt(cameraTarget);
895
+
896
+ // ์  ์—…๋ฐ์ดํŠธ
897
+ this.enemies.forEach(enemy => {
898
+ enemy.update(this.fighter.position, deltaTime);
899
+ });
900
+
901
+ // ์ถฉ๋Œ ๊ฒ€์‚ฌ
902
+ this.checkCollisions();
903
+
904
+ // UI ์—…๋ฐ์ดํŠธ
905
+ this.updateUI();
906
+
907
+ // ์Šน๋ฆฌ ์กฐ๊ฑด ํ™•์ธ
908
+ if (this.enemies.length === 0) {
909
+ this.endGame(true);
910
+ }
911
+ }
912
+
913
+ this.renderer.render(this.scene, this.camera);
914
+ }
915
+
916
+ endGame(victory = false) {
917
+ this.isGameOver = true;
918
+
919
+ const gameOverDiv = document.createElement('div');
920
+ gameOverDiv.style.position = 'absolute';
921
+ gameOverDiv.style.top = '50%';
922
+ gameOverDiv.style.left = '50%';
923
+ gameOverDiv.style.transform = 'translate(-50%, -50%)';
924
+ gameOverDiv.style.color = victory ? '#00ff00' : '#ff0000';
925
+ gameOverDiv.style.fontSize = '48px';
926
+ gameOverDiv.style.backgroundColor = 'rgba(0, 0, 0, 0.8)';
927
+ gameOverDiv.style.padding = '20px';
928
+ gameOverDiv.style.borderRadius = '10px';
929
+ gameOverDiv.style.textAlign = 'center';
930
+ gameOverDiv.style.zIndex = '10000';
931
+
932
+ gameOverDiv.innerHTML = `
933
+ ${victory ? 'MISSION ACCOMPLISHED!' : 'SHOT DOWN!'}<br>
934
+ Score: ${this.score}<br>
935
+ Mission Time: ${Math.floor(this.missionTime / 60)}:${(this.missionTime % 60).toString().padStart(2, '0')}<br>
936
+ <button onclick="location.reload()"
937
+ style="font-size: 24px; padding: 10px; margin-top: 20px;
938
+ cursor: pointer; background: #00ff00; border: none;
939
+ color: black; border-radius: 5px;">
940
+ New Mission
941
+ </button>
942
+ `;
943
+
944
+ document.body.appendChild(gameOverDiv);
945
+ document.exitPointerLock();
946
+ }
947
+
948
+ handleLoadingError() {
949
+ document.getElementById('loading').innerHTML =
950
+ '<div style="color: red;">๋กœ๋”ฉ ์‹คํŒจ. ํŽ˜์ด์ง€๋ฅผ ์ƒˆ๋กœ๊ณ ์นจํ•ด ์ฃผ์„ธ์š”.</div>';
951
+ }
952
+ }
953
+
954
+ // ๊ฒŒ์ž„ ์‹œ์ž‘ ํ•จ์ˆ˜
955
+ window.startGame = function() {
956
+ document.getElementById('startScreen').style.display = 'none';
957
+ document.body.requestPointerLock();
958
+
959
+ if (!window.gameInstance) {
960
+ window.gameInstance = new Game();
961
+ }
962
+
963
+ window.gameInstance.initialize();
964
+ };
965
+
966
+ // ๊ฒŒ์ž„ ์ธ์Šคํ„ด์Šค ์ดˆ๊ธฐํ™”
967
+ document.addEventListener('DOMContentLoaded', () => {
968
+ console.log('์ „ํˆฌ๊ธฐ ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ ์ค€๋น„ ์™„๋ฃŒ');
969
+ });
970
+ </script>
971
+ </body>
972
+ </html>
index.js DELETED
@@ -1,76 +0,0 @@
1
- import { pipeline } from 'https://cdn.jsdelivr.net/npm/@huggingface/[email protected]';
2
-
3
- // Reference the elements that we will need
4
- const status = document.getElementById('status');
5
- const fileUpload = document.getElementById('upload');
6
- const imageContainer = document.getElementById('container');
7
- const example = document.getElementById('example');
8
-
9
- const EXAMPLE_URL = 'https://huggingface.co/datasets/Xenova/transformers.js-docs/resolve/main/city-streets.jpg';
10
-
11
- // Create a new object detection pipeline
12
- status.textContent = 'Loading model...';
13
- const detector = await pipeline('object-detection', 'Xenova/detr-resnet-50');
14
- status.textContent = 'Ready';
15
-
16
- example.addEventListener('click', (e) => {
17
- e.preventDefault();
18
- detect(EXAMPLE_URL);
19
- });
20
-
21
- fileUpload.addEventListener('change', function (e) {
22
- const file = e.target.files[0];
23
- if (!file) {
24
- return;
25
- }
26
-
27
- const reader = new FileReader();
28
-
29
- // Set up a callback when the file is loaded
30
- reader.onload = e2 => detect(e2.target.result);
31
-
32
- reader.readAsDataURL(file);
33
- });
34
-
35
-
36
- // Detect objects in the image
37
- async function detect(img) {
38
- imageContainer.innerHTML = '';
39
- imageContainer.style.backgroundImage = `url(${img})`;
40
-
41
- status.textContent = 'Analysing...';
42
- const output = await detector(img, {
43
- threshold: 0.5,
44
- percentage: true,
45
- });
46
- status.textContent = '';
47
- output.forEach(renderBox);
48
- }
49
-
50
- // Render a bounding box and label on the image
51
- function renderBox({ box, label }) {
52
- const { xmax, xmin, ymax, ymin } = box;
53
-
54
- // Generate a random color for the box
55
- const color = '#' + Math.floor(Math.random() * 0xFFFFFF).toString(16).padStart(6, 0);
56
-
57
- // Draw the box
58
- const boxElement = document.createElement('div');
59
- boxElement.className = 'bounding-box';
60
- Object.assign(boxElement.style, {
61
- borderColor: color,
62
- left: 100 * xmin + '%',
63
- top: 100 * ymin + '%',
64
- width: 100 * (xmax - xmin) + '%',
65
- height: 100 * (ymax - ymin) + '%',
66
- })
67
-
68
- // Draw label
69
- const labelElement = document.createElement('span');
70
- labelElement.textContent = label;
71
- labelElement.className = 'bounding-box-label';
72
- labelElement.style.backgroundColor = color;
73
-
74
- boxElement.appendChild(labelElement);
75
- imageContainer.appendChild(boxElement);
76
- }