cutechicken commited on
Commit
f5058f0
ยท
verified ยท
1 Parent(s): 46b60f2

Update game.js

Browse files
Files changed (1) hide show
  1. game.js +221 -368
game.js CHANGED
@@ -10,7 +10,7 @@ const GAME_CONSTANTS = {
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,
@@ -25,29 +25,31 @@ class Fighter {
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,11 +57,10 @@ class Fighter {
55
  this.bullets = [];
56
  this.lastShootTime = 0;
57
 
58
- // ์นด๋ฉ”๋ผ
59
- this.cameraDistance = 50; // ๊ฑฐ๋ฆฌ ์ฆ๊ฐ€
60
- this.cameraHeight = 15; // ๋†’์ด ์ฆ๊ฐ€
61
- this.cameraAngle = 0; // ๋งˆ์šฐ์Šค๋กœ ์ œ์–ด๋˜๋Š” ์นด๋ฉ”๋ผ ๊ฐ๋„
62
- this.cameraPitch = 0.1; // ์•ฝ๊ฐ„ ์œ„์—์„œ ๋‚ด๋ ค๋‹ค๋ณด๋Š” ๊ฐ๋„
63
  }
64
 
65
  async initialize(scene, loader) {
@@ -67,7 +68,7 @@ class Fighter {
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) => {
@@ -90,154 +91,128 @@ class Fighter {
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;
221
-
222
- // ์†๋„ ์—…๋ฐ์ดํŠธ
223
- this.velocity.add(this.acceleration.clone().multiplyScalar(deltaTime));
224
 
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) {
236
  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;
@@ -248,176 +223,117 @@ class Fighter {
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);
289
 
290
- // ์ด๊ตฌ ํ™”์—ผ ํšจ๊ณผ
291
- this.createMuzzleFlash(scene, bullet.position);
292
-
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');
303
  }
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) {
347
  for (let i = this.bullets.length - 1; i >= 0; i--) {
348
  const bullet = this.bullets[i];
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
  }
358
  }
359
  }
360
 
 
361
  takeDamage(damage) {
362
  this.health -= damage;
363
  return this.health <= 0;
364
  }
365
 
366
- // ํƒฑํฌ ๊ฒŒ์ž„๊ณผ ์œ ์‚ฌํ•œ ์นด๋ฉ”๋ผ ์‹œ์Šคํ…œ
367
  getCameraPosition() {
368
- // ์ „ํˆฌ๊ธฐ ๋’ค์ชฝ์—์„œ ์ถ”์ ํ•˜๋Š” ์นด๋ฉ”๋ผ (๋” ๋ฉ€๋ฆฌ)
369
- const cameraDistance = 50; // ๊ฑฐ๋ฆฌ ์ฆ๊ฐ€
370
- const cameraHeight = 15; // ๋†’์ด ์ฆ๊ฐ€
371
-
372
  // ์ „ํˆฌ๊ธฐ ๋’ค์ชฝ ๋ฐฉํ–ฅ ๊ณ„์‚ฐ
373
- const backwardDirection = new THREE.Vector3(0, 0, -1); // ๋’ค์ชฝ
374
- backwardDirection.applyEuler(this.rotation);
375
 
376
- // ์นด๋ฉ”๋ผ ์œ„์น˜ = ์ „ํˆฌ๊ธฐ ์œ„์น˜ + (๋’ค์ชฝ ๋ฐฉํ–ฅ * ๊ฑฐ๋ฆฌ) + ๋†’์ด
377
- const cameraPosition = this.position.clone()
378
- .add(backwardDirection.multiplyScalar(cameraDistance))
379
- .add(new THREE.Vector3(0, cameraHeight, 0));
380
 
381
- // ๋งˆ์šฐ์Šค๋กœ ์นด๋ฉ”๋ผ ๊ฐ๋„ ์กฐ์ •
382
- const mouseOffset = new THREE.Vector3(
383
- Math.sin(this.cameraAngle) * 20,
384
- 0,
385
- Math.cos(this.cameraAngle) * 20
386
- );
387
-
388
- return cameraPosition.add(mouseOffset);
389
  }
390
 
 
391
  getCameraTarget() {
392
- // ์ „ํˆฌ๊ธฐ๋ฅผ ๋ฐ”๋ผ๋ณด๋Š” ํƒ€๊ฒŸ (์•ฝ๊ฐ„ ์•ž์ชฝ)
393
- const targetOffset = new THREE.Vector3(0, 0, 10);
394
- targetOffset.applyEuler(this.rotation);
395
- return this.position.clone().add(targetOffset);
396
  }
397
  }
398
 
399
- // ์  ์ „ํˆฌ๊ธฐ ํด๋ž˜์Šค (๊ฐœ์„ ๋จ)
400
  class EnemyFighter {
401
  constructor(scene, position) {
402
  this.mesh = null;
403
  this.isLoaded = false;
404
  this.scene = scene;
405
  this.position = position.clone();
406
- this.velocity = new THREE.Vector3(0, 0, 180);
407
  this.rotation = new THREE.Euler(0, 0, 0);
408
- this.health = 120;
409
- this.speed = 180;
410
  this.bullets = [];
411
  this.lastShootTime = 0;
412
 
413
- // ๊ณ ๊ธ‰ AI ์ƒํƒœ
414
  this.aiState = 'patrol';
415
  this.targetPosition = position.clone();
416
  this.patrolCenter = position.clone();
417
- this.patrolRadius = 3000;
418
  this.lastStateChange = 0;
419
- this.pursuitMode = false;
420
- this.evasionTimer = 0;
421
  }
422
 
423
  async initialize(loader) {
@@ -425,7 +341,7 @@ class EnemyFighter {
425
  const result = await loader.loadAsync('models/mig-29.glb');
426
  this.mesh = result.scene;
427
  this.mesh.position.copy(this.position);
428
- this.mesh.scale.set(2.5, 2.5, 2.5);
429
 
430
  this.mesh.traverse((child) => {
431
  if (child.isMesh) {
@@ -447,21 +363,21 @@ class EnemyFighter {
447
  createFallbackModel() {
448
  const group = new THREE.Group();
449
 
450
- const fuselageGeometry = new THREE.CylinderGeometry(0.8, 1.2, 12, 8);
451
- const fuselageMaterial = new THREE.MeshLambertMaterial({ color: 0x600000 });
452
  const fuselage = new THREE.Mesh(fuselageGeometry, fuselageMaterial);
453
  fuselage.rotation.x = Math.PI / 2;
454
  group.add(fuselage);
455
 
456
- const wingGeometry = new THREE.BoxGeometry(16, 0.4, 4);
457
- const wingMaterial = new THREE.MeshLambertMaterial({ color: 0x400000 });
458
  const wings = new THREE.Mesh(wingGeometry, wingMaterial);
459
- wings.position.z = -1;
460
  group.add(wings);
461
 
462
  this.mesh = group;
463
  this.mesh.position.copy(this.position);
464
- this.mesh.scale.set(2.5, 2.5, 2.5);
465
  this.scene.add(this.mesh);
466
  this.isLoaded = true;
467
 
@@ -474,101 +390,49 @@ class EnemyFighter {
474
  const currentTime = Date.now();
475
  const distanceToPlayer = this.position.distanceTo(playerPosition);
476
 
477
- // AI ์ƒํƒœ ๊ฒฐ์ •
478
- if (currentTime - this.lastStateChange > 3000) {
479
- if (distanceToPlayer < 4000) {
480
- this.aiState = 'attack';
481
- } else if (distanceToPlayer < 8000) {
482
- this.aiState = 'intercept';
483
- } else {
484
- this.aiState = 'patrol';
 
 
 
 
485
  }
486
- this.lastStateChange = currentTime;
487
- }
488
-
489
- // AI ํ–‰๋™ ์‹คํ–‰
490
- switch (this.aiState) {
491
- case 'attack':
492
- this.attackBehavior(playerPosition, deltaTime);
493
- break;
494
- case 'intercept':
495
- this.interceptBehavior(playerPosition, deltaTime);
496
- break;
497
- default:
498
- this.patrolBehavior(deltaTime);
499
- }
500
-
501
- // ๋ฌผ๋ฆฌ ์—…๋ฐ์ดํŠธ
502
- this.updatePhysics(deltaTime);
503
-
504
- // ๊ณต๊ฒฉ ์‹œ๋„
505
- if (distanceToPlayer < 3000 && currentTime - this.lastShootTime > 1200) {
506
- this.shoot();
507
- }
508
-
509
- // ์ด์•Œ ์—…๋ฐ์ดํŠธ
510
- this.updateBullets(deltaTime);
511
- }
512
-
513
- attackBehavior(playerPosition, deltaTime) {
514
- // ํ”Œ๋ ˆ์ด์–ด๋ฅผ ํ–ฅํ•œ ์ธํ„ฐ์…‰ํŠธ ์ฝ”์Šค ๊ณ„์‚ฐ
515
- const playerVelocity = new THREE.Vector3(); // ํ”Œ๋ ˆ์ด์–ด ์†๋„ ์ถ”์ •
516
- const interceptPoint = playerPosition.clone().add(playerVelocity.multiplyScalar(2));
517
-
518
- const direction = new THREE.Vector3()
519
- .subVectors(interceptPoint, this.position)
520
- .normalize();
521
-
522
- this.velocity = direction.multiplyScalar(this.speed * 1.2); // ๊ณต๊ฒฉ ์‹œ ์†๋„ ์ฆ๊ฐ€
523
-
524
- // ํšŒ์ „ ๊ณ„์‚ฐ
525
- this.rotation.y = Math.atan2(direction.x, direction.z);
526
- this.rotation.x = Math.atan2(-direction.y,
527
- Math.sqrt(direction.x * direction.x + direction.z * direction.z));
528
- }
529
-
530
- interceptBehavior(playerPosition, deltaTime) {
531
- const direction = new THREE.Vector3()
532
- .subVectors(playerPosition, this.position)
533
- .normalize();
534
-
535
- this.velocity = direction.multiplyScalar(this.speed);
536
- this.rotation.y = Math.atan2(direction.x, direction.z);
537
- }
538
-
539
- patrolBehavior(deltaTime) {
540
- if (this.position.distanceTo(this.targetPosition) < 500) {
541
- // ์ƒˆ๋กœ์šด ์ˆœ์ฐฐ ์ง€์  ์„ค์ •
542
- const angle = Math.random() * Math.PI * 2;
543
- this.targetPosition = this.patrolCenter.clone().add(
544
- new THREE.Vector3(
545
- Math.cos(angle) * this.patrolRadius,
546
- (Math.random() - 0.5) * 2000,
547
- Math.sin(angle) * this.patrolRadius
548
- )
549
- );
550
  }
551
 
552
- const direction = new THREE.Vector3()
553
- .subVectors(this.targetPosition, this.position)
554
- .normalize();
555
-
556
- this.velocity = direction.multiplyScalar(this.speed * 0.8);
557
- this.rotation.y = Math.atan2(direction.x, direction.z);
558
- }
559
-
560
- updatePhysics(deltaTime) {
561
- // ๊ฐ„๋‹จํ•œ ๋ฌผ๋ฆฌ ์—…๋ฐ์ดํŠธ
562
  this.position.add(this.velocity.clone().multiplyScalar(deltaTime));
563
 
564
  // ๊ณ ๋„ ์ œํ•œ
565
  if (this.position.y < GAME_CONSTANTS.MIN_ALTITUDE) {
566
  this.position.y = GAME_CONSTANTS.MIN_ALTITUDE;
567
- this.velocity.y = Math.abs(this.velocity.y);
568
  }
569
  if (this.position.y > GAME_CONSTANTS.MAX_ALTITUDE) {
570
  this.position.y = GAME_CONSTANTS.MAX_ALTITUDE;
571
- this.velocity.y = -Math.abs(this.velocity.y);
572
  }
573
 
574
  // ๋งต ๊ฒฝ๊ณ„ ์ˆœํ™˜
@@ -581,26 +445,29 @@ class EnemyFighter {
581
  // ๋ฉ”์‹œ ์—…๋ฐ์ดํŠธ
582
  this.mesh.position.copy(this.position);
583
  this.mesh.rotation.copy(this.rotation);
 
 
 
584
  }
585
 
586
  shoot() {
587
  this.lastShootTime = Date.now();
588
 
589
- const bulletGeometry = new THREE.SphereGeometry(0.25);
590
  const bulletMaterial = new THREE.MeshBasicMaterial({
591
  color: 0xff0000,
592
  emissive: 0xff0000,
593
- emissiveIntensity: 0.7
594
  });
595
  const bullet = new THREE.Mesh(bulletGeometry, bulletMaterial);
596
 
597
- const muzzleOffset = new THREE.Vector3(0, 0, 10);
598
  muzzleOffset.applyEuler(this.rotation);
599
  bullet.position.copy(this.position).add(muzzleOffset);
600
 
601
  const direction = new THREE.Vector3(0, 0, 1);
602
  direction.applyEuler(this.rotation);
603
- bullet.velocity = direction.multiplyScalar(1200);
604
 
605
  this.scene.add(bullet);
606
  this.bullets.push(bullet);
@@ -611,7 +478,7 @@ class EnemyFighter {
611
  const bullet = this.bullets[i];
612
  bullet.position.add(bullet.velocity.clone().multiplyScalar(deltaTime));
613
 
614
- if (bullet.position.distanceTo(this.position) > 8000 ||
615
  bullet.position.y < 0) {
616
  this.scene.remove(bullet);
617
  this.bullets.splice(i, 1);
@@ -638,7 +505,7 @@ class EnemyFighter {
638
  class Game {
639
  constructor() {
640
  this.scene = new THREE.Scene();
641
- this.camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 100000);
642
  this.renderer = new THREE.WebGLRenderer({ antialias: true });
643
  this.renderer.setSize(window.innerWidth, window.innerHeight);
644
  this.renderer.shadowMap.enabled = true;
@@ -670,7 +537,7 @@ class Game {
670
  // ๊ธฐ๋ณธ ์ดˆ๊ธฐํ™”
671
  this.setupScene();
672
  this.setupEventListeners();
673
- this.preloadGame(); // ๊ฒŒ์ž„ ์‚ฌ์ „ ๋กœ๋”ฉ
674
  }
675
 
676
  async preloadGame() {
@@ -684,12 +551,12 @@ class Game {
684
  throw new Error('์ „ํˆฌ๊ธฐ ๋กœ๋”ฉ ์‹คํŒจ');
685
  }
686
 
687
- // ์ดˆ๊ธฐ ์นด๋ฉ”๋ผ ์œ„์น˜ ์„ค์ • (์ „ํˆฌ๊ธฐ ๋’ค์ชฝ)
688
- const initialCameraPos = new THREE.Vector3(0, 2020, -60);
689
  this.camera.position.copy(initialCameraPos);
690
  this.camera.lookAt(this.fighter.position);
691
 
692
- // ์  ์ƒ์„ฑ (์‚ฌ์ „ ๋กœ๋”ฉ)
693
  await this.preloadEnemies();
694
 
695
  this.isLoaded = true;
@@ -698,7 +565,7 @@ class Game {
698
  // ๋กœ๋”ฉ ํ™”๋ฉด ์ˆจ๊ธฐ๊ธฐ
699
  document.getElementById('loading').style.display = 'none';
700
 
701
- // ๋ Œ๋”๋ง ์‹œ์ž‘ (๊ฒŒ์ž„ ์‹œ์ž‘ ์ „)
702
  this.animate();
703
 
704
  } catch (error) {
@@ -711,11 +578,11 @@ class Game {
711
  async preloadEnemies() {
712
  for (let i = 0; i < GAME_CONSTANTS.ENEMY_COUNT; i++) {
713
  const angle = (i / GAME_CONSTANTS.ENEMY_COUNT) * Math.PI * 2;
714
- const distance = 8000 + Math.random() * 4000;
715
 
716
  const position = new THREE.Vector3(
717
  Math.cos(angle) * distance,
718
- 3000 + Math.random() * 3000,
719
  Math.sin(angle) * distance
720
  );
721
 
@@ -726,62 +593,62 @@ class Game {
726
  }
727
 
728
  setupScene() {
729
- // ํ•˜๋Š˜ ๊ทธ๋ผ๋ฐ์ด์…˜
730
  this.scene.background = new THREE.Color(0x87CEEB);
731
- this.scene.fog = new THREE.Fog(0x87CEEB, 2000, 50000);
732
 
733
- // ์กฐ๋ช… ์‹œ์Šคํ…œ
734
- const ambientLight = new THREE.AmbientLight(0xffffff, 0.7);
735
  this.scene.add(ambientLight);
736
 
737
- const directionalLight = new THREE.DirectionalLight(0xffffff, 1.2);
738
- directionalLight.position.set(10000, 15000, 10000);
739
  directionalLight.castShadow = true;
740
- directionalLight.shadow.mapSize.width = 4096;
741
- directionalLight.shadow.mapSize.height = 4096;
742
  directionalLight.shadow.camera.near = 0.5;
743
- directionalLight.shadow.camera.far = 30000;
744
- directionalLight.shadow.camera.left = -15000;
745
- directionalLight.shadow.camera.right = 15000;
746
- directionalLight.shadow.camera.top = 15000;
747
- directionalLight.shadow.camera.bottom = -15000;
748
  this.scene.add(directionalLight);
749
 
750
- // ์ง€ํ˜• (ํฐ ํ‰๋ฉด)
751
- const groundGeometry = new THREE.PlaneGeometry(GAME_CONSTANTS.MAP_SIZE * 2, GAME_CONSTANTS.MAP_SIZE * 2);
752
  const groundMaterial = new THREE.MeshLambertMaterial({
753
  color: 0x8FBC8F,
754
  transparent: true,
755
- opacity: 0.9
756
  });
757
  const ground = new THREE.Mesh(groundGeometry, groundMaterial);
758
  ground.rotation.x = -Math.PI / 2;
759
  ground.receiveShadow = true;
760
  this.scene.add(ground);
761
 
762
- // ๊ตฌ๋ฆ„ ์ถ”๊ฐ€ (๋” ๋งŽ์ด)
763
  this.addClouds();
764
  }
765
 
766
  addClouds() {
767
- const cloudGeometry = new THREE.SphereGeometry(200, 8, 6);
768
  const cloudMaterial = new THREE.MeshLambertMaterial({
769
  color: 0xffffff,
770
  transparent: true,
771
- opacity: 0.6
772
  });
773
 
774
- for (let i = 0; i < 200; i++) {
775
  const cloud = new THREE.Mesh(cloudGeometry, cloudMaterial);
776
  cloud.position.set(
777
- (Math.random() - 0.5) * GAME_CONSTANTS.MAP_SIZE * 2,
778
- Math.random() * 6000 + 1500,
779
- (Math.random() - 0.5) * GAME_CONSTANTS.MAP_SIZE * 2
780
  );
781
  cloud.scale.set(
782
- Math.random() * 4 + 1,
783
- Math.random() * 3 + 0.5,
784
- Math.random() * 4 + 1
785
  );
786
  this.scene.add(cloud);
787
  }
@@ -809,7 +676,7 @@ class Game {
809
  }
810
  });
811
 
812
- // ๋งˆ์šฐ์Šค ์ด๋ฒคํŠธ (ํƒฑํฌ ๊ฒŒ์ž„๊ณผ ์œ ์‚ฌ)
813
  document.addEventListener('mousemove', (event) => {
814
  if (!document.pointerLockElement || !this.isStarted || this.isGameOver) return;
815
 
@@ -844,11 +711,7 @@ class Game {
844
  }
845
 
846
  this.isStarted = true;
847
-
848
- // BGM ์‹œ์ž‘
849
  this.startBGM();
850
-
851
- // ๊ฒŒ์ž„ ํƒ€์ด๋จธ ์‹œ์ž‘
852
  this.startGameTimer();
853
 
854
  console.log('๊ฒŒ์ž„ ์‹œ์ž‘!');
@@ -859,7 +722,7 @@ class Game {
859
 
860
  try {
861
  this.bgm = new Audio('sounds/main_bgm.ogg');
862
- this.bgm.volume = 0.6;
863
  this.bgm.loop = true;
864
  this.bgm.play();
865
  this.bgmPlaying = true;
@@ -875,7 +738,7 @@ class Game {
875
  this.gameTime--;
876
 
877
  if (this.gameTime <= 0) {
878
- this.endGame(true); // ์‹œ๊ฐ„ ์ข…๋ฃŒ ์‹œ ์Šน๋ฆฌ
879
  }
880
  }
881
  }, 1000);
@@ -883,7 +746,6 @@ class Game {
883
 
884
  updateUI() {
885
  if (this.fighter.isLoaded) {
886
- // ์†๋„๋ฅผ ๋…ธํŠธ ๋‹จ์œ„๋กœ ๋ณ€ํ™˜
887
  const speedKnots = Math.round(this.fighter.speed * 1.94384);
888
  const altitudeFeet = Math.round(this.fighter.altitude * 3.28084);
889
 
@@ -892,7 +754,6 @@ class Game {
892
  document.getElementById('health').style.width = `${this.fighter.health}%`;
893
  document.getElementById('ammoDisplay').textContent = `AMMO: ${this.fighter.ammo}`;
894
 
895
- // ์ƒ์„ธ ์ •๋ณด ํ‘œ์‹œ
896
  const gameStats = document.getElementById('gameStats');
897
  if (gameStats) {
898
  gameStats.innerHTML = `
@@ -912,15 +773,13 @@ class Game {
912
  const radar = document.getElementById('radar');
913
  if (!radar) return;
914
 
915
- // ๊ธฐ์กด ์  ๋„ํŠธ ์ œ๊ฑฐ
916
  const oldDots = radar.getElementsByClassName('enemy-dot');
917
  while (oldDots[0]) {
918
  oldDots[0].remove();
919
  }
920
 
921
- // ๋ ˆ์ด๋” ์ค‘์‹ฌ
922
  const radarCenter = { x: 100, y: 100 };
923
- const radarRange = 15000; // ๊ฐ์ง€ ๋ฒ”์œ„
924
 
925
  this.enemies.forEach(enemy => {
926
  if (!enemy.mesh || !enemy.isLoaded) return;
@@ -953,15 +812,14 @@ class Game {
953
  if (!enemy.mesh || !enemy.isLoaded) continue;
954
 
955
  const distance = bullet.position.distanceTo(enemy.position);
956
- if (distance < 30) {
957
- // ๋ช…์ค‘!
958
  this.scene.remove(bullet);
959
  this.fighter.bullets.splice(i, 1);
960
 
961
- if (enemy.takeDamage(40)) {
962
  enemy.destroy();
963
  this.enemies.splice(j, 1);
964
- this.score += 150;
965
  }
966
  break;
967
  }
@@ -972,12 +830,11 @@ class Game {
972
  this.enemies.forEach(enemy => {
973
  enemy.bullets.forEach((bullet, index) => {
974
  const distance = bullet.position.distanceTo(this.fighter.position);
975
- if (distance < 35) {
976
- // ํ”ผ๊ฒฉ!
977
  this.scene.remove(bullet);
978
  enemy.bullets.splice(index, 1);
979
 
980
- if (this.fighter.takeDamage(25)) {
981
  this.endGame(false);
982
  }
983
  }
@@ -1019,13 +876,12 @@ class Game {
1019
  }
1020
  }
1021
 
1022
- // ์นด๋ฉ”๋ผ ์—…๋ฐ์ดํŠธ (ํƒฑํฌ ๊ฒŒ์ž„๊ณผ ์œ ์‚ฌ)
1023
- const cameraPos = this.fighter.getCameraPosition();
1024
- const cameraTarget = this.fighter.getCameraTarget();
1025
 
1026
- // ๋ถ€๋“œ๋Ÿฌ์šด ์นด๋ฉ”๋ผ ์ด๋™
1027
- this.camera.position.lerp(cameraPos, 0.1);
1028
- this.camera.lookAt(cameraTarget);
1029
  }
1030
 
1031
  this.renderer.render(this.scene, this.camera);
@@ -1034,7 +890,6 @@ class Game {
1034
  endGame(victory = false) {
1035
  this.isGameOver = true;
1036
 
1037
- // BGM ์ •์ง€
1038
  if (this.bgm) {
1039
  this.bgm.pause();
1040
  this.bgm = null;
@@ -1056,9 +911,7 @@ class Game {
1056
  <div style="color: #0f0; font-size: 24px; margin: 20px 0;">
1057
  Final Score: ${this.score}<br>
1058
  Enemies Destroyed: ${GAME_CONSTANTS.ENEMY_COUNT - this.enemies.length}<br>
1059
- Mission Time: ${GAME_CONSTANTS.MISSION_DURATION - this.gameTime}s<br>
1060
- Max Speed: ${Math.round(this.fighter.speed * 1.94384)} KT<br>
1061
- Max G-Force: ${this.fighter.gForce.toFixed(1)}
1062
  </div>
1063
  <button class="start-button" onclick="location.reload()">
1064
  New Mission
@@ -1072,7 +925,7 @@ class Game {
1072
  // ๊ฒŒ์ž„ ์ธ์Šคํ„ด์Šค
1073
  let gameInstance = null;
1074
 
1075
- // ์ „์—ญ ํ•จ์ˆ˜ (HTML์—์„œ ํ˜ธ์ถœ)
1076
  window.startGame = function() {
1077
  document.getElementById('startScreen').style.display = 'none';
1078
  document.body.requestPointerLock();
@@ -1084,7 +937,7 @@ window.startGame = function() {
1084
  }
1085
  };
1086
 
1087
- // ๊ฒŒ์ž„ ์ดˆ๊ธฐํ™” (ํŽ˜์ด์ง€ ๋กœ๋“œ ์‹œ)
1088
  document.addEventListener('DOMContentLoaded', () => {
1089
  console.log('์ „ํˆฌ๊ธฐ ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ ์ดˆ๊ธฐํ™” ์ค‘...');
1090
  gameInstance = new Game();
 
10
  MAX_SPEED: 800,
11
  STALL_SPEED: 120,
12
  GRAVITY: 9.8,
13
+ MOUSE_SENSITIVITY: 0.001, // ๋งˆ์šฐ์Šค ๊ฐ๋„ ๋‚ฎ์ถค
14
  MAX_G_FORCE: 12.0,
15
  ENEMY_COUNT: 4,
16
  MISSILE_COUNT: 6,
 
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
 
32
+ // ๋น„ํ–‰ ์ œ์–ด (์›Œ์ฌ๋” ์Šคํƒ€์ผ)
33
+ this.baseSpeed = 150; // ๊ธฐ๋ณธ ์†๋„
34
+ this.throttle = 0.5; // ๊ธฐ๋ณธ ์Šค๋กœํ‹€ 50%
35
+ this.speed = 150;
36
  this.altitude = 2000;
37
  this.gForce = 1.0;
38
  this.health = 100;
39
 
40
+ // ์กฐ์ข… ์ž…๋ ฅ ์‹œ์Šคํ…œ
41
+ this.pitchInput = 0; // ๋งˆ์šฐ์Šค Y -> ๊ธฐ์ˆ˜ ์ƒํ•˜
42
+ this.rollInput = 0; // ๋งˆ์šฐ์Šค X -> ์ขŒ์šฐ ํšŒ์ „ (๋กค)
43
+ this.yawInput = 0; // A/D ํ‚ค -> ๋Ÿฌ๋”
44
+
45
+ // ๋งˆ์šฐ์Šค ๋ˆ„์  ์ž…๋ ฅ
46
+ this.mousePitch = 0;
47
+ this.mouseRoll = 0;
48
 
49
  // ๋ถ€๋“œ๋Ÿฌ์šด ํšŒ์ „์„ ์œ„ํ•œ ๋ชฉํ‘œ๊ฐ’
50
+ this.targetPitch = 0;
51
+ this.targetRoll = 0;
52
+ this.targetYaw = 0;
53
 
54
  // ๋ฌด๊ธฐ
55
  this.missiles = GAME_CONSTANTS.MISSILE_COUNT;
 
57
  this.bullets = [];
58
  this.lastShootTime = 0;
59
 
60
+ // ์นด๋ฉ”๋ผ ์„ค์ •
61
+ this.cameraDistance = 40;
62
+ this.cameraHeight = 10;
63
+ this.cameraLag = 0.08; // ์นด๋ฉ”๋ผ ๋”ฐ๋ผ์˜ค๋Š” ์†๋„
 
64
  }
65
 
66
  async initialize(scene, loader) {
 
68
  const result = await loader.loadAsync('models/f-15.glb');
69
  this.mesh = result.scene;
70
  this.mesh.position.copy(this.position);
71
+ this.mesh.scale.set(2, 2, 2);
72
 
73
  // ๊ทธ๋ฆผ์ž ์„ค์ •
74
  this.mesh.traverse((child) => {
 
91
  createFallbackModel(scene) {
92
  const group = new THREE.Group();
93
 
94
+ // ๋™์ฒด
95
+ const fuselageGeometry = new THREE.CylinderGeometry(0.8, 1.5, 12, 8);
96
  const fuselageMaterial = new THREE.MeshLambertMaterial({ color: 0x606060 });
97
  const fuselage = new THREE.Mesh(fuselageGeometry, fuselageMaterial);
98
  fuselage.rotation.x = Math.PI / 2;
99
  group.add(fuselage);
100
 
101
  // ์ฃผ์ต
102
+ const wingGeometry = new THREE.BoxGeometry(16, 0.3, 4);
103
  const wingMaterial = new THREE.MeshLambertMaterial({ color: 0x404040 });
104
  const wings = new THREE.Mesh(wingGeometry, wingMaterial);
105
+ wings.position.z = -1;
106
  group.add(wings);
107
 
108
  // ์ˆ˜์ง ์•ˆ์ •ํŒ
109
+ const tailGeometry = new THREE.BoxGeometry(0.3, 4, 3);
110
  const tailMaterial = new THREE.MeshLambertMaterial({ color: 0x404040 });
111
  const tail = new THREE.Mesh(tailGeometry, tailMaterial);
112
+ tail.position.z = -5;
113
+ tail.position.y = 1.5;
114
  group.add(tail);
115
 
116
  // ์ˆ˜ํ‰ ์•ˆ์ •ํŒ
117
+ const horizontalTailGeometry = new THREE.BoxGeometry(6, 0.2, 2);
118
  const horizontalTail = new THREE.Mesh(horizontalTailGeometry, tailMaterial);
119
+ horizontalTail.position.z = -5;
120
+ horizontalTail.position.y = 0.5;
121
  group.add(horizontalTail);
122
 
123
  this.mesh = group;
124
  this.mesh.position.copy(this.position);
125
+ this.mesh.scale.set(2, 2, 2);
126
  scene.add(this.mesh);
127
  this.isLoaded = true;
128
 
129
  console.log('Fallback ์ „ํˆฌ๊ธฐ ๋ชจ๋ธ ์ƒ์„ฑ ์™„๋ฃŒ');
130
  }
131
 
132
+ // ์›Œ์ฌ๋” ์Šคํƒ€์ผ ๋งˆ์šฐ์Šค ์ž…๋ ฅ ์ฒ˜๋ฆฌ
133
  updateMouseInput(deltaX, deltaY) {
134
+ // ๋งˆ์šฐ์Šค X = ๋กค (์ขŒ์šฐ ๊ธฐ์šธ๊ธฐ)
135
+ // ๋งˆ์šฐ์Šค Y = ํ”ผ์น˜ (๊ธฐ์ˆ˜ ์ƒํ•˜)
136
+ this.mouseRoll += deltaX * GAME_CONSTANTS.MOUSE_SENSITIVITY;
137
+ this.mousePitch += deltaY * GAME_CONSTANTS.MOUSE_SENSITIVITY;
 
 
 
138
 
139
+ // ์ž…๋ ฅ ์ œํ•œ (ยฑ1)
140
+ this.mouseRoll = Math.max(-1, Math.min(1, this.mouseRoll));
141
+ this.mousePitch = Math.max(-1, Math.min(1, this.mousePitch));
142
  }
143
 
144
+ // ํ‚ค๋ณด๋“œ ์ž…๋ ฅ ์ฒ˜๋ฆฌ
145
  updateControls(keys, deltaTime) {
146
+ // W/S: ์Šค๋กœํ‹€ ์กฐ์ ˆ
147
+ if (keys.w) {
148
+ this.throttle = Math.min(1.0, this.throttle + deltaTime * 0.8);
149
+ }
150
+ if (keys.s) {
151
+ this.throttle = Math.max(0.1, this.throttle - deltaTime * 0.8);
152
+ }
153
+
154
+ // A/D: ๋Ÿฌ๋” (์š” ํšŒ์ „)
155
+ this.yawInput = 0;
156
+ if (keys.a) this.yawInput = -0.5;
157
+ if (keys.d) this.yawInput = 0.5;
158
+
159
+ // ํ˜„์žฌ ์ž…๋ ฅ์„ ์กฐ์ข… ์ž…๋ ฅ์œผ๋กœ ์„ค์ •
160
+ this.pitchInput = this.mousePitch;
161
+ this.rollInput = this.mouseRoll;
162
+
163
+ // ๋ชฉํ‘œ ํšŒ์ „๊ฐ ๊ณ„์‚ฐ (๋ถ€๋“œ๋Ÿฌ์šด ์ œ์–ด)
164
+ const maxPitchAngle = Math.PI / 6; // 30๋„
165
+ const maxRollAngle = Math.PI / 4; // 45๋„
166
+
167
+ this.targetPitch = this.pitchInput * maxPitchAngle;
168
+ this.targetRoll = this.rollInput * maxRollAngle;
169
+ this.targetYaw += this.yawInput * deltaTime * 1.5;
170
+
171
+ // ๋งˆ์šฐ์Šค ์ž…๋ ฅ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ๊ฐ์‡ 
172
+ this.mouseRoll *= 0.95;
173
+ this.mousePitch *= 0.95;
174
  }
175
 
176
+ // ๋ฌผ๋ฆฌ ์‹œ๋ฎฌ๋ ˆ์ด์…˜
177
  updatePhysics(deltaTime) {
178
  if (!this.mesh) return;
179
 
180
+ // ํšŒ์ „ ๋ถ€๋“œ๋Ÿฝ๊ฒŒ ๋ณด๊ฐ„
181
+ const rotationSpeed = deltaTime * 3.0;
182
+ this.rotation.x = THREE.MathUtils.lerp(this.rotation.x, this.targetPitch, rotationSpeed);
183
+ this.rotation.y = THREE.MathUtils.lerp(this.rotation.y, this.targetYaw, rotationSpeed);
184
+ this.rotation.z = THREE.MathUtils.lerp(this.rotation.z, this.targetRoll, rotationSpeed);
185
 
186
+ // ์Šค๋กœํ‹€์— ๋”ฐ๋ฅธ ์†๋„ ๊ณ„์‚ฐ
187
+ const minSpeed = 100; // ์ตœ์†Œ ์†๋„
188
+ const maxSpeed = 300; // ์ตœ๋Œ€ ์†๋„
189
+ const targetSpeed = minSpeed + (maxSpeed - minSpeed) * this.throttle;
190
 
191
+ // ์†๋„ ๋ถ€๋“œ๋Ÿฝ๊ฒŒ ๋ณ€ํ™”
192
+ this.speed = THREE.MathUtils.lerp(this.speed, targetSpeed, deltaTime * 2);
 
193
 
194
+ // ์ „์ง„ ๋ฐฉํ–ฅ ๋ฒกํ„ฐ ๊ณ„์‚ฐ
195
  const forward = new THREE.Vector3(0, 0, 1);
196
  forward.applyEuler(this.rotation);
197
 
198
+ // ์†๋„ ๋ฒกํ„ฐ ์„ค์ • (ํ•ญ์ƒ ์ „์ง„)
199
+ this.velocity = forward.multiplyScalar(this.speed);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
200
 
201
  // ์œ„์น˜ ์—…๋ฐ์ดํŠธ
202
  this.position.add(this.velocity.clone().multiplyScalar(deltaTime));
203
 
204
+ // ๊ณ ๋„ ์ œํ•œ
205
  if (this.position.y < GAME_CONSTANTS.MIN_ALTITUDE) {
206
  this.position.y = GAME_CONSTANTS.MIN_ALTITUDE;
207
+ if (this.velocity.y < 0) this.velocity.y = 0;
 
208
  }
209
 
210
  if (this.position.y > GAME_CONSTANTS.MAX_ALTITUDE) {
211
  this.position.y = GAME_CONSTANTS.MAX_ALTITUDE;
212
+ if (this.velocity.y > 0) this.velocity.y = 0;
213
  }
214
 
215
+ // ๋งต ๊ฒฝ๊ณ„ ์ˆœํ™˜ (๋ฌดํ•œ ๋งต)
216
  const mapLimit = GAME_CONSTANTS.MAP_SIZE / 2;
217
  if (this.position.x > mapLimit) this.position.x = -mapLimit;
218
  if (this.position.x < -mapLimit) this.position.x = mapLimit;
 
223
  this.mesh.position.copy(this.position);
224
  this.mesh.rotation.copy(this.rotation);
225
 
226
+ // ํ˜„์žฌ ๊ฐ’ ์—…๋ฐ์ดํŠธ
 
227
  this.altitude = this.position.y;
228
 
229
+ // G-Force ๊ณ„์‚ฐ (ํšŒ์ „ ๊ฐ•๋„์— ๋”ฐ๋ผ)
230
+ const rotationIntensity = Math.abs(this.targetPitch) + Math.abs(this.targetRoll);
231
+ this.gForce = 1.0 + rotationIntensity * 3;
 
 
 
232
  }
233
 
234
+ // ๋ฐœ์‚ฌ ์‹œ์Šคํ…œ
235
  shoot(scene) {
236
  const currentTime = Date.now();
237
+ if (currentTime - this.lastShootTime < 100 || this.ammo <= 0) return;
238
 
239
  this.lastShootTime = currentTime;
240
  this.ammo--;
241
 
242
+ // ์ด์•Œ ์ƒ์„ฑ
243
+ const bulletGeometry = new THREE.SphereGeometry(0.2);
244
  const bulletMaterial = new THREE.MeshBasicMaterial({
245
  color: 0xffff00,
246
  emissive: 0xffff00,
247
+ emissiveIntensity: 0.7
248
  });
249
  const bullet = new THREE.Mesh(bulletGeometry, bulletMaterial);
250
 
251
+ // ์ด๊ตฌ ์œ„์น˜ (์ „ํˆฌ๊ธฐ ์•ž์ชฝ)
252
+ const muzzleOffset = new THREE.Vector3(0, 0, 8);
253
  muzzleOffset.applyEuler(this.rotation);
254
  bullet.position.copy(this.position).add(muzzleOffset);
255
 
256
+ // ์ด์•Œ ์†๋„ (์ „ํˆฌ๊ธฐ ์†๋„ + ์ด์•Œ ์†๋„)
257
+ const bulletSpeed = 1000;
258
  const direction = new THREE.Vector3(0, 0, 1);
259
  direction.applyEuler(this.rotation);
260
  bullet.velocity = direction.multiplyScalar(bulletSpeed).add(this.velocity);
261
 
 
 
 
262
  scene.add(bullet);
263
  this.bullets.push(bullet);
264
 
265
  // ๋ฐœ์‚ฌ์Œ
266
  try {
267
  const audio = new Audio('sounds/gunfire.ogg');
268
+ audio.volume = 0.3;
269
  audio.play();
270
  } catch (e) {
271
  console.log('Sound file not found');
272
  }
273
  }
274
 
275
+ // ์ด์•Œ ์—…๋ฐ์ดํŠธ
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
276
  updateBullets(scene, deltaTime) {
277
  for (let i = this.bullets.length - 1; i >= 0; i--) {
278
  const bullet = this.bullets[i];
279
  bullet.position.add(bullet.velocity.clone().multiplyScalar(deltaTime));
280
 
281
  // ์ด์•Œ ์ œ๊ฑฐ ์กฐ๊ฑด
282
+ if (bullet.position.distanceTo(this.position) > 8000 ||
283
  bullet.position.y < 0 ||
284
+ bullet.position.y > GAME_CONSTANTS.MAX_ALTITUDE + 500) {
285
  scene.remove(bullet);
286
  this.bullets.splice(i, 1);
287
  }
288
  }
289
  }
290
 
291
+ // ํ”ผํ•ด ์‹œ์Šคํ…œ
292
  takeDamage(damage) {
293
  this.health -= damage;
294
  return this.health <= 0;
295
  }
296
 
297
+ // ์นด๋ฉ”๋ผ ์œ„์น˜ ๊ณ„์‚ฐ (์ „ํˆฌ๊ธฐ ๋’ค์ชฝ์—์„œ ์ถ”์ )
298
  getCameraPosition() {
 
 
 
 
299
  // ์ „ํˆฌ๊ธฐ ๋’ค์ชฝ ๋ฐฉํ–ฅ ๊ณ„์‚ฐ
300
+ const backward = new THREE.Vector3(0, 0, -1);
301
+ backward.applyEuler(this.rotation);
302
 
303
+ // ์นด๋ฉ”๋ผ ์œ„์น˜ = ์ „ํˆฌ๊ธฐ ์œ„์น˜ + ๋’ค์ชฝ ๋ฐฉํ–ฅ * ๊ฑฐ๋ฆฌ + ๋†’์ด
304
+ const cameraPos = this.position.clone()
305
+ .add(backward.multiplyScalar(this.cameraDistance))
306
+ .add(new THREE.Vector3(0, this.cameraHeight, 0));
307
 
308
+ return cameraPos;
 
 
 
 
 
 
 
309
  }
310
 
311
+ // ์นด๋ฉ”๋ผ ํƒ€๊ฒŸ (์ „ํˆฌ๊ธฐ๋ฅผ ๋ฐ”๋ผ๋ด„)
312
  getCameraTarget() {
313
+ return this.position.clone().add(new THREE.Vector3(0, 0, 0));
 
 
 
314
  }
315
  }
316
 
317
+ // ์  ์ „ํˆฌ๊ธฐ ํด๋ž˜์Šค (๊ฐ„๋‹จํ•œ ๋ฒ„์ „)
318
  class EnemyFighter {
319
  constructor(scene, position) {
320
  this.mesh = null;
321
  this.isLoaded = false;
322
  this.scene = scene;
323
  this.position = position.clone();
324
+ this.velocity = new THREE.Vector3(0, 0, 120);
325
  this.rotation = new THREE.Euler(0, 0, 0);
326
+ this.health = 100;
327
+ this.speed = 120;
328
  this.bullets = [];
329
  this.lastShootTime = 0;
330
 
331
+ // AI ์ƒํƒœ
332
  this.aiState = 'patrol';
333
  this.targetPosition = position.clone();
334
  this.patrolCenter = position.clone();
335
+ this.patrolRadius = 2000;
336
  this.lastStateChange = 0;
 
 
337
  }
338
 
339
  async initialize(loader) {
 
341
  const result = await loader.loadAsync('models/mig-29.glb');
342
  this.mesh = result.scene;
343
  this.mesh.position.copy(this.position);
344
+ this.mesh.scale.set(1.5, 1.5, 1.5);
345
 
346
  this.mesh.traverse((child) => {
347
  if (child.isMesh) {
 
363
  createFallbackModel() {
364
  const group = new THREE.Group();
365
 
366
+ const fuselageGeometry = new THREE.CylinderGeometry(0.6, 1.0, 8, 8);
367
+ const fuselageMaterial = new THREE.MeshLambertMaterial({ color: 0x800000 });
368
  const fuselage = new THREE.Mesh(fuselageGeometry, fuselageMaterial);
369
  fuselage.rotation.x = Math.PI / 2;
370
  group.add(fuselage);
371
 
372
+ const wingGeometry = new THREE.BoxGeometry(12, 0.3, 3);
373
+ const wingMaterial = new THREE.MeshLambertMaterial({ color: 0x600000 });
374
  const wings = new THREE.Mesh(wingGeometry, wingMaterial);
375
+ wings.position.z = -0.5;
376
  group.add(wings);
377
 
378
  this.mesh = group;
379
  this.mesh.position.copy(this.position);
380
+ this.mesh.scale.set(1.5, 1.5, 1.5);
381
  this.scene.add(this.mesh);
382
  this.isLoaded = true;
383
 
 
390
  const currentTime = Date.now();
391
  const distanceToPlayer = this.position.distanceTo(playerPosition);
392
 
393
+ // ๊ฐ„๋‹จํ•œ AI: ํ”Œ๋ ˆ์ด์–ด ์ถ”์ 
394
+ if (distanceToPlayer < 5000) {
395
+ const direction = new THREE.Vector3()
396
+ .subVectors(playerPosition, this.position)
397
+ .normalize();
398
+
399
+ this.velocity = direction.multiplyScalar(this.speed);
400
+ this.rotation.y = Math.atan2(direction.x, direction.z);
401
+
402
+ // ๊ณต๊ฒฉ
403
+ if (distanceToPlayer < 2000 && currentTime - this.lastShootTime > 1500) {
404
+ this.shoot();
405
  }
406
+ } else {
407
+ // ์ˆœ์ฐฐ
408
+ if (this.position.distanceTo(this.targetPosition) < 300) {
409
+ const angle = Math.random() * Math.PI * 2;
410
+ this.targetPosition = this.patrolCenter.clone().add(
411
+ new THREE.Vector3(
412
+ Math.cos(angle) * this.patrolRadius,
413
+ (Math.random() - 0.5) * 1000,
414
+ Math.sin(angle) * this.patrolRadius
415
+ )
416
+ );
417
+ }
418
+
419
+ const direction = new THREE.Vector3()
420
+ .subVectors(this.targetPosition, this.position)
421
+ .normalize();
422
+
423
+ this.velocity = direction.multiplyScalar(this.speed * 0.7);
424
+ this.rotation.y = Math.atan2(direction.x, direction.z);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
425
  }
426
 
427
+ // ์œ„์น˜ ์—…๋ฐ์ดํŠธ
 
 
 
 
 
 
 
 
 
428
  this.position.add(this.velocity.clone().multiplyScalar(deltaTime));
429
 
430
  // ๊ณ ๋„ ์ œํ•œ
431
  if (this.position.y < GAME_CONSTANTS.MIN_ALTITUDE) {
432
  this.position.y = GAME_CONSTANTS.MIN_ALTITUDE;
 
433
  }
434
  if (this.position.y > GAME_CONSTANTS.MAX_ALTITUDE) {
435
  this.position.y = GAME_CONSTANTS.MAX_ALTITUDE;
 
436
  }
437
 
438
  // ๋งต ๊ฒฝ๊ณ„ ์ˆœํ™˜
 
445
  // ๋ฉ”์‹œ ์—…๋ฐ์ดํŠธ
446
  this.mesh.position.copy(this.position);
447
  this.mesh.rotation.copy(this.rotation);
448
+
449
+ // ์ด์•Œ ์—…๋ฐ์ดํŠธ
450
+ this.updateBullets(deltaTime);
451
  }
452
 
453
  shoot() {
454
  this.lastShootTime = Date.now();
455
 
456
+ const bulletGeometry = new THREE.SphereGeometry(0.15);
457
  const bulletMaterial = new THREE.MeshBasicMaterial({
458
  color: 0xff0000,
459
  emissive: 0xff0000,
460
+ emissiveIntensity: 0.5
461
  });
462
  const bullet = new THREE.Mesh(bulletGeometry, bulletMaterial);
463
 
464
+ const muzzleOffset = new THREE.Vector3(0, 0, 6);
465
  muzzleOffset.applyEuler(this.rotation);
466
  bullet.position.copy(this.position).add(muzzleOffset);
467
 
468
  const direction = new THREE.Vector3(0, 0, 1);
469
  direction.applyEuler(this.rotation);
470
+ bullet.velocity = direction.multiplyScalar(800);
471
 
472
  this.scene.add(bullet);
473
  this.bullets.push(bullet);
 
478
  const bullet = this.bullets[i];
479
  bullet.position.add(bullet.velocity.clone().multiplyScalar(deltaTime));
480
 
481
+ if (bullet.position.distanceTo(this.position) > 5000 ||
482
  bullet.position.y < 0) {
483
  this.scene.remove(bullet);
484
  this.bullets.splice(i, 1);
 
505
  class Game {
506
  constructor() {
507
  this.scene = new THREE.Scene();
508
+ this.camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 50000);
509
  this.renderer = new THREE.WebGLRenderer({ antialias: true });
510
  this.renderer.setSize(window.innerWidth, window.innerHeight);
511
  this.renderer.shadowMap.enabled = true;
 
537
  // ๊ธฐ๋ณธ ์ดˆ๊ธฐํ™”
538
  this.setupScene();
539
  this.setupEventListeners();
540
+ this.preloadGame();
541
  }
542
 
543
  async preloadGame() {
 
551
  throw new Error('์ „ํˆฌ๊ธฐ ๋กœ๋”ฉ ์‹คํŒจ');
552
  }
553
 
554
+ // ์ดˆ๊ธฐ ์นด๋ฉ”๋ผ ์œ„์น˜ ์„ค์ •
555
+ const initialCameraPos = this.fighter.getCameraPosition();
556
  this.camera.position.copy(initialCameraPos);
557
  this.camera.lookAt(this.fighter.position);
558
 
559
+ // ์  ์ƒ์„ฑ
560
  await this.preloadEnemies();
561
 
562
  this.isLoaded = true;
 
565
  // ๋กœ๋”ฉ ํ™”๋ฉด ์ˆจ๊ธฐ๊ธฐ
566
  document.getElementById('loading').style.display = 'none';
567
 
568
+ // ๋ Œ๋”๋ง ์‹œ์ž‘
569
  this.animate();
570
 
571
  } catch (error) {
 
578
  async preloadEnemies() {
579
  for (let i = 0; i < GAME_CONSTANTS.ENEMY_COUNT; i++) {
580
  const angle = (i / GAME_CONSTANTS.ENEMY_COUNT) * Math.PI * 2;
581
+ const distance = 6000 + Math.random() * 3000;
582
 
583
  const position = new THREE.Vector3(
584
  Math.cos(angle) * distance,
585
+ 2500 + Math.random() * 2000,
586
  Math.sin(angle) * distance
587
  );
588
 
 
593
  }
594
 
595
  setupScene() {
596
+ // ํ•˜๋Š˜ ๋ฐฐ๊ฒฝ
597
  this.scene.background = new THREE.Color(0x87CEEB);
598
+ this.scene.fog = new THREE.Fog(0x87CEEB, 1000, 30000);
599
 
600
+ // ์กฐ๋ช…
601
+ const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
602
  this.scene.add(ambientLight);
603
 
604
+ const directionalLight = new THREE.DirectionalLight(0xffffff, 1.0);
605
+ directionalLight.position.set(8000, 10000, 8000);
606
  directionalLight.castShadow = true;
607
+ directionalLight.shadow.mapSize.width = 2048;
608
+ directionalLight.shadow.mapSize.height = 2048;
609
  directionalLight.shadow.camera.near = 0.5;
610
+ directionalLight.shadow.camera.far = 20000;
611
+ directionalLight.shadow.camera.left = -10000;
612
+ directionalLight.shadow.camera.right = 10000;
613
+ directionalLight.shadow.camera.top = 10000;
614
+ directionalLight.shadow.camera.bottom = -10000;
615
  this.scene.add(directionalLight);
616
 
617
+ // ์ง€ํ˜•
618
+ const groundGeometry = new THREE.PlaneGeometry(GAME_CONSTANTS.MAP_SIZE, GAME_CONSTANTS.MAP_SIZE);
619
  const groundMaterial = new THREE.MeshLambertMaterial({
620
  color: 0x8FBC8F,
621
  transparent: true,
622
+ opacity: 0.8
623
  });
624
  const ground = new THREE.Mesh(groundGeometry, groundMaterial);
625
  ground.rotation.x = -Math.PI / 2;
626
  ground.receiveShadow = true;
627
  this.scene.add(ground);
628
 
629
+ // ๊ตฌ๋ฆ„ ์ถ”๊ฐ€
630
  this.addClouds();
631
  }
632
 
633
  addClouds() {
634
+ const cloudGeometry = new THREE.SphereGeometry(100, 8, 6);
635
  const cloudMaterial = new THREE.MeshLambertMaterial({
636
  color: 0xffffff,
637
  transparent: true,
638
+ opacity: 0.5
639
  });
640
 
641
+ for (let i = 0; i < 100; i++) {
642
  const cloud = new THREE.Mesh(cloudGeometry, cloudMaterial);
643
  cloud.position.set(
644
+ (Math.random() - 0.5) * GAME_CONSTANTS.MAP_SIZE,
645
+ Math.random() * 4000 + 1000,
646
+ (Math.random() - 0.5) * GAME_CONSTANTS.MAP_SIZE
647
  );
648
  cloud.scale.set(
649
+ Math.random() * 3 + 1,
650
+ Math.random() * 2 + 0.5,
651
+ Math.random() * 3 + 1
652
  );
653
  this.scene.add(cloud);
654
  }
 
676
  }
677
  });
678
 
679
+ // ๋งˆ์šฐ์Šค ์ด๋ฒคํŠธ
680
  document.addEventListener('mousemove', (event) => {
681
  if (!document.pointerLockElement || !this.isStarted || this.isGameOver) return;
682
 
 
711
  }
712
 
713
  this.isStarted = true;
 
 
714
  this.startBGM();
 
 
715
  this.startGameTimer();
716
 
717
  console.log('๊ฒŒ์ž„ ์‹œ์ž‘!');
 
722
 
723
  try {
724
  this.bgm = new Audio('sounds/main_bgm.ogg');
725
+ this.bgm.volume = 0.5;
726
  this.bgm.loop = true;
727
  this.bgm.play();
728
  this.bgmPlaying = true;
 
738
  this.gameTime--;
739
 
740
  if (this.gameTime <= 0) {
741
+ this.endGame(true);
742
  }
743
  }
744
  }, 1000);
 
746
 
747
  updateUI() {
748
  if (this.fighter.isLoaded) {
 
749
  const speedKnots = Math.round(this.fighter.speed * 1.94384);
750
  const altitudeFeet = Math.round(this.fighter.altitude * 3.28084);
751
 
 
754
  document.getElementById('health').style.width = `${this.fighter.health}%`;
755
  document.getElementById('ammoDisplay').textContent = `AMMO: ${this.fighter.ammo}`;
756
 
 
757
  const gameStats = document.getElementById('gameStats');
758
  if (gameStats) {
759
  gameStats.innerHTML = `
 
773
  const radar = document.getElementById('radar');
774
  if (!radar) return;
775
 
 
776
  const oldDots = radar.getElementsByClassName('enemy-dot');
777
  while (oldDots[0]) {
778
  oldDots[0].remove();
779
  }
780
 
 
781
  const radarCenter = { x: 100, y: 100 };
782
+ const radarRange = 10000;
783
 
784
  this.enemies.forEach(enemy => {
785
  if (!enemy.mesh || !enemy.isLoaded) return;
 
812
  if (!enemy.mesh || !enemy.isLoaded) continue;
813
 
814
  const distance = bullet.position.distanceTo(enemy.position);
815
+ if (distance < 25) {
 
816
  this.scene.remove(bullet);
817
  this.fighter.bullets.splice(i, 1);
818
 
819
+ if (enemy.takeDamage(30)) {
820
  enemy.destroy();
821
  this.enemies.splice(j, 1);
822
+ this.score += 100;
823
  }
824
  break;
825
  }
 
830
  this.enemies.forEach(enemy => {
831
  enemy.bullets.forEach((bullet, index) => {
832
  const distance = bullet.position.distanceTo(this.fighter.position);
833
+ if (distance < 30) {
 
834
  this.scene.remove(bullet);
835
  enemy.bullets.splice(index, 1);
836
 
837
+ if (this.fighter.takeDamage(20)) {
838
  this.endGame(false);
839
  }
840
  }
 
876
  }
877
  }
878
 
879
+ // ์นด๋ฉ”๋ผ ์—…๋ฐ์ดํŠธ (๋ถ€๋“œ๋Ÿฝ๊ฒŒ ๋”ฐ๋ผ๊ฐ€๊ธฐ)
880
+ const targetCameraPos = this.fighter.getCameraPosition();
881
+ const targetCameraTarget = this.fighter.getCameraTarget();
882
 
883
+ this.camera.position.lerp(targetCameraPos, this.fighter.cameraLag);
884
+ this.camera.lookAt(targetCameraTarget);
 
885
  }
886
 
887
  this.renderer.render(this.scene, this.camera);
 
890
  endGame(victory = false) {
891
  this.isGameOver = true;
892
 
 
893
  if (this.bgm) {
894
  this.bgm.pause();
895
  this.bgm = null;
 
911
  <div style="color: #0f0; font-size: 24px; margin: 20px 0;">
912
  Final Score: ${this.score}<br>
913
  Enemies Destroyed: ${GAME_CONSTANTS.ENEMY_COUNT - this.enemies.length}<br>
914
+ Mission Time: ${GAME_CONSTANTS.MISSION_DURATION - this.gameTime}s
 
 
915
  </div>
916
  <button class="start-button" onclick="location.reload()">
917
  New Mission
 
925
  // ๊ฒŒ์ž„ ์ธ์Šคํ„ด์Šค
926
  let gameInstance = null;
927
 
928
+ // ์ „์—ญ ํ•จ์ˆ˜
929
  window.startGame = function() {
930
  document.getElementById('startScreen').style.display = 'none';
931
  document.body.requestPointerLock();
 
937
  }
938
  };
939
 
940
+ // ๊ฒŒ์ž„ ์ดˆ๊ธฐํ™”
941
  document.addEventListener('DOMContentLoaded', () => {
942
  console.log('์ „ํˆฌ๊ธฐ ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ ์ดˆ๊ธฐํ™” ์ค‘...');
943
  gameInstance = new Game();