cutechicken commited on
Commit
258fcf8
ยท
verified ยท
1 Parent(s): 70c5630

Update game.js

Browse files
Files changed (1) hide show
  1. game.js +423 -278
game.js CHANGED
@@ -5,16 +5,16 @@ import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
5
  const GAME_CONSTANTS = {
6
  MISSION_DURATION: 180,
7
  MAP_SIZE: 15000,
8
- MAX_ALTITUDE: 10000,
9
- MIN_ALTITUDE: 50,
10
- MAX_SPEED: 600,
11
- STALL_SPEED: 100,
12
  GRAVITY: 9.8,
13
- MOUSE_SENSITIVITY: 0.003,
14
  MAX_G_FORCE: 12.0,
15
- ENEMY_COUNT: 3,
16
- MISSILE_COUNT: 4,
17
- AMMO_COUNT: 200
18
  };
19
 
20
  // ์ „ํˆฌ๊ธฐ ํด๋ž˜์Šค
@@ -25,28 +25,29 @@ class Fighter {
25
 
26
  // ๋ฌผ๋ฆฌ ์†์„ฑ
27
  this.position = new THREE.Vector3(0, 2000, 0);
28
- this.velocity = new THREE.Vector3(0, 0, 150); // ์ดˆ๊ธฐ ์†๋„
29
  this.acceleration = new THREE.Vector3(0, 0, 0);
30
  this.rotation = new THREE.Euler(0, 0, 0);
31
  this.angularVelocity = new THREE.Vector3(0, 0, 0);
32
 
33
  // ๋น„ํ–‰ ์ œ์–ด
34
- this.throttle = 0.5;
35
- this.speed = 150; // m/s
36
  this.altitude = 2000;
37
  this.gForce = 1.0;
38
  this.health = 100;
39
 
40
- // ์กฐ์ข… ์ž…๋ ฅ
41
  this.controlInput = {
42
- pitch: 0, // ๋งˆ์šฐ์Šค Y
43
- yaw: 0, // ๋Ÿฌ๋” (A/D)
44
- roll: 0, // ๋งˆ์šฐ์Šค X
45
- throttle: 0.5
46
  };
47
 
48
- // ๋ชฉํ‘œ ํšŒ์ „๊ฐ
49
  this.targetRotation = new THREE.Euler(0, 0, 0);
 
50
 
51
  // ๋ฌด๊ธฐ
52
  this.missiles = GAME_CONSTANTS.MISSILE_COUNT;
@@ -54,9 +55,11 @@ class Fighter {
54
  this.bullets = [];
55
  this.lastShootTime = 0;
56
 
57
- // ์นด๋ฉ”๋ผ
58
- this.cameraOffset = new THREE.Vector3(0, 5, -25);
59
- this.cameraLag = 0.1;
 
 
60
  }
61
 
62
  async initialize(scene, loader) {
@@ -64,7 +67,7 @@ class Fighter {
64
  const result = await loader.loadAsync('models/f-15.glb');
65
  this.mesh = result.scene;
66
  this.mesh.position.copy(this.position);
67
- this.mesh.scale.set(2, 2, 2);
68
 
69
  // ๊ทธ๋ฆผ์ž ์„ค์ •
70
  this.mesh.traverse((child) => {
@@ -85,109 +88,133 @@ class Fighter {
85
  }
86
 
87
  createFallbackModel(scene) {
88
- // ๋ชจ๋ธ ๋กœ๋”ฉ ์‹คํŒจ ์‹œ ๊ธฐ๋ณธ ๋ชจ์–‘ ์ƒ์„ฑ
89
  const group = new THREE.Group();
90
 
91
- // ๋™์ฒด
92
- const fuselageGeometry = new THREE.CylinderGeometry(0.5, 1, 8, 8);
93
- const fuselageMaterial = new THREE.MeshLambertMaterial({ color: 0x808080 });
94
  const fuselage = new THREE.Mesh(fuselageGeometry, fuselageMaterial);
95
  fuselage.rotation.x = Math.PI / 2;
96
  group.add(fuselage);
97
 
98
  // ์ฃผ์ต
99
- const wingGeometry = new THREE.BoxGeometry(12, 0.2, 3);
100
- const wingMaterial = new THREE.MeshLambertMaterial({ color: 0x606060 });
101
  const wings = new THREE.Mesh(wingGeometry, wingMaterial);
102
- wings.position.z = -1;
103
  group.add(wings);
104
 
105
  // ์ˆ˜์ง ์•ˆ์ •ํŒ
106
- const tailGeometry = new THREE.BoxGeometry(0.2, 3, 2);
107
- const tailMaterial = new THREE.MeshLambertMaterial({ color: 0x606060 });
108
  const tail = new THREE.Mesh(tailGeometry, tailMaterial);
109
- tail.position.z = -3;
110
- tail.position.y = 1;
111
  group.add(tail);
112
 
 
 
 
 
 
 
 
113
  this.mesh = group;
114
  this.mesh.position.copy(this.position);
115
- this.mesh.scale.set(2, 2, 2);
116
  scene.add(this.mesh);
117
  this.isLoaded = true;
118
 
119
  console.log('Fallback ์ „ํˆฌ๊ธฐ ๋ชจ๋ธ ์ƒ์„ฑ ์™„๋ฃŒ');
120
  }
121
 
 
122
  updateMouseInput(deltaX, deltaY) {
123
- // ๋งˆ์šฐ์Šค ์ž…๋ ฅ์„ ์กฐ์ข… ์ž…๋ ฅ์œผ๋กœ ๋ณ€ํ™˜
124
  this.controlInput.roll += deltaX * GAME_CONSTANTS.MOUSE_SENSITIVITY;
125
  this.controlInput.pitch += deltaY * GAME_CONSTANTS.MOUSE_SENSITIVITY;
126
 
127
- // ์ œํ•œ
128
  this.controlInput.roll = Math.max(-1, Math.min(1, this.controlInput.roll));
129
  this.controlInput.pitch = Math.max(-1, Math.min(1, this.controlInput.pitch));
 
 
 
130
  }
131
 
132
  updateControls(keys, deltaTime) {
133
- // ์Šค๋กœํ‹€ (W/S)
134
- if (keys.w) this.throttle = Math.min(1, this.throttle + deltaTime * 0.8);
135
- if (keys.s) this.throttle = Math.max(0, this.throttle - deltaTime * 0.8);
136
 
137
- // ๋Ÿฌ๋” (A/D)
138
  this.controlInput.yaw = 0;
139
  if (keys.a) this.controlInput.yaw = -1;
140
  if (keys.d) this.controlInput.yaw = 1;
141
 
142
- // ๋ชฉํ‘œ ํšŒ์ „๊ฐ ๊ณ„์‚ฐ (๋” ๋ฏผ๊ฐํ•œ ์กฐ์ข…)
143
- const maxPitchAngle = Math.PI / 3; // 60๋„
144
- const maxRollAngle = Math.PI / 2; // 90๋„
 
145
 
 
146
  this.targetRotation.x = this.controlInput.pitch * maxPitchAngle;
147
  this.targetRotation.z = this.controlInput.roll * maxRollAngle;
148
- this.targetRotation.y += this.controlInput.yaw * deltaTime * 2;
149
 
150
- // ์ž…๋ ฅ ๊ฐ์‡ 
151
- this.controlInput.roll *= 0.92;
152
- this.controlInput.pitch *= 0.92;
153
  }
154
 
155
  updatePhysics(deltaTime) {
156
  if (!this.mesh) return;
157
 
158
- // ํ˜„์žฌ ํšŒ์ „์„ ๋ชฉํ‘œ ํšŒ์ „์œผ๋กœ ๋ถ€๋“œ๋Ÿฝ๊ฒŒ ๋ณด๊ฐ„
159
- const rotationSpeed = deltaTime * 3.5; // ๋” ๋น ๋ฅธ ์‘๋‹ต
160
- this.rotation.x = THREE.MathUtils.lerp(this.rotation.x, this.targetRotation.x, rotationSpeed);
161
- this.rotation.y = THREE.MathUtils.lerp(this.rotation.y, this.targetRotation.y, rotationSpeed);
162
- this.rotation.z = THREE.MathUtils.lerp(this.rotation.z, this.targetRotation.z, rotationSpeed);
 
 
 
 
 
163
 
164
- // ์—”์ง„ ์ถ”๋ ฅ ๊ณ„์‚ฐ
165
- const maxThrust = 200000; // ๋‰ดํ„ด
166
- const mass = 30000; // kg
167
- const thrust = this.throttle * maxThrust;
168
 
169
  // ์ „์ง„ ๋ฐฉํ–ฅ ๋ฒกํ„ฐ
170
  const forward = new THREE.Vector3(0, 0, 1);
171
  forward.applyEuler(this.rotation);
172
 
173
- // ์–‘๋ ฅ ๊ณ„์‚ฐ (๋‹จ์ˆœํ™”)
174
- const lift = this.speed * this.speed * 0.001 * Math.cos(this.rotation.x);
175
- const liftVector = new THREE.Vector3(0, 1, 0);
176
- liftVector.applyEuler(this.rotation);
177
- liftVector.multiplyScalar(lift);
178
 
179
- // ์ถ”๋ ฅ ๊ฐ€์†๋„
180
- const thrustAccel = forward.multiplyScalar(thrust / mass);
 
 
 
 
 
 
181
 
182
  // ์ค‘๋ ฅ
183
  const gravity = new THREE.Vector3(0, -GAME_CONSTANTS.GRAVITY, 0);
184
 
185
- // ํ•ญ๋ ฅ
186
- const dragCoeff = 0.15;
187
- const drag = this.velocity.clone().multiplyScalar(-dragCoeff * this.velocity.length());
188
 
189
  // ์ด ๊ฐ€์†๋„
190
- this.acceleration = thrustAccel.add(gravity).add(drag).add(liftVector);
 
 
 
 
191
 
192
  // G-Force ๊ณ„์‚ฐ
193
  this.gForce = this.acceleration.length() / GAME_CONSTANTS.GRAVITY;
@@ -198,12 +225,11 @@ class Fighter {
198
  // ์œ„์น˜ ์—…๋ฐ์ดํŠธ
199
  this.position.add(this.velocity.clone().multiplyScalar(deltaTime));
200
 
201
- // ๊ณ ๋„ ์ œํ•œ
202
  if (this.position.y < GAME_CONSTANTS.MIN_ALTITUDE) {
203
  this.position.y = GAME_CONSTANTS.MIN_ALTITUDE;
204
  this.velocity.y = Math.max(0, this.velocity.y);
205
- // ์ง€๋ฉด ์ถฉ๋Œ ์‹œ ํ”ผํ•ด
206
- this.takeDamage(50);
207
  }
208
 
209
  if (this.position.y > GAME_CONSTANTS.MAX_ALTITUDE) {
@@ -211,49 +237,52 @@ class Fighter {
211
  this.velocity.y = Math.min(0, this.velocity.y);
212
  }
213
 
214
- // ๋งต ๊ฒฝ๊ณ„ ์ œํ•œ
215
  const mapLimit = GAME_CONSTANTS.MAP_SIZE / 2;
216
- this.position.x = Math.max(-mapLimit, Math.min(mapLimit, this.position.x));
217
- this.position.z = Math.max(-mapLimit, Math.min(mapLimit, this.position.z));
 
 
218
 
219
  // ๋ฉ”์‹œ ์—…๋ฐ์ดํŠธ
220
  this.mesh.position.copy(this.position);
221
  this.mesh.rotation.copy(this.rotation);
222
 
223
- // ์†๋„์™€ ๊ณ ๋„ ๊ณ„์‚ฐ
224
- this.speed = this.velocity.length();
225
  this.altitude = this.position.y;
226
 
227
- // ์‹ค์† ์ฒดํฌ
228
  if (this.speed < GAME_CONSTANTS.STALL_SPEED) {
229
- // ์‹ค์† ์‹œ ์ œ์–ด๋ ฅ ๊ฐ์†Œ
230
- this.acceleration.y -= 20;
 
231
  }
232
  }
233
 
234
  shoot(scene) {
235
  const currentTime = Date.now();
236
- if (currentTime - this.lastShootTime < 100 || this.ammo <= 0) return;
237
 
238
  this.lastShootTime = currentTime;
239
  this.ammo--;
240
 
241
- // ๊ธฐ๊ด€์ด ๋ฐœ์‚ฌ
242
- const bulletGeometry = new THREE.SphereGeometry(0.2);
243
  const bulletMaterial = new THREE.MeshBasicMaterial({
244
  color: 0xffff00,
245
  emissive: 0xffff00,
246
- emissiveIntensity: 0.5
247
  });
248
  const bullet = new THREE.Mesh(bulletGeometry, bulletMaterial);
249
 
250
- // ์ด๊ตฌ ์œ„์น˜ ๊ณ„์‚ฐ
251
- const muzzleOffset = new THREE.Vector3(0, 0, 8);
252
  muzzleOffset.applyEuler(this.rotation);
253
  bullet.position.copy(this.position).add(muzzleOffset);
254
 
255
- // ํƒ„์† ๊ณ„์‚ฐ
256
- const bulletSpeed = 1200; // m/s
257
  const direction = new THREE.Vector3(0, 0, 1);
258
  direction.applyEuler(this.rotation);
259
  bullet.velocity = direction.multiplyScalar(bulletSpeed).add(this.velocity);
@@ -264,10 +293,10 @@ class Fighter {
264
  scene.add(bullet);
265
  this.bullets.push(bullet);
266
 
267
- // ๋ฐœ์‚ฌ์Œ (์˜ˆ์‹œ)
268
  try {
269
  const audio = new Audio('sounds/gunfire.ogg');
270
- audio.volume = 0.3;
271
  audio.play();
272
  } catch (e) {
273
  console.log('Sound file not found');
@@ -275,20 +304,43 @@ class Fighter {
275
  }
276
 
277
  createMuzzleFlash(scene, position) {
278
- const flashGeometry = new THREE.SphereGeometry(2);
 
 
 
279
  const flashMaterial = new THREE.MeshBasicMaterial({
280
  color: 0xffa500,
281
  transparent: true,
282
- opacity: 0.8
283
  });
284
  const flash = new THREE.Mesh(flashGeometry, flashMaterial);
285
- flash.position.copy(position);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
286
 
287
- scene.add(flash);
 
 
288
 
289
  setTimeout(() => {
290
- scene.remove(flash);
291
- }, 100);
292
  }
293
 
294
  updateBullets(scene, deltaTime) {
@@ -297,9 +349,9 @@ class Fighter {
297
  bullet.position.add(bullet.velocity.clone().multiplyScalar(deltaTime));
298
 
299
  // ์ด์•Œ ์ œ๊ฑฐ ์กฐ๊ฑด
300
- if (bullet.position.distanceTo(this.position) > 8000 ||
301
  bullet.position.y < 0 ||
302
- bullet.position.y > GAME_CONSTANTS.MAX_ALTITUDE) {
303
  scene.remove(bullet);
304
  this.bullets.splice(i, 1);
305
  }
@@ -311,41 +363,49 @@ class Fighter {
311
  return this.health <= 0;
312
  }
313
 
 
314
  getCameraPosition() {
315
- // ์ „ํˆฌ๊ธฐ ๋’ค์ชฝ ์นด๋ฉ”๋ผ ์œ„์น˜
316
- const offset = this.cameraOffset.clone();
317
- offset.applyEuler(this.rotation);
 
 
 
 
 
 
 
318
  return this.position.clone().add(offset);
319
  }
320
 
321
  getCameraTarget() {
322
- // ์นด๋ฉ”๋ผ๊ฐ€ ๋ฐ”๋ผ๋ณผ ์œ„์น˜
323
- const target = new THREE.Vector3(0, 0, 10);
324
- target.applyEuler(this.rotation);
325
- return this.position.clone().add(target);
326
  }
327
  }
328
 
329
- // ์  ์ „ํˆฌ๊ธฐ ํด๋ž˜์Šค
330
  class EnemyFighter {
331
  constructor(scene, position) {
332
  this.mesh = null;
333
  this.isLoaded = false;
334
  this.scene = scene;
335
  this.position = position.clone();
336
- this.velocity = new THREE.Vector3(0, 0, 120);
337
  this.rotation = new THREE.Euler(0, 0, 0);
338
- this.health = 100;
339
- this.speed = 150;
340
  this.bullets = [];
341
  this.lastShootTime = 0;
342
 
343
- // AI ์ƒํƒœ
344
  this.aiState = 'patrol';
345
  this.targetPosition = position.clone();
346
  this.patrolCenter = position.clone();
347
- this.patrolRadius = 2000;
348
  this.lastStateChange = 0;
 
 
349
  }
350
 
351
  async initialize(loader) {
@@ -353,7 +413,7 @@ class EnemyFighter {
353
  const result = await loader.loadAsync('models/mig-29.glb');
354
  this.mesh = result.scene;
355
  this.mesh.position.copy(this.position);
356
- this.mesh.scale.set(2, 2, 2);
357
 
358
  this.mesh.traverse((child) => {
359
  if (child.isMesh) {
@@ -373,24 +433,23 @@ class EnemyFighter {
373
  }
374
 
375
  createFallbackModel() {
376
- // ๋ชจ๋ธ ๋กœ๋”ฉ ์‹คํŒจ ์‹œ ๊ธฐ๋ณธ ๋ชจ์–‘ ์ƒ์„ฑ
377
  const group = new THREE.Group();
378
 
379
- const fuselageGeometry = new THREE.CylinderGeometry(0.4, 0.8, 6, 8);
380
- const fuselageMaterial = new THREE.MeshLambertMaterial({ color: 0x800000 });
381
  const fuselage = new THREE.Mesh(fuselageGeometry, fuselageMaterial);
382
  fuselage.rotation.x = Math.PI / 2;
383
  group.add(fuselage);
384
 
385
- const wingGeometry = new THREE.BoxGeometry(8, 0.2, 2);
386
- const wingMaterial = new THREE.MeshLambertMaterial({ color: 0x600000 });
387
  const wings = new THREE.Mesh(wingGeometry, wingMaterial);
388
- wings.position.z = -0.5;
389
  group.add(wings);
390
 
391
  this.mesh = group;
392
  this.mesh.position.copy(this.position);
393
- this.mesh.scale.set(2, 2, 2);
394
  this.scene.add(this.mesh);
395
  this.isLoaded = true;
396
 
@@ -403,57 +462,91 @@ class EnemyFighter {
403
  const currentTime = Date.now();
404
  const distanceToPlayer = this.position.distanceTo(playerPosition);
405
 
406
- // AI ์ƒํƒœ ์—…๋ฐ์ดํŠธ
407
- if (currentTime - this.lastStateChange > 5000) {
408
- if (distanceToPlayer < 3000) {
409
  this.aiState = 'attack';
 
 
410
  } else {
411
  this.aiState = 'patrol';
412
  }
413
  this.lastStateChange = currentTime;
414
  }
415
 
416
- // AI ํ–‰๋™
417
- if (this.aiState === 'attack' && distanceToPlayer < 4000) {
418
- // ํ”Œ๋ ˆ์ด์–ด ์ถ”์ 
419
- const direction = new THREE.Vector3()
420
- .subVectors(playerPosition, this.position)
421
- .normalize();
422
-
423
- this.velocity = direction.multiplyScalar(this.speed);
424
-
425
- // ํšŒ์ „ ๊ณ„์‚ฐ
426
- this.rotation.y = Math.atan2(direction.x, direction.z);
427
- this.rotation.x = Math.atan2(-direction.y,
428
- Math.sqrt(direction.x * direction.x + direction.z * direction.z));
429
-
430
- // ๊ณต๊ฒฉ
431
- if (distanceToPlayer < 2000 && currentTime - this.lastShootTime > 1500) {
432
- this.shoot();
433
- }
434
- } else {
435
- // ์ˆœ์ฐฐ ๋ชจ๋“œ
436
- if (this.position.distanceTo(this.targetPosition) < 200) {
437
- // ์ƒˆ๋กœ์šด ์ˆœ์ฐฐ ์ง€์  ์„ค์ •
438
- const angle = Math.random() * Math.PI * 2;
439
- this.targetPosition = this.patrolCenter.clone().add(
440
- new THREE.Vector3(
441
- Math.cos(angle) * this.patrolRadius,
442
- (Math.random() - 0.5) * 1000,
443
- Math.sin(angle) * this.patrolRadius
444
- )
445
- );
446
- }
447
-
448
- const direction = new THREE.Vector3()
449
- .subVectors(this.targetPosition, this.position)
450
- .normalize();
451
-
452
- this.velocity = direction.multiplyScalar(this.speed * 0.7);
453
- this.rotation.y = Math.atan2(direction.x, direction.z);
454
  }
455
 
456
  // ๋ฌผ๋ฆฌ ์—…๋ฐ์ดํŠธ
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
457
  this.position.add(this.velocity.clone().multiplyScalar(deltaTime));
458
 
459
  // ๊ณ ๋„ ์ œํ•œ
@@ -466,41 +559,36 @@ class EnemyFighter {
466
  this.velocity.y = -Math.abs(this.velocity.y);
467
  }
468
 
469
- // ๋งต ๊ฒฝ๊ณ„ ์ œํ•œ
470
  const mapLimit = GAME_CONSTANTS.MAP_SIZE / 2;
471
- if (Math.abs(this.position.x) > mapLimit || Math.abs(this.position.z) > mapLimit) {
472
- // ๋งต ์ค‘์•™์„ ํ–ฅํ•˜๋„๋ก
473
- const centerDirection = new THREE.Vector3(0, this.position.y, 0)
474
- .sub(this.position).normalize();
475
- this.velocity = centerDirection.multiplyScalar(this.speed);
476
- }
477
 
478
  // ๋ฉ”์‹œ ์—…๋ฐ์ดํŠธ
479
  this.mesh.position.copy(this.position);
480
  this.mesh.rotation.copy(this.rotation);
481
-
482
- // ์ด์•Œ ์—…๋ฐ์ดํŠธ
483
- this.updateBullets(deltaTime);
484
  }
485
 
486
  shoot() {
487
  this.lastShootTime = Date.now();
488
 
489
- const bulletGeometry = new THREE.SphereGeometry(0.15);
490
  const bulletMaterial = new THREE.MeshBasicMaterial({
491
  color: 0xff0000,
492
  emissive: 0xff0000,
493
- emissiveIntensity: 0.5
494
  });
495
  const bullet = new THREE.Mesh(bulletGeometry, bulletMaterial);
496
 
497
- const muzzleOffset = new THREE.Vector3(0, 0, 6);
498
  muzzleOffset.applyEuler(this.rotation);
499
  bullet.position.copy(this.position).add(muzzleOffset);
500
 
501
  const direction = new THREE.Vector3(0, 0, 1);
502
  direction.applyEuler(this.rotation);
503
- bullet.velocity = direction.multiplyScalar(1000);
504
 
505
  this.scene.add(bullet);
506
  this.bullets.push(bullet);
@@ -511,7 +599,7 @@ class EnemyFighter {
511
  const bullet = this.bullets[i];
512
  bullet.position.add(bullet.velocity.clone().multiplyScalar(deltaTime));
513
 
514
- if (bullet.position.distanceTo(this.position) > 5000 ||
515
  bullet.position.y < 0) {
516
  this.scene.remove(bullet);
517
  this.bullets.splice(i, 1);
@@ -534,16 +622,17 @@ class EnemyFighter {
534
  }
535
  }
536
 
537
- // ๊ฒŒ์ž„ ํด๋ž˜์Šค
538
  class Game {
539
  constructor() {
540
  this.scene = new THREE.Scene();
541
- this.camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 50000);
542
  this.renderer = new THREE.WebGLRenderer({ antialias: true });
543
  this.renderer.setSize(window.innerWidth, window.innerHeight);
544
  this.renderer.shadowMap.enabled = true;
545
  this.renderer.shadowMap.type = THREE.PCFSoftShadowMap;
546
  this.renderer.setClearColor(0x87CEEB);
 
547
 
548
  document.getElementById('gameContainer').appendChild(this.renderer.domElement);
549
 
@@ -556,122 +645,131 @@ class Game {
556
  this.score = 0;
557
  this.lastTime = performance.now();
558
  this.gameTimer = null;
 
 
 
 
 
559
 
560
  // ์ž…๋ ฅ ์ƒํƒœ
561
  this.keys = { w: false, a: false, s: false, d: false };
562
  this.isStarted = false;
563
 
564
- this.setupEventListeners();
565
  this.setupScene();
 
 
566
  }
567
 
568
- async initialize() {
569
  try {
570
- document.getElementById('loading').style.display = 'block';
571
 
572
- // ์ „ํˆฌ๊ธฐ ์ดˆ๊ธฐํ™”
573
  await this.fighter.initialize(this.scene, this.loader);
574
 
575
  if (!this.fighter.isLoaded) {
576
  throw new Error('์ „ํˆฌ๊ธฐ ๋กœ๋”ฉ ์‹คํŒจ');
577
  }
578
 
579
- // ์  ์ƒ์„ฑ
580
- await this.spawnEnemies();
581
 
582
  this.isLoaded = true;
 
 
 
583
  document.getElementById('loading').style.display = 'none';
584
 
585
- // ๊ฒŒ์ž„ ์‹œ์ž‘
586
- this.startGameTimer();
587
  this.animate();
588
 
589
  } catch (error) {
590
- console.error('๊ฒŒ์ž„ ์ดˆ๊ธฐํ™” ์‹คํŒจ:', error);
591
  document.getElementById('loading').innerHTML =
592
- '<div class="loading-text" style="color: red;">๋กœ๋”ฉ ์‹คํŒจ</div>';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
593
  }
594
  }
595
 
596
  setupScene() {
597
- // ํ•˜๋Š˜ ๋ฐฐ๊ฒฝ
598
  this.scene.background = new THREE.Color(0x87CEEB);
599
- this.scene.fog = new THREE.Fog(0x87CEEB, 1000, 20000);
600
 
601
- // ์กฐ๋ช…
602
- const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
603
  this.scene.add(ambientLight);
604
 
605
- const directionalLight = new THREE.DirectionalLight(0xffffff, 1.0);
606
- directionalLight.position.set(5000, 8000, 5000);
607
  directionalLight.castShadow = true;
608
- directionalLight.shadow.mapSize.width = 2048;
609
- directionalLight.shadow.mapSize.height = 2048;
610
  directionalLight.shadow.camera.near = 0.5;
611
- directionalLight.shadow.camera.far = 15000;
612
- directionalLight.shadow.camera.left = -8000;
613
- directionalLight.shadow.camera.right = 8000;
614
- directionalLight.shadow.camera.top = 8000;
615
- directionalLight.shadow.camera.bottom = -8000;
616
  this.scene.add(directionalLight);
617
 
618
- // ์ง€ํ˜•
619
- const groundGeometry = new THREE.PlaneGeometry(GAME_CONSTANTS.MAP_SIZE, GAME_CONSTANTS.MAP_SIZE);
620
  const groundMaterial = new THREE.MeshLambertMaterial({
621
  color: 0x8FBC8F,
622
  transparent: true,
623
- opacity: 0.8
624
  });
625
  const ground = new THREE.Mesh(groundGeometry, groundMaterial);
626
  ground.rotation.x = -Math.PI / 2;
627
  ground.receiveShadow = true;
628
  this.scene.add(ground);
629
 
630
- // ๊ตฌ๋ฆ„ ์ถ”๊ฐ€
631
  this.addClouds();
632
  }
633
 
634
  addClouds() {
635
- const cloudGeometry = new THREE.SphereGeometry(150, 8, 6);
636
  const cloudMaterial = new THREE.MeshLambertMaterial({
637
  color: 0xffffff,
638
  transparent: true,
639
  opacity: 0.6
640
  });
641
 
642
- for (let i = 0; i < 100; i++) {
643
  const cloud = new THREE.Mesh(cloudGeometry, cloudMaterial);
644
  cloud.position.set(
645
- (Math.random() - 0.5) * GAME_CONSTANTS.MAP_SIZE,
646
- Math.random() * 4000 + 1000,
647
- (Math.random() - 0.5) * GAME_CONSTANTS.MAP_SIZE
648
  );
649
  cloud.scale.set(
650
- Math.random() * 3 + 1,
651
- Math.random() * 2 + 0.5,
652
- Math.random() * 3 + 1
653
  );
654
  this.scene.add(cloud);
655
  }
656
  }
657
 
658
- async spawnEnemies() {
659
- for (let i = 0; i < GAME_CONSTANTS.ENEMY_COUNT; i++) {
660
- const angle = (i / GAME_CONSTANTS.ENEMY_COUNT) * Math.PI * 2;
661
- const distance = 5000 + Math.random() * 3000;
662
-
663
- const position = new THREE.Vector3(
664
- Math.cos(angle) * distance,
665
- 2000 + Math.random() * 2000,
666
- Math.sin(angle) * distance
667
- );
668
-
669
- const enemy = new EnemyFighter(this.scene, position);
670
- await enemy.initialize(this.loader);
671
- this.enemies.push(enemy);
672
- }
673
- }
674
-
675
  setupEventListeners() {
676
  // ํ‚ค๋ณด๋“œ ์ด๋ฒคํŠธ
677
  document.addEventListener('keydown', (event) => {
@@ -694,7 +792,7 @@ class Game {
694
  }
695
  });
696
 
697
- // ๋งˆ์šฐ์Šค ์ด๋ฒคํŠธ
698
  document.addEventListener('mousemove', (event) => {
699
  if (!document.pointerLockElement || !this.isStarted || this.isGameOver) return;
700
 
@@ -722,6 +820,38 @@ class Game {
722
  });
723
  }
724
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
725
  startGameTimer() {
726
  this.gameTimer = setInterval(() => {
727
  if (!this.isGameOver) {
@@ -745,21 +875,25 @@ class Game {
745
  document.getElementById('health').style.width = `${this.fighter.health}%`;
746
  document.getElementById('ammoDisplay').textContent = `AMMO: ${this.fighter.ammo}`;
747
 
748
- // ์ƒˆ๋กœ์šด ์ •๋ณด ์ถ”๊ฐ€
749
  const gameStats = document.getElementById('gameStats');
750
- gameStats.innerHTML = `
751
- <div>Score: ${this.score}</div>
752
- <div>Time: ${this.gameTime}s</div>
753
- <div>Speed: ${speedKnots} KT</div>
754
- <div>Alt: ${altitudeFeet} FT</div>
755
- <div>G-Force: ${this.fighter.gForce.toFixed(1)}</div>
756
- <div>Targets: ${this.enemies.length}</div>
757
- `;
 
 
 
758
  }
759
  }
760
 
761
  updateRadar() {
762
  const radar = document.getElementById('radar');
 
763
 
764
  // ๊ธฐ์กด ์  ๋„ํŠธ ์ œ๊ฑฐ
765
  const oldDots = radar.getElementsByClassName('enemy-dot');
@@ -768,8 +902,8 @@ class Game {
768
  }
769
 
770
  // ๋ ˆ์ด๋” ์ค‘์‹ฌ
771
- const radarCenter = { x: 100, y: 100 }; // 200px / 2
772
- const radarRange = 8000; // ๊ฐ์ง€ ๋ฒ”์œ„
773
 
774
  this.enemies.forEach(enemy => {
775
  if (!enemy.mesh || !enemy.isLoaded) return;
@@ -802,15 +936,15 @@ class Game {
802
  if (!enemy.mesh || !enemy.isLoaded) continue;
803
 
804
  const distance = bullet.position.distanceTo(enemy.position);
805
- if (distance < 20) {
806
  // ๋ช…์ค‘!
807
  this.scene.remove(bullet);
808
  this.fighter.bullets.splice(i, 1);
809
 
810
- if (enemy.takeDamage(25)) {
811
  enemy.destroy();
812
  this.enemies.splice(j, 1);
813
- this.score += 100;
814
  }
815
  break;
816
  }
@@ -821,12 +955,12 @@ class Game {
821
  this.enemies.forEach(enemy => {
822
  enemy.bullets.forEach((bullet, index) => {
823
  const distance = bullet.position.distanceTo(this.fighter.position);
824
- if (distance < 25) {
825
  // ํ”ผ๊ฒฉ!
826
  this.scene.remove(bullet);
827
  enemy.bullets.splice(index, 1);
828
 
829
- if (this.fighter.takeDamage(20)) {
830
  this.endGame(false);
831
  }
832
  }
@@ -837,40 +971,42 @@ class Game {
837
  animate() {
838
  if (this.isGameOver) return;
839
 
840
- requestAnimationFrame(() => this.animate());
841
 
842
  const currentTime = performance.now();
843
  const deltaTime = Math.min((currentTime - this.lastTime) / 1000, 0.1);
844
  this.lastTime = currentTime;
845
 
846
- if (this.isLoaded && this.fighter.isLoaded && this.isStarted) {
847
- // ์ „ํˆฌ๊ธฐ ์—…๋ฐ์ดํŠธ
848
- this.fighter.updateControls(this.keys, deltaTime);
849
- this.fighter.updatePhysics(deltaTime);
850
- this.fighter.updateBullets(this.scene, deltaTime);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
851
 
852
- // ์นด๋ฉ”๋ผ ์—…๋ฐ์ดํŠธ
853
  const cameraPos = this.fighter.getCameraPosition();
854
  const cameraTarget = this.fighter.getCameraTarget();
855
- this.camera.position.lerp(cameraPos, 0.1);
856
  this.camera.lookAt(cameraTarget);
857
-
858
- // ์  ์—…๋ฐ์ดํŠธ
859
- this.enemies.forEach(enemy => {
860
- enemy.update(this.fighter.position, deltaTime);
861
- });
862
-
863
- // ์ถฉ๋Œ ๊ฒ€์‚ฌ
864
- this.checkCollisions();
865
-
866
- // UI ์—…๋ฐ์ดํŠธ
867
- this.updateUI();
868
- this.updateRadar();
869
-
870
- // ์Šน๋ฆฌ ์กฐ๊ฑด ํ™•์ธ
871
- if (this.enemies.length === 0) {
872
- this.endGame(true);
873
- }
874
  }
875
 
876
  this.renderer.render(this.scene, this.camera);
@@ -879,6 +1015,13 @@ class Game {
879
  endGame(victory = false) {
880
  this.isGameOver = true;
881
 
 
 
 
 
 
 
 
882
  if (this.gameTimer) {
883
  clearInterval(this.gameTimer);
884
  }
@@ -892,9 +1035,11 @@ class Game {
892
  ${victory ? 'MISSION ACCOMPLISHED!' : 'SHOT DOWN!'}
893
  </h1>
894
  <div style="color: #0f0; font-size: 24px; margin: 20px 0;">
895
- Score: ${this.score}<br>
896
  Enemies Destroyed: ${GAME_CONSTANTS.ENEMY_COUNT - this.enemies.length}<br>
897
- Mission Time: ${GAME_CONSTANTS.MISSION_DURATION - this.gameTime}s
 
 
898
  </div>
899
  <button class="start-button" onclick="location.reload()">
900
  New Mission
@@ -908,20 +1053,20 @@ class Game {
908
  // ๊ฒŒ์ž„ ์ธ์Šคํ„ด์Šค
909
  let gameInstance = null;
910
 
911
- // ์ „์—ญ ํ•จ์ˆ˜
912
  window.startGame = function() {
913
  document.getElementById('startScreen').style.display = 'none';
914
  document.body.requestPointerLock();
915
 
916
- if (!gameInstance) {
917
- gameInstance = new Game();
 
 
918
  }
919
-
920
- gameInstance.isStarted = true;
921
- gameInstance.initialize();
922
  };
923
 
924
- // ๊ฒŒ์ž„ ์ดˆ๊ธฐํ™”
925
  document.addEventListener('DOMContentLoaded', () => {
926
- console.log('์ „ํˆฌ๊ธฐ ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ ์ค€๋น„ ์™„๋ฃŒ');
 
927
  });
 
5
  const GAME_CONSTANTS = {
6
  MISSION_DURATION: 180,
7
  MAP_SIZE: 15000,
8
+ MAX_ALTITUDE: 12000,
9
+ MIN_ALTITUDE: 100,
10
+ MAX_SPEED: 800,
11
+ STALL_SPEED: 120,
12
  GRAVITY: 9.8,
13
+ MOUSE_SENSITIVITY: 0.002,
14
  MAX_G_FORCE: 12.0,
15
+ ENEMY_COUNT: 4,
16
+ MISSILE_COUNT: 6,
17
+ AMMO_COUNT: 300
18
  };
19
 
20
  // ์ „ํˆฌ๊ธฐ ํด๋ž˜์Šค
 
25
 
26
  // ๋ฌผ๋ฆฌ ์†์„ฑ
27
  this.position = new THREE.Vector3(0, 2000, 0);
28
+ this.velocity = new THREE.Vector3(0, 0, 200); // ์ดˆ๊ธฐ ์†๋„ ์ฆ๊ฐ€
29
  this.acceleration = new THREE.Vector3(0, 0, 0);
30
  this.rotation = new THREE.Euler(0, 0, 0);
31
  this.angularVelocity = new THREE.Vector3(0, 0, 0);
32
 
33
  // ๋น„ํ–‰ ์ œ์–ด
34
+ this.throttle = 0.6; // ์ดˆ๊ธฐ ์Šค๋กœํ‹€ ์ฆ๊ฐ€
35
+ this.speed = 200;
36
  this.altitude = 2000;
37
  this.gForce = 1.0;
38
  this.health = 100;
39
 
40
+ // ์กฐ์ข… ์ž…๋ ฅ (์›Œ์ฌ๋” ์Šคํƒ€์ผ)
41
  this.controlInput = {
42
+ pitch: 0, // ๋งˆ์šฐ์Šค Y (์ƒํ•˜)
43
+ yaw: 0, // ๋Ÿฌ๋” (A/D)
44
+ roll: 0, // ๋งˆ์šฐ์Šค X (์ขŒ์šฐ ๊ธฐ์šธ๊ธฐ)
45
+ elevatorTrim: 0
46
  };
47
 
48
+ // ๋ถ€๋“œ๋Ÿฌ์šด ํšŒ์ „์„ ์œ„ํ•œ ๋ชฉํ‘œ๊ฐ’
49
  this.targetRotation = new THREE.Euler(0, 0, 0);
50
+ this.rotationSpeed = 2.5; // ํšŒ์ „ ๋ฐ˜์‘์„ฑ
51
 
52
  // ๋ฌด๊ธฐ
53
  this.missiles = GAME_CONSTANTS.MISSILE_COUNT;
 
55
  this.bullets = [];
56
  this.lastShootTime = 0;
57
 
58
+ // ์นด๋ฉ”๋ผ (ํƒฑํฌ์™€ ์œ ์‚ฌํ•œ ์‹œ์Šคํ…œ)
59
+ this.cameraDistance = 35;
60
+ this.cameraHeight = 8;
61
+ this.cameraAngle = 0; // ๋งˆ์šฐ์Šค๋กœ ์ œ์–ด๋˜๋Š” ์นด๋ฉ”๋ผ ๊ฐ๋„
62
+ this.cameraPitch = 0.1; // ์•ฝ๊ฐ„ ์œ„์—์„œ ๋‚ด๋ ค๋‹ค๋ณด๋Š” ๊ฐ๋„
63
  }
64
 
65
  async initialize(scene, loader) {
 
67
  const result = await loader.loadAsync('models/f-15.glb');
68
  this.mesh = result.scene;
69
  this.mesh.position.copy(this.position);
70
+ this.mesh.scale.set(3, 3, 3); // ํฌ๊ธฐ ์ฆ๊ฐ€
71
 
72
  // ๊ทธ๋ฆผ์ž ์„ค์ •
73
  this.mesh.traverse((child) => {
 
88
  }
89
 
90
  createFallbackModel(scene) {
 
91
  const group = new THREE.Group();
92
 
93
+ // ๋™์ฒด (๋” ํฐ ํฌ๊ธฐ)
94
+ const fuselageGeometry = new THREE.CylinderGeometry(1, 2, 16, 8);
95
+ const fuselageMaterial = new THREE.MeshLambertMaterial({ color: 0x606060 });
96
  const fuselage = new THREE.Mesh(fuselageGeometry, fuselageMaterial);
97
  fuselage.rotation.x = Math.PI / 2;
98
  group.add(fuselage);
99
 
100
  // ์ฃผ์ต
101
+ const wingGeometry = new THREE.BoxGeometry(20, 0.5, 6);
102
+ const wingMaterial = new THREE.MeshLambertMaterial({ color: 0x404040 });
103
  const wings = new THREE.Mesh(wingGeometry, wingMaterial);
104
+ wings.position.z = -2;
105
  group.add(wings);
106
 
107
  // ์ˆ˜์ง ์•ˆ์ •ํŒ
108
+ const tailGeometry = new THREE.BoxGeometry(0.5, 6, 4);
109
+ const tailMaterial = new THREE.MeshLambertMaterial({ color: 0x404040 });
110
  const tail = new THREE.Mesh(tailGeometry, tailMaterial);
111
+ tail.position.z = -6;
112
+ tail.position.y = 2;
113
  group.add(tail);
114
 
115
+ // ์ˆ˜ํ‰ ์•ˆ์ •ํŒ
116
+ const horizontalTailGeometry = new THREE.BoxGeometry(8, 0.3, 3);
117
+ const horizontalTail = new THREE.Mesh(horizontalTailGeometry, tailMaterial);
118
+ horizontalTail.position.z = -6;
119
+ horizontalTail.position.y = 1;
120
+ group.add(horizontalTail);
121
+
122
  this.mesh = group;
123
  this.mesh.position.copy(this.position);
124
+ this.mesh.scale.set(3, 3, 3);
125
  scene.add(this.mesh);
126
  this.isLoaded = true;
127
 
128
  console.log('Fallback ์ „ํˆฌ๊ธฐ ๋ชจ๋ธ ์ƒ์„ฑ ์™„๋ฃŒ');
129
  }
130
 
131
+ // ์›Œ์ฌ๋” ์Šคํƒ€์ผ ๋งˆ์šฐ์Šค ์ž…๋ ฅ
132
  updateMouseInput(deltaX, deltaY) {
133
+ // ๋งˆ์šฐ์Šค X๋Š” ์—์ผ๋Ÿฌ๋ก  (๋กค), Y๋Š” ์—˜๋ฆฌ๋ฒ ์ดํ„ฐ (ํ”ผ์น˜)
134
  this.controlInput.roll += deltaX * GAME_CONSTANTS.MOUSE_SENSITIVITY;
135
  this.controlInput.pitch += deltaY * GAME_CONSTANTS.MOUSE_SENSITIVITY;
136
 
137
+ // ์ž…๋ ฅ ์ œํ•œ
138
  this.controlInput.roll = Math.max(-1, Math.min(1, this.controlInput.roll));
139
  this.controlInput.pitch = Math.max(-1, Math.min(1, this.controlInput.pitch));
140
+
141
+ // ์นด๋ฉ”๋ผ ๊ฐ๋„๋„ ๋งˆ์šฐ์Šค๋กœ ์ œ์–ด (ํƒฑํฌ ๊ฒŒ์ž„ ๋ฐฉ์‹)
142
+ this.cameraAngle += deltaX * GAME_CONSTANTS.MOUSE_SENSITIVITY * 0.5;
143
  }
144
 
145
  updateControls(keys, deltaTime) {
146
+ // ์Šค๋กœํ‹€ ์ œ์–ด (W/S)
147
+ if (keys.w) this.throttle = Math.min(1, this.throttle + deltaTime * 1.2);
148
+ if (keys.s) this.throttle = Math.max(0, this.throttle - deltaTime * 1.2);
149
 
150
+ // ๋Ÿฌ๋” ์ œ์–ด (A/D)
151
  this.controlInput.yaw = 0;
152
  if (keys.a) this.controlInput.yaw = -1;
153
  if (keys.d) this.controlInput.yaw = 1;
154
 
155
+ // ๋ชฉํ‘œ ํšŒ์ „๊ฐ ๊ณ„์‚ฐ (๋” ํ˜„์‹ค์ ์ธ ๋ฒ”์œ„)
156
+ const maxPitchAngle = Math.PI / 4; // 45๋„
157
+ const maxRollAngle = Math.PI / 3; // 60๋„
158
+ const maxYawRate = deltaTime * 1.5;
159
 
160
+ // ๋ชฉํ‘œ ํšŒ์ „ ์„ค์ •
161
  this.targetRotation.x = this.controlInput.pitch * maxPitchAngle;
162
  this.targetRotation.z = this.controlInput.roll * maxRollAngle;
163
+ this.targetRotation.y += this.controlInput.yaw * maxYawRate;
164
 
165
+ // ์ž…๋ ฅ ๊ฐ์‡  (์›Œ์ฌ๋”์™€ ์œ ์‚ฌ)
166
+ this.controlInput.roll *= 0.88;
167
+ this.controlInput.pitch *= 0.88;
168
  }
169
 
170
  updatePhysics(deltaTime) {
171
  if (!this.mesh) return;
172
 
173
+ // ๋ถ€๋“œ๋Ÿฌ์šด ํšŒ์ „ ๋ณด๊ฐ„ (๋” ํ˜„์‹ค์ )
174
+ const rotationLerpFactor = deltaTime * this.rotationSpeed;
175
+ this.rotation.x = THREE.MathUtils.lerp(this.rotation.x, this.targetRotation.x, rotationLerpFactor);
176
+ this.rotation.y = THREE.MathUtils.lerp(this.rotation.y, this.targetRotation.y, rotationLerpFactor);
177
+ this.rotation.z = THREE.MathUtils.lerp(this.rotation.z, this.targetRotation.z, rotationLerpFactor);
178
+
179
+ // ์—”์ง„ ์ถ”๋ ฅ ๊ณ„์‚ฐ (F-15 ๊ธฐ์ค€)
180
+ const maxThrust = 250000; // ๋‰ดํ„ด
181
+ const mass = 28000; // kg
182
+ const currentThrust = this.throttle * maxThrust;
183
 
184
+ // ๊ณต๊ธฐ์—ญํ•™์  ํž˜ ๊ณ„์‚ฐ
185
+ const airDensity = Math.max(0.3, 1.225 * Math.exp(-this.position.y / 8000)); // ๊ณ ๋„์— ๋”ฐ๋ฅธ ๊ณต๊ธฐ๋ฐ€๋„
186
+ const velocity = this.velocity.length();
 
187
 
188
  // ์ „์ง„ ๋ฐฉํ–ฅ ๋ฒกํ„ฐ
189
  const forward = new THREE.Vector3(0, 0, 1);
190
  forward.applyEuler(this.rotation);
191
 
192
+ // ์ƒ์Šน ๋ฐฉํ–ฅ ๋ฒกํ„ฐ
193
+ const up = new THREE.Vector3(0, 1, 0);
194
+ up.applyEuler(this.rotation);
 
 
195
 
196
+ // ์–‘๋ ฅ ๊ณ„์‚ฐ (๋” ํ˜„์‹ค์ )
197
+ const angleOfAttack = Math.asin(Math.max(-1, Math.min(1, this.velocity.clone().normalize().dot(up))));
198
+ const liftCoeff = Math.sin(2 * angleOfAttack) * 1.5;
199
+ const lift = 0.5 * airDensity * velocity * velocity * liftCoeff * 50; // ๋‚ ๊ฐœ๋ฉด์  ๊ณ ๋ ค
200
+ const liftVector = up.multiplyScalar(lift / mass);
201
+
202
+ // ์ถ”๋ ฅ ๋ฐฉํ–ฅ
203
+ const thrustVector = forward.multiplyScalar(currentThrust / mass);
204
 
205
  // ์ค‘๋ ฅ
206
  const gravity = new THREE.Vector3(0, -GAME_CONSTANTS.GRAVITY, 0);
207
 
208
+ // ํ•ญ๋ ฅ (์†๋„์˜ ์ œ๊ณฑ์— ๋น„๋ก€)
209
+ const dragCoeff = 0.02 + Math.abs(angleOfAttack) * 0.1; // ๋ฐ›์Œ๊ฐ์— ๋”ฐ๋ฅธ ํ•ญ๋ ฅ ์ฆ๊ฐ€
210
+ const drag = this.velocity.clone().multiplyScalar(-dragCoeff * velocity * airDensity / mass);
211
 
212
  // ์ด ๊ฐ€์†๋„
213
+ this.acceleration = new THREE.Vector3()
214
+ .add(thrustVector)
215
+ .add(liftVector)
216
+ .add(gravity)
217
+ .add(drag);
218
 
219
  // G-Force ๊ณ„์‚ฐ
220
  this.gForce = this.acceleration.length() / GAME_CONSTANTS.GRAVITY;
 
225
  // ์œ„์น˜ ์—…๋ฐ์ดํŠธ
226
  this.position.add(this.velocity.clone().multiplyScalar(deltaTime));
227
 
228
+ // ๊ณ ๋„ ์ œํ•œ ๋ฐ ์ง€๋ฉด ์ถฉ๋Œ
229
  if (this.position.y < GAME_CONSTANTS.MIN_ALTITUDE) {
230
  this.position.y = GAME_CONSTANTS.MIN_ALTITUDE;
231
  this.velocity.y = Math.max(0, this.velocity.y);
232
+ this.takeDamage(30); // ์ง€๋ฉด ๊ทผ์ ‘ ํ”ผํ•ด
 
233
  }
234
 
235
  if (this.position.y > GAME_CONSTANTS.MAX_ALTITUDE) {
 
237
  this.velocity.y = Math.min(0, this.velocity.y);
238
  }
239
 
240
+ // ๋งต ๊ฒฝ๊ณ„ ์ˆœํ™˜ (๋ฌดํ•œ ๋งต ํšจ๊ณผ)
241
  const mapLimit = GAME_CONSTANTS.MAP_SIZE / 2;
242
+ if (this.position.x > mapLimit) this.position.x = -mapLimit;
243
+ if (this.position.x < -mapLimit) this.position.x = mapLimit;
244
+ if (this.position.z > mapLimit) this.position.z = -mapLimit;
245
+ if (this.position.z < -mapLimit) this.position.z = mapLimit;
246
 
247
  // ๋ฉ”์‹œ ์—…๋ฐ์ดํŠธ
248
  this.mesh.position.copy(this.position);
249
  this.mesh.rotation.copy(this.rotation);
250
 
251
+ // ์†๋„์™€ ๊ณ ๋„ ๊ฐฑ์‹ 
252
+ this.speed = velocity;
253
  this.altitude = this.position.y;
254
 
255
+ // ์‹ค์† ์ฒดํฌ ๋ฐ ์ฒ˜๋ฆฌ
256
  if (this.speed < GAME_CONSTANTS.STALL_SPEED) {
257
+ // ์‹ค์† ์‹œ ๊ธ‰๊ฒฉํ•œ ํ•˜๊ฐ•
258
+ this.acceleration.y -= 15;
259
+ this.targetRotation.x = Math.max(this.targetRotation.x, -Math.PI / 6); // ๊ธฐ์ˆ˜ ํ•˜๊ฐ•
260
  }
261
  }
262
 
263
  shoot(scene) {
264
  const currentTime = Date.now();
265
+ if (currentTime - this.lastShootTime < 80 || this.ammo <= 0) return;
266
 
267
  this.lastShootTime = currentTime;
268
  this.ammo--;
269
 
270
+ // ๊ธฐ๊ด€์ด ๋ฐœ์‚ฌ (30mm ๊ธฐ์ค€)
271
+ const bulletGeometry = new THREE.SphereGeometry(0.3);
272
  const bulletMaterial = new THREE.MeshBasicMaterial({
273
  color: 0xffff00,
274
  emissive: 0xffff00,
275
+ emissiveIntensity: 0.8
276
  });
277
  const bullet = new THREE.Mesh(bulletGeometry, bulletMaterial);
278
 
279
+ // ์ด๊ตฌ ์œ„์น˜ ๊ณ„์‚ฐ (์ „ํˆฌ๊ธฐ ๊ธฐ์ˆ˜)
280
+ const muzzleOffset = new THREE.Vector3(0, 0, 12);
281
  muzzleOffset.applyEuler(this.rotation);
282
  bullet.position.copy(this.position).add(muzzleOffset);
283
 
284
+ // ํƒ„์† ๊ณ„์‚ฐ (์‹ค์ œ ์†๋„ + ์ „ํˆฌ๊ธฐ ์†๋„)
285
+ const bulletSpeed = 1500; // m/s
286
  const direction = new THREE.Vector3(0, 0, 1);
287
  direction.applyEuler(this.rotation);
288
  bullet.velocity = direction.multiplyScalar(bulletSpeed).add(this.velocity);
 
293
  scene.add(bullet);
294
  this.bullets.push(bullet);
295
 
296
+ // ๋ฐœ์‚ฌ์Œ
297
  try {
298
  const audio = new Audio('sounds/gunfire.ogg');
299
+ audio.volume = 0.4;
300
  audio.play();
301
  } catch (e) {
302
  console.log('Sound file not found');
 
304
  }
305
 
306
  createMuzzleFlash(scene, position) {
307
+ const flashGroup = new THREE.Group();
308
+
309
+ // ์ฃผ ํ™”์—ผ
310
+ const flashGeometry = new THREE.SphereGeometry(3);
311
  const flashMaterial = new THREE.MeshBasicMaterial({
312
  color: 0xffa500,
313
  transparent: true,
314
+ opacity: 0.9
315
  });
316
  const flash = new THREE.Mesh(flashGeometry, flashMaterial);
317
+ flash.scale.set(2, 2, 4);
318
+ flashGroup.add(flash);
319
+
320
+ // ์—ฐ๊ธฐ ํšจ๊ณผ
321
+ for (let i = 0; i < 8; i++) {
322
+ const smokeGeometry = new THREE.SphereGeometry(1.5);
323
+ const smokeMaterial = new THREE.MeshBasicMaterial({
324
+ color: 0x666666,
325
+ transparent: true,
326
+ opacity: 0.6
327
+ });
328
+ const smoke = new THREE.Mesh(smokeGeometry, smokeMaterial);
329
+ smoke.position.set(
330
+ (Math.random() - 0.5) * 2,
331
+ (Math.random() - 0.5) * 2,
332
+ -Math.random() * 3
333
+ );
334
+ flashGroup.add(smoke);
335
+ }
336
 
337
+ flashGroup.position.copy(position);
338
+ flashGroup.quaternion.setFromEuler(this.rotation);
339
+ scene.add(flashGroup);
340
 
341
  setTimeout(() => {
342
+ scene.remove(flashGroup);
343
+ }, 150);
344
  }
345
 
346
  updateBullets(scene, deltaTime) {
 
349
  bullet.position.add(bullet.velocity.clone().multiplyScalar(deltaTime));
350
 
351
  // ์ด์•Œ ์ œ๊ฑฐ ์กฐ๊ฑด
352
+ if (bullet.position.distanceTo(this.position) > 12000 ||
353
  bullet.position.y < 0 ||
354
+ bullet.position.y > GAME_CONSTANTS.MAX_ALTITUDE + 1000) {
355
  scene.remove(bullet);
356
  this.bullets.splice(i, 1);
357
  }
 
363
  return this.health <= 0;
364
  }
365
 
366
+ // ํƒฑํฌ ๊ฒŒ์ž„๊ณผ ์œ ์‚ฌํ•œ ์นด๋ฉ”๋ผ ์‹œ์Šคํ…œ
367
  getCameraPosition() {
368
+ // ์ „ํˆฌ๊ธฐ ๋’ค์ชฝ์—์„œ ์ถ”์ ํ•˜๋Š” ์นด๋ฉ”๋ผ
369
+ const offset = new THREE.Vector3(
370
+ Math.sin(this.cameraAngle) * this.cameraDistance,
371
+ this.cameraHeight,
372
+ Math.cos(this.cameraAngle) * this.cameraDistance
373
+ );
374
+
375
+ // ์ „ํˆฌ๊ธฐ์˜ ํšŒ์ „ ์ ์šฉ
376
+ offset.applyEuler(new THREE.Euler(0, this.rotation.y, 0));
377
+
378
  return this.position.clone().add(offset);
379
  }
380
 
381
  getCameraTarget() {
382
+ // ์ „ํˆฌ๊ธฐ๋ฅผ ์ค‘์‹ฌ์œผ๋กœ ํ•œ ํƒ€๊ฒŸ
383
+ return this.position.clone().add(new THREE.Vector3(0, 2, 0));
 
 
384
  }
385
  }
386
 
387
+ // ์  ์ „ํˆฌ๊ธฐ ํด๋ž˜์Šค (๊ฐœ์„ ๋จ)
388
  class EnemyFighter {
389
  constructor(scene, position) {
390
  this.mesh = null;
391
  this.isLoaded = false;
392
  this.scene = scene;
393
  this.position = position.clone();
394
+ this.velocity = new THREE.Vector3(0, 0, 180);
395
  this.rotation = new THREE.Euler(0, 0, 0);
396
+ this.health = 120;
397
+ this.speed = 180;
398
  this.bullets = [];
399
  this.lastShootTime = 0;
400
 
401
+ // ๊ณ ๊ธ‰ AI ์ƒํƒœ
402
  this.aiState = 'patrol';
403
  this.targetPosition = position.clone();
404
  this.patrolCenter = position.clone();
405
+ this.patrolRadius = 3000;
406
  this.lastStateChange = 0;
407
+ this.pursuitMode = false;
408
+ this.evasionTimer = 0;
409
  }
410
 
411
  async initialize(loader) {
 
413
  const result = await loader.loadAsync('models/mig-29.glb');
414
  this.mesh = result.scene;
415
  this.mesh.position.copy(this.position);
416
+ this.mesh.scale.set(2.5, 2.5, 2.5);
417
 
418
  this.mesh.traverse((child) => {
419
  if (child.isMesh) {
 
433
  }
434
 
435
  createFallbackModel() {
 
436
  const group = new THREE.Group();
437
 
438
+ const fuselageGeometry = new THREE.CylinderGeometry(0.8, 1.2, 12, 8);
439
+ const fuselageMaterial = new THREE.MeshLambertMaterial({ color: 0x600000 });
440
  const fuselage = new THREE.Mesh(fuselageGeometry, fuselageMaterial);
441
  fuselage.rotation.x = Math.PI / 2;
442
  group.add(fuselage);
443
 
444
+ const wingGeometry = new THREE.BoxGeometry(16, 0.4, 4);
445
+ const wingMaterial = new THREE.MeshLambertMaterial({ color: 0x400000 });
446
  const wings = new THREE.Mesh(wingGeometry, wingMaterial);
447
+ wings.position.z = -1;
448
  group.add(wings);
449
 
450
  this.mesh = group;
451
  this.mesh.position.copy(this.position);
452
+ this.mesh.scale.set(2.5, 2.5, 2.5);
453
  this.scene.add(this.mesh);
454
  this.isLoaded = true;
455
 
 
462
  const currentTime = Date.now();
463
  const distanceToPlayer = this.position.distanceTo(playerPosition);
464
 
465
+ // AI ์ƒํƒœ ๊ฒฐ์ •
466
+ if (currentTime - this.lastStateChange > 3000) {
467
+ if (distanceToPlayer < 4000) {
468
  this.aiState = 'attack';
469
+ } else if (distanceToPlayer < 8000) {
470
+ this.aiState = 'intercept';
471
  } else {
472
  this.aiState = 'patrol';
473
  }
474
  this.lastStateChange = currentTime;
475
  }
476
 
477
+ // AI ํ–‰๋™ ์‹คํ–‰
478
+ switch (this.aiState) {
479
+ case 'attack':
480
+ this.attackBehavior(playerPosition, deltaTime);
481
+ break;
482
+ case 'intercept':
483
+ this.interceptBehavior(playerPosition, deltaTime);
484
+ break;
485
+ default:
486
+ this.patrolBehavior(deltaTime);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
487
  }
488
 
489
  // ๋ฌผ๋ฆฌ ์—…๋ฐ์ดํŠธ
490
+ this.updatePhysics(deltaTime);
491
+
492
+ // ๊ณต๊ฒฉ ์‹œ๋„
493
+ if (distanceToPlayer < 3000 && currentTime - this.lastShootTime > 1200) {
494
+ this.shoot();
495
+ }
496
+
497
+ // ์ด์•Œ ์—…๋ฐ์ดํŠธ
498
+ this.updateBullets(deltaTime);
499
+ }
500
+
501
+ attackBehavior(playerPosition, deltaTime) {
502
+ // ํ”Œ๋ ˆ์ด์–ด๋ฅผ ํ–ฅํ•œ ์ธํ„ฐ์…‰ํŠธ ์ฝ”์Šค ๊ณ„์‚ฐ
503
+ const playerVelocity = new THREE.Vector3(); // ํ”Œ๋ ˆ์ด์–ด ์†๋„ ์ถ”์ •
504
+ const interceptPoint = playerPosition.clone().add(playerVelocity.multiplyScalar(2));
505
+
506
+ const direction = new THREE.Vector3()
507
+ .subVectors(interceptPoint, this.position)
508
+ .normalize();
509
+
510
+ this.velocity = direction.multiplyScalar(this.speed * 1.2); // ๊ณต๊ฒฉ ์‹œ ์†๋„ ์ฆ๊ฐ€
511
+
512
+ // ํšŒ์ „ ๊ณ„์‚ฐ
513
+ this.rotation.y = Math.atan2(direction.x, direction.z);
514
+ this.rotation.x = Math.atan2(-direction.y,
515
+ Math.sqrt(direction.x * direction.x + direction.z * direction.z));
516
+ }
517
+
518
+ interceptBehavior(playerPosition, deltaTime) {
519
+ const direction = new THREE.Vector3()
520
+ .subVectors(playerPosition, this.position)
521
+ .normalize();
522
+
523
+ this.velocity = direction.multiplyScalar(this.speed);
524
+ this.rotation.y = Math.atan2(direction.x, direction.z);
525
+ }
526
+
527
+ patrolBehavior(deltaTime) {
528
+ if (this.position.distanceTo(this.targetPosition) < 500) {
529
+ // ์ƒˆ๋กœ์šด ์ˆœ์ฐฐ ์ง€์  ์„ค์ •
530
+ const angle = Math.random() * Math.PI * 2;
531
+ this.targetPosition = this.patrolCenter.clone().add(
532
+ new THREE.Vector3(
533
+ Math.cos(angle) * this.patrolRadius,
534
+ (Math.random() - 0.5) * 2000,
535
+ Math.sin(angle) * this.patrolRadius
536
+ )
537
+ );
538
+ }
539
+
540
+ const direction = new THREE.Vector3()
541
+ .subVectors(this.targetPosition, this.position)
542
+ .normalize();
543
+
544
+ this.velocity = direction.multiplyScalar(this.speed * 0.8);
545
+ this.rotation.y = Math.atan2(direction.x, direction.z);
546
+ }
547
+
548
+ updatePhysics(deltaTime) {
549
+ // ๊ฐ„๋‹จํ•œ ๋ฌผ๋ฆฌ ์—…๋ฐ์ดํŠธ
550
  this.position.add(this.velocity.clone().multiplyScalar(deltaTime));
551
 
552
  // ๊ณ ๋„ ์ œํ•œ
 
559
  this.velocity.y = -Math.abs(this.velocity.y);
560
  }
561
 
562
+ // ๋งต ๊ฒฝ๊ณ„ ์ˆœํ™˜
563
  const mapLimit = GAME_CONSTANTS.MAP_SIZE / 2;
564
+ if (this.position.x > mapLimit) this.position.x = -mapLimit;
565
+ if (this.position.x < -mapLimit) this.position.x = mapLimit;
566
+ if (this.position.z > mapLimit) this.position.z = -mapLimit;
567
+ if (this.position.z < -mapLimit) this.position.z = mapLimit;
 
 
568
 
569
  // ๋ฉ”์‹œ ์—…๋ฐ์ดํŠธ
570
  this.mesh.position.copy(this.position);
571
  this.mesh.rotation.copy(this.rotation);
 
 
 
572
  }
573
 
574
  shoot() {
575
  this.lastShootTime = Date.now();
576
 
577
+ const bulletGeometry = new THREE.SphereGeometry(0.25);
578
  const bulletMaterial = new THREE.MeshBasicMaterial({
579
  color: 0xff0000,
580
  emissive: 0xff0000,
581
+ emissiveIntensity: 0.7
582
  });
583
  const bullet = new THREE.Mesh(bulletGeometry, bulletMaterial);
584
 
585
+ const muzzleOffset = new THREE.Vector3(0, 0, 10);
586
  muzzleOffset.applyEuler(this.rotation);
587
  bullet.position.copy(this.position).add(muzzleOffset);
588
 
589
  const direction = new THREE.Vector3(0, 0, 1);
590
  direction.applyEuler(this.rotation);
591
+ bullet.velocity = direction.multiplyScalar(1200);
592
 
593
  this.scene.add(bullet);
594
  this.bullets.push(bullet);
 
599
  const bullet = this.bullets[i];
600
  bullet.position.add(bullet.velocity.clone().multiplyScalar(deltaTime));
601
 
602
+ if (bullet.position.distanceTo(this.position) > 8000 ||
603
  bullet.position.y < 0) {
604
  this.scene.remove(bullet);
605
  this.bullets.splice(i, 1);
 
622
  }
623
  }
624
 
625
+ // ๋ฉ”์ธ ๊ฒŒ์ž„ ํด๋ž˜์Šค
626
  class Game {
627
  constructor() {
628
  this.scene = new THREE.Scene();
629
+ this.camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 100000);
630
  this.renderer = new THREE.WebGLRenderer({ antialias: true });
631
  this.renderer.setSize(window.innerWidth, window.innerHeight);
632
  this.renderer.shadowMap.enabled = true;
633
  this.renderer.shadowMap.type = THREE.PCFSoftShadowMap;
634
  this.renderer.setClearColor(0x87CEEB);
635
+ this.renderer.setPixelRatio(window.devicePixelRatio);
636
 
637
  document.getElementById('gameContainer').appendChild(this.renderer.domElement);
638
 
 
645
  this.score = 0;
646
  this.lastTime = performance.now();
647
  this.gameTimer = null;
648
+ this.animationFrameId = null;
649
+
650
+ // ์˜ค๋””์˜ค ์‹œ์Šคํ…œ
651
+ this.bgm = null;
652
+ this.bgmPlaying = false;
653
 
654
  // ์ž…๋ ฅ ์ƒํƒœ
655
  this.keys = { w: false, a: false, s: false, d: false };
656
  this.isStarted = false;
657
 
658
+ // ๊ธฐ๋ณธ ์ดˆ๊ธฐํ™”
659
  this.setupScene();
660
+ this.setupEventListeners();
661
+ this.preloadGame(); // ๊ฒŒ์ž„ ์‚ฌ์ „ ๋กœ๋”ฉ
662
  }
663
 
664
+ async preloadGame() {
665
  try {
666
+ console.log('๊ฒŒ์ž„ ๋ฆฌ์†Œ์Šค ์‚ฌ์ „ ๋กœ๋”ฉ ์ค‘...');
667
 
668
+ // ์ „ํˆฌ๊ธฐ ์‚ฌ์ „ ๋กœ๋”ฉ
669
  await this.fighter.initialize(this.scene, this.loader);
670
 
671
  if (!this.fighter.isLoaded) {
672
  throw new Error('์ „ํˆฌ๊ธฐ ๋กœ๋”ฉ ์‹คํŒจ');
673
  }
674
 
675
+ // ์  ์ƒ์„ฑ (์‚ฌ์ „ ๋กœ๋”ฉ)
676
+ await this.preloadEnemies();
677
 
678
  this.isLoaded = true;
679
+ console.log('๊ฒŒ์ž„ ๋ฆฌ์†Œ์Šค ๋กœ๋”ฉ ์™„๋ฃŒ');
680
+
681
+ // ๋กœ๋”ฉ ํ™”๋ฉด ์ˆจ๊ธฐ๊ธฐ
682
  document.getElementById('loading').style.display = 'none';
683
 
684
+ // ๋ Œ๋”๋ง ์‹œ์ž‘ (๊ฒŒ์ž„ ์‹œ์ž‘ ์ „)
 
685
  this.animate();
686
 
687
  } catch (error) {
688
+ console.error('๊ฒŒ์ž„ ์‚ฌ์ „ ๋กœ๋”ฉ ์‹คํŒจ:', error);
689
  document.getElementById('loading').innerHTML =
690
+ '<div class="loading-text" style="color: red;">๋กœ๋”ฉ ์‹คํŒจ. ํŽ˜์ด์ง€๋ฅผ ์ƒˆ๋กœ๊ณ ์นจํ•ด์ฃผ์„ธ์š”.</div>';
691
+ }
692
+ }
693
+
694
+ async preloadEnemies() {
695
+ for (let i = 0; i < GAME_CONSTANTS.ENEMY_COUNT; i++) {
696
+ const angle = (i / GAME_CONSTANTS.ENEMY_COUNT) * Math.PI * 2;
697
+ const distance = 8000 + Math.random() * 4000;
698
+
699
+ const position = new THREE.Vector3(
700
+ Math.cos(angle) * distance,
701
+ 3000 + Math.random() * 3000,
702
+ Math.sin(angle) * distance
703
+ );
704
+
705
+ const enemy = new EnemyFighter(this.scene, position);
706
+ await enemy.initialize(this.loader);
707
+ this.enemies.push(enemy);
708
  }
709
  }
710
 
711
  setupScene() {
712
+ // ํ•˜๋Š˜ ๊ทธ๋ผ๋ฐ์ด์…˜
713
  this.scene.background = new THREE.Color(0x87CEEB);
714
+ this.scene.fog = new THREE.Fog(0x87CEEB, 2000, 50000);
715
 
716
+ // ์กฐ๋ช… ์‹œ์Šคํ…œ
717
+ const ambientLight = new THREE.AmbientLight(0xffffff, 0.7);
718
  this.scene.add(ambientLight);
719
 
720
+ const directionalLight = new THREE.DirectionalLight(0xffffff, 1.2);
721
+ directionalLight.position.set(10000, 15000, 10000);
722
  directionalLight.castShadow = true;
723
+ directionalLight.shadow.mapSize.width = 4096;
724
+ directionalLight.shadow.mapSize.height = 4096;
725
  directionalLight.shadow.camera.near = 0.5;
726
+ directionalLight.shadow.camera.far = 30000;
727
+ directionalLight.shadow.camera.left = -15000;
728
+ directionalLight.shadow.camera.right = 15000;
729
+ directionalLight.shadow.camera.top = 15000;
730
+ directionalLight.shadow.camera.bottom = -15000;
731
  this.scene.add(directionalLight);
732
 
733
+ // ์ง€ํ˜• (ํฐ ํ‰๋ฉด)
734
+ const groundGeometry = new THREE.PlaneGeometry(GAME_CONSTANTS.MAP_SIZE * 2, GAME_CONSTANTS.MAP_SIZE * 2);
735
  const groundMaterial = new THREE.MeshLambertMaterial({
736
  color: 0x8FBC8F,
737
  transparent: true,
738
+ opacity: 0.9
739
  });
740
  const ground = new THREE.Mesh(groundGeometry, groundMaterial);
741
  ground.rotation.x = -Math.PI / 2;
742
  ground.receiveShadow = true;
743
  this.scene.add(ground);
744
 
745
+ // ๊ตฌ๋ฆ„ ์ถ”๊ฐ€ (๋” ๋งŽ์ด)
746
  this.addClouds();
747
  }
748
 
749
  addClouds() {
750
+ const cloudGeometry = new THREE.SphereGeometry(200, 8, 6);
751
  const cloudMaterial = new THREE.MeshLambertMaterial({
752
  color: 0xffffff,
753
  transparent: true,
754
  opacity: 0.6
755
  });
756
 
757
+ for (let i = 0; i < 200; i++) {
758
  const cloud = new THREE.Mesh(cloudGeometry, cloudMaterial);
759
  cloud.position.set(
760
+ (Math.random() - 0.5) * GAME_CONSTANTS.MAP_SIZE * 2,
761
+ Math.random() * 6000 + 1500,
762
+ (Math.random() - 0.5) * GAME_CONSTANTS.MAP_SIZE * 2
763
  );
764
  cloud.scale.set(
765
+ Math.random() * 4 + 1,
766
+ Math.random() * 3 + 0.5,
767
+ Math.random() * 4 + 1
768
  );
769
  this.scene.add(cloud);
770
  }
771
  }
772
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
773
  setupEventListeners() {
774
  // ํ‚ค๋ณด๋“œ ์ด๋ฒคํŠธ
775
  document.addEventListener('keydown', (event) => {
 
792
  }
793
  });
794
 
795
+ // ๋งˆ์šฐ์Šค ์ด๋ฒคํŠธ (ํƒฑํฌ ๊ฒŒ์ž„๊ณผ ์œ ์‚ฌ)
796
  document.addEventListener('mousemove', (event) => {
797
  if (!document.pointerLockElement || !this.isStarted || this.isGameOver) return;
798
 
 
820
  });
821
  }
822
 
823
+ startGame() {
824
+ if (!this.isLoaded) {
825
+ console.log('๊ฒŒ์ž„์ด ์•„์ง ๋กœ๋”ฉ ์ค‘์ž…๋‹ˆ๋‹ค...');
826
+ return;
827
+ }
828
+
829
+ this.isStarted = true;
830
+
831
+ // BGM ์‹œ์ž‘
832
+ this.startBGM();
833
+
834
+ // ๊ฒŒ์ž„ ํƒ€์ด๋จธ ์‹œ์ž‘
835
+ this.startGameTimer();
836
+
837
+ console.log('๊ฒŒ์ž„ ์‹œ์ž‘!');
838
+ }
839
+
840
+ startBGM() {
841
+ if (this.bgmPlaying) return;
842
+
843
+ try {
844
+ this.bgm = new Audio('sounds/main_bgm.ogg');
845
+ this.bgm.volume = 0.6;
846
+ this.bgm.loop = true;
847
+ this.bgm.play();
848
+ this.bgmPlaying = true;
849
+ console.log('BGM ์žฌ์ƒ ์‹œ์ž‘');
850
+ } catch (error) {
851
+ console.log('BGM ํŒŒ์ผ์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค:', error);
852
+ }
853
+ }
854
+
855
  startGameTimer() {
856
  this.gameTimer = setInterval(() => {
857
  if (!this.isGameOver) {
 
875
  document.getElementById('health').style.width = `${this.fighter.health}%`;
876
  document.getElementById('ammoDisplay').textContent = `AMMO: ${this.fighter.ammo}`;
877
 
878
+ // ์ƒ์„ธ ์ •๋ณด ํ‘œ์‹œ
879
  const gameStats = document.getElementById('gameStats');
880
+ if (gameStats) {
881
+ gameStats.innerHTML = `
882
+ <div>Score: ${this.score}</div>
883
+ <div>Time: ${this.gameTime}s</div>
884
+ <div>Speed: ${speedKnots} KT</div>
885
+ <div>Alt: ${altitudeFeet} FT</div>
886
+ <div>Throttle: ${Math.round(this.fighter.throttle * 100)}%</div>
887
+ <div>G-Force: ${this.fighter.gForce.toFixed(1)}</div>
888
+ <div>Targets: ${this.enemies.length}</div>
889
+ `;
890
+ }
891
  }
892
  }
893
 
894
  updateRadar() {
895
  const radar = document.getElementById('radar');
896
+ if (!radar) return;
897
 
898
  // ๊ธฐ์กด ์  ๋„ํŠธ ์ œ๊ฑฐ
899
  const oldDots = radar.getElementsByClassName('enemy-dot');
 
902
  }
903
 
904
  // ๋ ˆ์ด๋” ์ค‘์‹ฌ
905
+ const radarCenter = { x: 100, y: 100 };
906
+ const radarRange = 15000; // ๊ฐ์ง€ ๋ฒ”์œ„
907
 
908
  this.enemies.forEach(enemy => {
909
  if (!enemy.mesh || !enemy.isLoaded) return;
 
936
  if (!enemy.mesh || !enemy.isLoaded) continue;
937
 
938
  const distance = bullet.position.distanceTo(enemy.position);
939
+ if (distance < 30) {
940
  // ๋ช…์ค‘!
941
  this.scene.remove(bullet);
942
  this.fighter.bullets.splice(i, 1);
943
 
944
+ if (enemy.takeDamage(40)) {
945
  enemy.destroy();
946
  this.enemies.splice(j, 1);
947
+ this.score += 150;
948
  }
949
  break;
950
  }
 
955
  this.enemies.forEach(enemy => {
956
  enemy.bullets.forEach((bullet, index) => {
957
  const distance = bullet.position.distanceTo(this.fighter.position);
958
+ if (distance < 35) {
959
  // ํ”ผ๊ฒฉ!
960
  this.scene.remove(bullet);
961
  enemy.bullets.splice(index, 1);
962
 
963
+ if (this.fighter.takeDamage(25)) {
964
  this.endGame(false);
965
  }
966
  }
 
971
  animate() {
972
  if (this.isGameOver) return;
973
 
974
+ this.animationFrameId = requestAnimationFrame(() => this.animate());
975
 
976
  const currentTime = performance.now();
977
  const deltaTime = Math.min((currentTime - this.lastTime) / 1000, 0.1);
978
  this.lastTime = currentTime;
979
 
980
+ if (this.isLoaded && this.fighter.isLoaded) {
981
+ if (this.isStarted) {
982
+ // ์ „ํˆฌ๊ธฐ ์—…๋ฐ์ดํŠธ
983
+ this.fighter.updateControls(this.keys, deltaTime);
984
+ this.fighter.updatePhysics(deltaTime);
985
+ this.fighter.updateBullets(this.scene, deltaTime);
986
+
987
+ // ์  ์—…๋ฐ์ดํŠธ
988
+ this.enemies.forEach(enemy => {
989
+ enemy.update(this.fighter.position, deltaTime);
990
+ });
991
+
992
+ // ์ถฉ๋Œ ๊ฒ€์‚ฌ
993
+ this.checkCollisions();
994
+
995
+ // UI ์—…๋ฐ์ดํŠธ
996
+ this.updateUI();
997
+ this.updateRadar();
998
+
999
+ // ์Šน๋ฆฌ ์กฐ๊ฑด ํ™•์ธ
1000
+ if (this.enemies.length === 0) {
1001
+ this.endGame(true);
1002
+ }
1003
+ }
1004
 
1005
+ // ์นด๋ฉ”๋ผ ์—…๋ฐ์ดํŠธ (ํƒฑํฌ ๊ฒŒ์ž„๊ณผ ์œ ์‚ฌ)
1006
  const cameraPos = this.fighter.getCameraPosition();
1007
  const cameraTarget = this.fighter.getCameraTarget();
1008
+ this.camera.position.lerp(cameraPos, 0.15);
1009
  this.camera.lookAt(cameraTarget);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1010
  }
1011
 
1012
  this.renderer.render(this.scene, this.camera);
 
1015
  endGame(victory = false) {
1016
  this.isGameOver = true;
1017
 
1018
+ // BGM ์ •์ง€
1019
+ if (this.bgm) {
1020
+ this.bgm.pause();
1021
+ this.bgm = null;
1022
+ this.bgmPlaying = false;
1023
+ }
1024
+
1025
  if (this.gameTimer) {
1026
  clearInterval(this.gameTimer);
1027
  }
 
1035
  ${victory ? 'MISSION ACCOMPLISHED!' : 'SHOT DOWN!'}
1036
  </h1>
1037
  <div style="color: #0f0; font-size: 24px; margin: 20px 0;">
1038
+ Final Score: ${this.score}<br>
1039
  Enemies Destroyed: ${GAME_CONSTANTS.ENEMY_COUNT - this.enemies.length}<br>
1040
+ Mission Time: ${GAME_CONSTANTS.MISSION_DURATION - this.gameTime}s<br>
1041
+ Max Speed: ${Math.round(this.fighter.speed * 1.94384)} KT<br>
1042
+ Max G-Force: ${this.fighter.gForce.toFixed(1)}
1043
  </div>
1044
  <button class="start-button" onclick="location.reload()">
1045
  New Mission
 
1053
  // ๊ฒŒ์ž„ ์ธ์Šคํ„ด์Šค
1054
  let gameInstance = null;
1055
 
1056
+ // ์ „์—ญ ํ•จ์ˆ˜ (HTML์—์„œ ํ˜ธ์ถœ)
1057
  window.startGame = function() {
1058
  document.getElementById('startScreen').style.display = 'none';
1059
  document.body.requestPointerLock();
1060
 
1061
+ if (gameInstance && gameInstance.isLoaded) {
1062
+ gameInstance.startGame();
1063
+ } else {
1064
+ console.log('๊ฒŒ์ž„์ด ์•„์ง ์ค€๋น„๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค...');
1065
  }
 
 
 
1066
  };
1067
 
1068
+ // ๊ฒŒ์ž„ ์ดˆ๊ธฐํ™” (ํŽ˜์ด์ง€ ๋กœ๋“œ ์‹œ)
1069
  document.addEventListener('DOMContentLoaded', () => {
1070
+ console.log('์ „ํˆฌ๊ธฐ ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ ์ดˆ๊ธฐํ™” ์ค‘...');
1071
+ gameInstance = new Game();
1072
  });