gunship999 commited on
Commit
d8b5041
β€’
1 Parent(s): a742c58

Update game.js

Browse files
Files changed (1) hide show
  1. game.js +371 -134
game.js CHANGED
@@ -5,11 +5,11 @@ import { PointerLockControls } from 'three/addons/controls/PointerLockControls.j
5
  // κ²Œμž„ μƒμˆ˜
6
  const GAME_DURATION = 180;
7
  const MAP_SIZE = 2000;
8
- const HELICOPTER_HEIGHT = 100; // 헬리μ½₯ν„° 고도 상ν–₯
9
- const ENEMY_GROUND_HEIGHT = 5; // 적 지상 높이
10
  const ENEMY_SCALE = 3;
11
  const MAX_HEALTH = 1000;
12
- const ENEMY_MOVE_SPEED = 0.1; // 적 이동 속도
13
  const ENEMY_MODELS = [
14
  './models/enemy1.glb',
15
  './models/enemy12.glb',
@@ -31,6 +31,7 @@ let playerHealth = MAX_HEALTH;
31
  let ammo = 30;
32
  let currentStage = 1;
33
  let isGameOver = false;
 
34
 
35
  // μ‚¬μš΄λ“œ ν’€ 클래슀
36
  class SoundPool {
@@ -67,7 +68,7 @@ class SoundPool {
67
  const sounds = {
68
  bgm: new Audio('Music.wav'),
69
  gunshot: new SoundPool('gun.wav', 20),
70
- explosion: new SoundPool('explosion.wav', 10) // 폭발 μ‚¬μš΄λ“œ μΆ”κ°€
71
  };
72
  sounds.bgm.loop = true;
73
 
@@ -79,43 +80,87 @@ const moveState = {
79
  right: false
80
  };
81
 
82
- function init() {
83
- console.log('Game initialized');
84
- console.log('Available enemy models:', ENEMY_MODELS);
85
 
 
86
  scene = new THREE.Scene();
87
  scene.background = new THREE.Color(0x87ceeb);
88
  scene.fog = new THREE.Fog(0x87ceeb, 0, 1500);
89
 
 
90
  camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 3000);
91
  camera.position.set(0, HELICOPTER_HEIGHT, 0);
92
 
93
- renderer = new THREE.WebGLRenderer({ antialias: true });
 
 
 
 
94
  renderer.setSize(window.innerWidth, window.innerHeight);
95
  renderer.shadowMap.enabled = true;
 
96
  document.body.appendChild(renderer.domElement);
97
 
98
- const ambientLight = new THREE.AmbientLight(0xffffff, 1.0);
 
99
  scene.add(ambientLight);
100
 
101
- const dirLight = new THREE.DirectionalLight(0xffffff, 1.0);
102
  dirLight.position.set(100, 100, 50);
103
  dirLight.castShadow = true;
 
 
104
  scene.add(dirLight);
105
 
 
106
  controls = new PointerLockControls(camera, document.body);
107
 
 
108
  document.addEventListener('click', onClick);
109
  document.addEventListener('keydown', onKeyDown);
110
  document.addEventListener('keyup', onKeyUp);
111
  window.addEventListener('resize', onWindowResize);
112
 
113
- createTerrain();
114
- console.log('Starting to load enemies...');
115
- loadEnemies();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
116
  }
117
 
118
- // 폭발 효과 생성 ν•¨μˆ˜
119
  function createExplosion(position) {
120
  const particleCount = 30;
121
  const particles = [];
@@ -141,8 +186,7 @@ function createExplosion(position) {
141
  scene.add(particle);
142
  }
143
 
144
- // 폭발 μ• λ‹ˆλ©”μ΄μ…˜
145
- const animateExplosion = () => {
146
  particles.forEach((particle, i) => {
147
  particle.position.add(particle.velocity);
148
  particle.material.opacity -= 0.02;
@@ -156,111 +200,264 @@ function createExplosion(position) {
156
  if (particles.length > 0) {
157
  requestAnimationFrame(animateExplosion);
158
  }
159
- };
160
-
161
  animateExplosion();
162
  sounds.explosion.play();
163
  }
164
 
165
- function createTerrain() {
166
- const geometry = new THREE.PlaneGeometry(MAP_SIZE, MAP_SIZE, 200, 200);
167
- const material = new THREE.MeshStandardMaterial({
168
- color: 0xD2B48C,
169
- roughness: 0.8,
170
- metalness: 0.2
171
  });
172
 
173
- const vertices = geometry.attributes.position.array;
174
- for (let i = 0; i < vertices.length; i += 3) {
175
- vertices[i + 2] = Math.sin(vertices[i] * 0.01) * Math.cos(vertices[i + 1] * 0.01) * 20;
 
 
 
 
 
 
 
 
 
 
 
 
176
  }
 
177
 
178
- geometry.attributes.position.needsUpdate = true;
179
- geometry.computeVertexNormals();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
180
 
181
- const terrain = new THREE.Mesh(geometry, material);
182
- terrain.rotation.x = -Math.PI / 2;
183
- terrain.receiveShadow = true;
184
- scene.add(terrain);
 
 
 
 
 
185
 
186
- addObstacles();
 
 
 
 
 
 
 
187
  }
188
 
189
- function createTemporaryEnemy(position) {
190
- const geometry = new THREE.BoxGeometry(5, 5, 5);
191
- const material = new THREE.MeshPhongMaterial({ color: 0xff0000 });
192
- const cube = new THREE.Mesh(geometry, material);
193
- cube.position.copy(position);
194
- return cube;
 
195
  }
196
 
197
- function loadEnemies() {
198
- const loader = new GLTFLoader();
199
- const enemyCount = 3 + currentStage;
200
-
201
- for (let i = 0; i < enemyCount; i++) {
202
- const angle = (i / enemyCount) * Math.PI * 2;
203
- const radius = 200;
204
- const position = new THREE.Vector3(
205
- Math.cos(angle) * radius,
206
- ENEMY_GROUND_HEIGHT,
207
- Math.sin(angle) * radius
208
- );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
209
 
210
- const tempEnemy = createTemporaryEnemy(position);
211
- scene.add(tempEnemy);
 
 
 
 
 
 
 
 
 
 
 
 
 
212
 
213
- const modelPath = ENEMY_MODELS[i % ENEMY_MODELS.length];
214
- console.log('Loading enemy model:', modelPath);
215
-
216
- loader.load(
217
- modelPath,
218
- (gltf) => {
219
- console.log('Successfully loaded enemy model:', modelPath);
220
- const enemy = gltf.scene;
221
- enemy.scale.set(ENEMY_SCALE, ENEMY_SCALE, ENEMY_SCALE);
222
- enemy.position.copy(position);
223
-
224
- enemy.rotation.y = Math.PI;
225
-
226
- enemy.traverse((node) => {
227
- if (node.isMesh) {
228
- node.castShadow = true;
229
- node.receiveShadow = true;
230
- node.material.metalness = 0.2;
231
- node.material.roughness = 0.8;
232
- }
233
- });
234
 
235
- scene.remove(tempEnemy);
236
- scene.add(enemy);
 
 
 
 
 
 
 
 
 
 
 
 
237
 
238
- const index = enemies.findIndex(e => e.model === tempEnemy);
239
- if (index !== -1) {
240
- enemies[index].model = enemy;
241
- enemies[index].muzzleFlash = createMuzzleFlash();
242
- enemy.add(enemies[index].muzzleFlash);
 
 
 
 
 
 
 
 
 
 
 
 
 
243
  }
244
- },
245
- (xhr) => {
246
- console.log(`${modelPath}: ${(xhr.loaded / xhr.total * 100)}% loaded`);
247
- },
248
- (error) => {
249
- console.error('Error loading enemy model:', modelPath, error);
250
  }
251
- );
252
-
253
- enemies.push({
254
- model: tempEnemy,
255
- health: 100,
256
- speed: ENEMY_MOVE_SPEED,
257
- lastAttackTime: 0
258
  });
 
 
 
 
 
259
  }
260
  }
261
 
262
- // λ‚˜λ¨Έμ§€ ν•¨μˆ˜λ“€μ€ 이전과 λ™μΌν•˜κ²Œ μœ μ§€λ˜λ©°, updateEnemies와 updateBullets ν•¨μˆ˜λ§Œ μˆ˜μ •λ©λ‹ˆλ‹€.
263
-
264
  function updateEnemies() {
265
  const currentTime = Date.now();
266
 
@@ -275,7 +472,6 @@ function updateEnemies() {
275
  newPosition.y = ENEMY_GROUND_HEIGHT;
276
  enemy.model.position.copy(newPosition);
277
 
278
- // 적이 ν”Œλ ˆμ΄μ–΄λ₯Ό 바라보도둝 μ„€μ •
279
  enemy.model.lookAt(new THREE.Vector3(
280
  camera.position.x,
281
  enemy.model.position.y,
@@ -285,53 +481,94 @@ function updateEnemies() {
285
  const distanceToPlayer = enemy.model.position.distanceTo(camera.position);
286
  if (distanceToPlayer < ENEMY_CONFIG.ATTACK_RANGE &&
287
  currentTime - enemy.lastAttackTime > ENEMY_CONFIG.ATTACK_INTERVAL) {
288
-
289
- if (enemy.muzzleFlash) {
290
- enemy.muzzleFlash.material.opacity = 1;
291
- enemy.muzzleFlash.visible = true;
292
- setTimeout(() => {
293
- enemy.muzzleFlash.visible = false;
294
- enemy.muzzleFlash.material.opacity = 0;
295
- }, 100);
296
- }
297
-
298
  enemyBullets.push(createEnemyBullet(enemy));
299
  enemy.lastAttackTime = currentTime;
300
  }
301
  });
302
  }
303
 
304
- function updateBullets() {
305
- for (let i = bullets.length - 1; i >= 0; i--) {
306
- bullets[i].position.add(bullets[i].velocity);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
307
 
308
- enemies.forEach(enemy => {
309
- if (bullets[i].position.distanceTo(enemy.model.position) < 5) {
310
- scene.remove(bullets[i]);
311
- bullets.splice(i, 1);
312
- enemy.health -= 25;
313
-
314
- // 피격 효과
315
- createExplosion(enemy.model.position.clone());
316
-
317
- if (enemy.health <= 0) {
318
- // 파괴 효과
319
- createExplosion(enemy.model.position.clone());
320
- createExplosion(enemy.model.position.clone().add(new THREE.Vector3(2, 0, 2)));
321
- createExplosion(enemy.model.position.clone().add(new THREE.Vector3(-2, 0, -2)));
322
- scene.remove(enemy.model);
323
- enemies = enemies.filter(e => e !== enemy);
324
- }
325
  }
326
- });
 
327
 
328
- if (bullets[i] && bullets[i].position.distanceTo(camera.position) > 1000) {
329
- scene.remove(bullets[i]);
330
- bullets.splice(i, 1);
331
  }
332
  }
333
  }
334
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
335
  // κ²Œμž„ μ‹œμž‘
336
- init();
337
- gameLoop();
 
 
 
5
  // κ²Œμž„ μƒμˆ˜
6
  const GAME_DURATION = 180;
7
  const MAP_SIZE = 2000;
8
+ const HELICOPTER_HEIGHT = 100; // 높이 증가
9
+ const ENEMY_GROUND_HEIGHT = 5;
10
  const ENEMY_SCALE = 3;
11
  const MAX_HEALTH = 1000;
12
+ const ENEMY_MOVE_SPEED = 0.1; // 속도 κ°μ†Œ
13
  const ENEMY_MODELS = [
14
  './models/enemy1.glb',
15
  './models/enemy12.glb',
 
31
  let ammo = 30;
32
  let currentStage = 1;
33
  let isGameOver = false;
34
+ let lastTime = performance.now();
35
 
36
  // μ‚¬μš΄λ“œ ν’€ 클래슀
37
  class SoundPool {
 
68
  const sounds = {
69
  bgm: new Audio('Music.wav'),
70
  gunshot: new SoundPool('gun.wav', 20),
71
+ explosion: new SoundPool('explosion.wav', 10)
72
  };
73
  sounds.bgm.loop = true;
74
 
 
80
  right: false
81
  };
82
 
83
+ async function init() {
84
+ console.log('Initializing game...');
 
85
 
86
+ // Scene μ΄ˆκΈ°ν™”
87
  scene = new THREE.Scene();
88
  scene.background = new THREE.Color(0x87ceeb);
89
  scene.fog = new THREE.Fog(0x87ceeb, 0, 1500);
90
 
91
+ // Camera μ„€μ •
92
  camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 3000);
93
  camera.position.set(0, HELICOPTER_HEIGHT, 0);
94
 
95
+ // Renderer μ„€μ •
96
+ renderer = new THREE.WebGLRenderer({
97
+ antialias: true,
98
+ powerPreference: "high-performance"
99
+ });
100
  renderer.setSize(window.innerWidth, window.innerHeight);
101
  renderer.shadowMap.enabled = true;
102
+ renderer.shadowMap.type = THREE.PCFSoftShadowMap;
103
  document.body.appendChild(renderer.domElement);
104
 
105
+ // μ‘°λͺ… μ„€μ •
106
+ const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
107
  scene.add(ambientLight);
108
 
109
+ const dirLight = new THREE.DirectionalLight(0xffffff, 1);
110
  dirLight.position.set(100, 100, 50);
111
  dirLight.castShadow = true;
112
+ dirLight.shadow.mapSize.width = 2048;
113
+ dirLight.shadow.mapSize.height = 2048;
114
  scene.add(dirLight);
115
 
116
+ // Controls μ„€μ •
117
  controls = new PointerLockControls(camera, document.body);
118
 
119
+ // 이벀트 λ¦¬μŠ€λ„ˆ
120
  document.addEventListener('click', onClick);
121
  document.addEventListener('keydown', onKeyDown);
122
  document.addEventListener('keyup', onKeyUp);
123
  window.addEventListener('resize', onWindowResize);
124
 
125
+ // μ§€ν˜• 생성
126
+ await createTerrain();
127
+
128
+ // 적 λ‘œλ“œ
129
+ await loadEnemies();
130
+
131
+ // λ‘œλ”© ν™”λ©΄ 제거
132
+ document.getElementById('loading').style.display = 'none';
133
+
134
+ console.log('Initialization complete');
135
+ }
136
+
137
+ function createTerrain() {
138
+ return new Promise((resolve) => {
139
+ const geometry = new THREE.PlaneGeometry(MAP_SIZE, MAP_SIZE, 200, 200);
140
+ const material = new THREE.MeshStandardMaterial({
141
+ color: 0xD2B48C,
142
+ roughness: 0.8,
143
+ metalness: 0.2
144
+ });
145
+
146
+ const vertices = geometry.attributes.position.array;
147
+ for (let i = 0; i < vertices.length; i += 3) {
148
+ vertices[i + 2] = Math.sin(vertices[i] * 0.01) * Math.cos(vertices[i + 1] * 0.01) * 20;
149
+ }
150
+
151
+ geometry.attributes.position.needsUpdate = true;
152
+ geometry.computeVertexNormals();
153
+
154
+ const terrain = new THREE.Mesh(geometry, material);
155
+ terrain.rotation.x = -Math.PI / 2;
156
+ terrain.receiveShadow = true;
157
+ scene.add(terrain);
158
+
159
+ addObstacles();
160
+ resolve();
161
+ });
162
  }
163
 
 
164
  function createExplosion(position) {
165
  const particleCount = 30;
166
  const particles = [];
 
186
  scene.add(particle);
187
  }
188
 
189
+ function animateExplosion() {
 
190
  particles.forEach((particle, i) => {
191
  particle.position.add(particle.velocity);
192
  particle.material.opacity -= 0.02;
 
200
  if (particles.length > 0) {
201
  requestAnimationFrame(animateExplosion);
202
  }
203
+ }
204
+
205
  animateExplosion();
206
  sounds.explosion.play();
207
  }
208
 
209
+ function addObstacles() {
210
+ const rockGeometry = new THREE.DodecahedronGeometry(10);
211
+ const rockMaterial = new THREE.MeshStandardMaterial({
212
+ color: 0x8B4513,
213
+ roughness: 0.9
 
214
  });
215
 
216
+ for (let i = 0; i < 100; i++) {
217
+ const rock = new THREE.Mesh(rockGeometry, rockMaterial);
218
+ rock.position.set(
219
+ (Math.random() - 0.5) * MAP_SIZE * 0.9,
220
+ Math.random() * 10,
221
+ (Math.random() - 0.5) * MAP_SIZE * 0.9
222
+ );
223
+ rock.rotation.set(
224
+ Math.random() * Math.PI,
225
+ Math.random() * Math.PI,
226
+ Math.random() * Math.PI
227
+ );
228
+ rock.castShadow = true;
229
+ rock.receiveShadow = true;
230
+ scene.add(rock);
231
  }
232
+ }
233
 
234
+ function loadEnemies() {
235
+ return new Promise((resolve) => {
236
+ const loader = new GLTFLoader();
237
+ const enemyCount = 3 + currentStage;
238
+ let loadedCount = 0;
239
+
240
+ for (let i = 0; i < enemyCount; i++) {
241
+ const angle = (i / enemyCount) * Math.PI * 2;
242
+ const radius = 200;
243
+ const position = new THREE.Vector3(
244
+ Math.cos(angle) * radius,
245
+ ENEMY_GROUND_HEIGHT,
246
+ Math.sin(angle) * radius
247
+ );
248
+
249
+ const modelPath = ENEMY_MODELS[i % ENEMY_MODELS.length];
250
+
251
+ loader.load(modelPath,
252
+ (gltf) => {
253
+ const enemy = gltf.scene;
254
+ enemy.scale.set(ENEMY_SCALE, ENEMY_SCALE, ENEMY_SCALE);
255
+ enemy.position.copy(position);
256
+ enemy.rotation.y = Math.PI;
257
+
258
+ enemy.traverse((node) => {
259
+ if (node.isMesh) {
260
+ node.castShadow = true;
261
+ node.receiveShadow = true;
262
+ node.material.metalness = 0.2;
263
+ node.material.roughness = 0.8;
264
+ }
265
+ });
266
+
267
+ scene.add(enemy);
268
+ enemies.push({
269
+ model: enemy,
270
+ health: 100,
271
+ speed: ENEMY_MOVE_SPEED,
272
+ lastAttackTime: 0
273
+ });
274
+
275
+ loadedCount++;
276
+ if (loadedCount === enemyCount) {
277
+ resolve();
278
+ }
279
+ },
280
+ (xhr) => {
281
+ console.log(`${modelPath}: ${(xhr.loaded / xhr.total * 100)}% loaded`);
282
+ },
283
+ (error) => {
284
+ console.error('Error loading enemy model:', error);
285
+ loadedCount++;
286
+ if (loadedCount === enemyCount) {
287
+ resolve();
288
+ }
289
+ }
290
+ );
291
+ }
292
+ });
293
+ }
294
 
295
+ // 이벀트 ν•Έλ“€λŸ¬λ“€...
296
+ function onClick() {
297
+ if (!controls.isLocked) {
298
+ controls.lock();
299
+ sounds.bgm.play();
300
+ } else if (ammo > 0) {
301
+ shoot();
302
+ }
303
+ }
304
 
305
+ function onKeyDown(event) {
306
+ switch(event.code) {
307
+ case 'KeyW': moveState.forward = true; break;
308
+ case 'KeyS': moveState.backward = true; break;
309
+ case 'KeyA': moveState.left = true; break;
310
+ case 'KeyD': moveState.right = true; break;
311
+ case 'KeyR': reload(); break;
312
+ }
313
  }
314
 
315
+ function onKeyUp(event) {
316
+ switch(event.code) {
317
+ case 'KeyW': moveState.forward = false; break;
318
+ case 'KeyS': moveState.backward = false; break;
319
+ case 'KeyA': moveState.left = false; break;
320
+ case 'KeyD': moveState.right = false; break;
321
+ }
322
  }
323
 
324
+ function onWindowResize() {
325
+ camera.aspect = window.innerWidth / window.innerHeight;
326
+ camera.updateProjectionMatrix();
327
+ renderer.setSize(window.innerWidth, window.innerHeight);
328
+ }
329
+
330
+ // κ²Œμž„ 메컀닉 ν•¨μˆ˜λ“€...
331
+ function shoot() {
332
+ if (ammo <= 0) return;
333
+
334
+ ammo--;
335
+ updateAmmoDisplay();
336
+
337
+ const bullet = createBullet();
338
+ bullets.push(bullet);
339
+
340
+ sounds.gunshot.play();
341
+ }
342
+
343
+ function createBullet() {
344
+ const bulletGeometry = new THREE.SphereGeometry(0.5);
345
+ const bulletMaterial = new THREE.MeshBasicMaterial({
346
+ color: 0xffff00,
347
+ emissive: 0xffff00,
348
+ emissiveIntensity: 1
349
+ });
350
+ const bullet = new THREE.Mesh(bulletGeometry, bulletMaterial);
351
+
352
+ bullet.position.copy(camera.position);
353
+ const direction = new THREE.Vector3();
354
+ camera.getWorldDirection(direction);
355
+ bullet.velocity = direction.multiplyScalar(3);
356
+
357
+ scene.add(bullet);
358
+ return bullet;
359
+ }
360
+
361
+ function reload() {
362
+ ammo = 30;
363
+ updateAmmoDisplay();
364
+ }
365
+
366
+ function updateAmmoDisplay() {
367
+ document.getElementById('ammo').textContent = `Ammo: ${ammo}/30`;
368
+ }
369
+
370
+ function updateHealthBar() {
371
+ const healthElement = document.getElementById('health');
372
+ const healthPercentage = (playerHealth / MAX_HEALTH) * 100;
373
+ healthElement.style.width = `${healthPercentage}%`;
374
+ }
375
+
376
+ function updateHelicopterHUD() {
377
+ const altitude = Math.round(camera.position.y);
378
+ document.querySelector('#altitude-indicator span').textContent = altitude;
379
+
380
+ const speed = Math.round(
381
+ Math.sqrt(
382
+ moveState.forward * moveState.forward +
383
+ moveState.right * moveState.right
384
+ ) * 100
385
+ );
386
+ document.querySelector('#speed-indicator span').textContent = speed;
387
 
388
+ const heading = Math.round(
389
+ (camera.rotation.y * (180 / Math.PI) + 360) % 360
390
+ );
391
+ document.querySelector('#compass span').textContent = heading;
392
+
393
+ updateRadar();
394
+ }
395
+
396
+ function updateRadar() {
397
+ const radarTargets = document.querySelector('.radar-targets');
398
+ radarTargets.innerHTML = '';
399
+
400
+ enemies.forEach(enemy => {
401
+ const relativePos = enemy.model.position.clone().sub(camera.position);
402
+ const distance = relativePos.length();
403
 
404
+ if (distance < 500) {
405
+ const angle = Math.atan2(relativePos.x, relativePos.z);
406
+ const normalizedDistance = distance / 500;
407
+
408
+ const dot = document.createElement('div');
409
+ dot.className = 'radar-dot';
410
+ dot.style.left = `${50 + Math.sin(angle) * normalizedDistance * 45}%`;
411
+ dot.style.top = `${50 + Math.cos(angle) * normalizedDistance * 45}%`;
412
+ radarTargets.appendChild(dot);
413
+ }
414
+ });
415
+ }
 
 
 
 
 
 
 
 
 
416
 
417
+ function updateMovement() {
418
+ if (controls.isLocked) {
419
+ const speed = 2.0;
420
+ if (moveState.forward) controls.moveForward(speed);
421
+ if (moveState.backward) controls.moveForward(-speed);
422
+ if (moveState.left) controls.moveRight(-speed);
423
+ if (moveState.right) controls.moveRight(speed);
424
+
425
+ // μ΅œμ†Œ 고도 μœ μ§€
426
+ if (camera.position.y < HELICOPTER_HEIGHT) {
427
+ camera.position.y = HELICOPTER_HEIGHT;
428
+ }
429
+ }
430
+ }
431
 
432
+ function updateBullets() {
433
+ for (let i = bullets.length - 1; i >= 0; i--) {
434
+ bullets[i].position.add(bullets[i].velocity);
435
+
436
+ enemies.forEach(enemy => {
437
+ if (bullets[i].position.distanceTo(enemy.model.position) < 5) {
438
+ scene.remove(bullets[i]);
439
+ bullets.splice(i, 1);
440
+ enemy.health -= 25;
441
+
442
+ createExplosion(enemy.model.position.clone());
443
+
444
+ if (enemy.health <= 0) {
445
+ createExplosion(enemy.model.position.clone());
446
+ createExplosion(enemy.model.position.clone().add(new THREE.Vector3(2, 0, 2)));
447
+ createExplosion(enemy.model.position.clone().add(new THREE.Vector3(-2, 0, -2)));
448
+ scene.remove(enemy.model);
449
+ enemies = enemies.filter(e => e !== enemy);
450
  }
 
 
 
 
 
 
451
  }
 
 
 
 
 
 
 
452
  });
453
+
454
+ if (bullets[i] && bullets[i].position.distanceTo(camera.position) > 1000) {
455
+ scene.remove(bullets[i]);
456
+ bullets.splice(i, 1);
457
+ }
458
  }
459
  }
460
 
 
 
461
  function updateEnemies() {
462
  const currentTime = Date.now();
463
 
 
472
  newPosition.y = ENEMY_GROUND_HEIGHT;
473
  enemy.model.position.copy(newPosition);
474
 
 
475
  enemy.model.lookAt(new THREE.Vector3(
476
  camera.position.x,
477
  enemy.model.position.y,
 
481
  const distanceToPlayer = enemy.model.position.distanceTo(camera.position);
482
  if (distanceToPlayer < ENEMY_CONFIG.ATTACK_RANGE &&
483
  currentTime - enemy.lastAttackTime > ENEMY_CONFIG.ATTACK_INTERVAL) {
 
 
 
 
 
 
 
 
 
 
484
  enemyBullets.push(createEnemyBullet(enemy));
485
  enemy.lastAttackTime = currentTime;
486
  }
487
  });
488
  }
489
 
490
+ function createEnemyBullet(enemy) {
491
+ const bulletGeometry = new THREE.SphereGeometry(0.5);
492
+ const bulletMaterial = new THREE.MeshBasicMaterial({
493
+ color: 0xff0000,
494
+ emissive: 0xff0000,
495
+ emissiveIntensity: 1
496
+ });
497
+ const bullet = new THREE.Mesh(bulletGeometry, bulletMaterial);
498
+
499
+ bullet.position.copy(enemy.model.position);
500
+ const direction = new THREE.Vector3();
501
+ direction.subVectors(camera.position, enemy.model.position).normalize();
502
+ bullet.velocity = direction.multiplyScalar(ENEMY_CONFIG.BULLET_SPEED);
503
+
504
+ scene.add(bullet);
505
+ return bullet;
506
+ }
507
+
508
+ function updateEnemyBullets() {
509
+ for (let i = enemyBullets.length - 1; i >= 0; i--) {
510
+ enemyBullets[i].position.add(enemyBullets[i].velocity);
511
 
512
+ if (enemyBullets[i].position.distanceTo(camera.position) < 3) {
513
+ playerHealth -= 10;
514
+ updateHealthBar();
515
+ scene.remove(enemyBullets[i]);
516
+ enemyBullets.splice(i, 1);
517
+
518
+ if (playerHealth <= 0) {
519
+ gameOver(false);
 
 
 
 
 
 
 
 
 
520
  }
521
+ continue;
522
+ }
523
 
524
+ if (enemyBullets[i].position.distanceTo(camera.position) > 1000) {
525
+ scene.remove(enemyBullets[i]);
526
+ enemyBullets.splice(i, 1);
527
  }
528
  }
529
  }
530
 
531
+ function checkGameStatus() {
532
+ if (enemies.length === 0 && currentStage < 5) {
533
+ currentStage++;
534
+ document.getElementById('stage').style.display = 'block';
535
+ document.getElementById('stage').textContent = `Stage ${currentStage}`;
536
+ setTimeout(() => {
537
+ document.getElementById('stage').style.display = 'none';
538
+ loadEnemies();
539
+ }, 2000);
540
+ }
541
+ }
542
+
543
+ function gameOver(won) {
544
+ isGameOver = true;
545
+ controls.unlock();
546
+ sounds.bgm.pause();
547
+ alert(won ? 'Mission Complete!' : 'Game Over!');
548
+ location.reload();
549
+ }
550
+
551
+ function gameLoop() {
552
+ requestAnimationFrame(gameLoop);
553
+
554
+ const time = performance.now();
555
+ const delta = (time - lastTime) / 1000;
556
+ lastTime = time;
557
+
558
+ if (controls.isLocked && !isGameOver) {
559
+ updateMovement();
560
+ updateBullets();
561
+ updateEnemies();
562
+ updateEnemyBullets();
563
+ updateHelicopterHUD();
564
+ checkGameStatus();
565
+ }
566
+
567
+ renderer.render(scene, camera);
568
+ }
569
+
570
  // κ²Œμž„ μ‹œμž‘
571
+ init().then(() => {
572
+ console.log('Game started');
573
+ gameLoop();
574
+ });