gini1 commited on
Commit
26e2815
1 Parent(s): bafb03c

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +242 -259
index.html CHANGED
@@ -7,6 +7,7 @@
7
  body {
8
  margin: 0;
9
  overflow: hidden;
 
10
  }
11
  #info {
12
  position: absolute;
@@ -100,15 +101,6 @@
100
  z-index: 100;
101
  border-radius: 5px;
102
  }
103
- #debug {
104
- position: absolute;
105
- bottom: 60px;
106
- left: 20px;
107
- color: white;
108
- font-family: Arial;
109
- font-size: 12px;
110
- z-index: 100;
111
- }
112
  </style>
113
  </head>
114
  <body>
@@ -117,8 +109,7 @@
117
  WASD - Move Helicopter<br>
118
  Mouse - Aim<br>
119
  Left Click - Shoot<br>
120
- R - Reload<br>
121
- Survive 3 minutes!
122
  </div>
123
  <div id="timer">Safe Time: 10s</div>
124
  <div id="gameTimer">Time: 3:00</div>
@@ -126,9 +117,6 @@
126
  <div id="healthBar"><div id="health"></div></div>
127
  <div id="ammo">Ammo: 30/30</div>
128
  <div id="stage">Stage 1</div>
129
- <div id="debug"></div>
130
-
131
- <script async src="https://unpkg.com/es-module-shims@1.6.3/dist/es-module-shims.js"></script>
132
 
133
  <script type="importmap">
134
  {
@@ -145,135 +133,86 @@
145
  import { PointerLockControls } from 'three/addons/controls/PointerLockControls.js';
146
 
147
  // 게임 상수
148
- const GAME_DURATION = 180; // 3분
149
- const MAX_STAGES = 5;
150
- const ENEMY_MODELS = ['1.GLB', '2.GLB', '3.GLB', '4.GLB'];
151
- const HELICOPTER_HEIGHT = 50; // 증가된 헬기 높이
152
- const ENEMY_SCALE = 3; // 증가된 적 크기
153
  const MAX_HEALTH = 1000;
154
- const MAP_SIZE = 2000; // 증가된 맵 크기
155
 
156
  // 게임 변수
157
  let scene, camera, renderer, controls;
158
- let enemies = [], bullets = [], enemyBullets = [];
159
- let isGameOver = false;
160
- let isSafePeriod = true;
161
- let startTime = 0;
162
- let gameStartTime = 0;
163
  let playerHealth = MAX_HEALTH;
164
  let ammo = 30;
165
  let currentStage = 1;
166
- const maxAmmo = 30;
167
 
168
- // 사운드 시스템
169
  const sounds = {
170
  bgm: new Audio('Music.wav'),
171
  gunshot: new Audio('gun.wav')
172
  };
173
-
174
  sounds.bgm.loop = true;
175
 
176
- // Scene 초기화
177
- function initScene() {
178
  scene = new THREE.Scene();
179
- scene.background = new THREE.Color(0xffd700); // 사막 하늘색
180
- scene.fog = new THREE.Fog(0xffd700, 0, 1500);
181
 
 
182
  camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 3000);
183
  camera.position.set(0, HELICOPTER_HEIGHT, 0);
184
 
 
185
  renderer = new THREE.WebGLRenderer({ antialias: true });
186
  renderer.setSize(window.innerWidth, window.innerHeight);
187
  renderer.shadowMap.enabled = true;
188
- renderer.shadowMap.type = THREE.PCFSoftShadowMap;
189
  document.body.appendChild(renderer.domElement);
190
 
191
- // 조명
192
- const ambientLight = new THREE.AmbientLight(0xffffff, 1.5);
193
  scene.add(ambientLight);
194
 
195
- const dirLight = new THREE.DirectionalLight(0xffffff, 2);
196
  dirLight.position.set(100, 100, 50);
197
  dirLight.castShadow = true;
198
- dirLight.shadow.mapSize.width = 4096;
199
- dirLight.shadow.mapSize.height = 4096;
200
- dirLight.shadow.camera.far = 1000;
201
- dirLight.shadow.camera.left = -500;
202
- dirLight.shadow.camera.right = 500;
203
- dirLight.shadow.camera.top = 500;
204
- dirLight.shadow.camera.bottom = -500;
205
  scene.add(dirLight);
206
 
207
- // 사막 햇빛 효과
208
- const sunLight = new THREE.DirectionalLight(0xfdb813, 1);
209
- sunLight.position.set(-100, 200, -100);
210
- scene.add(sunLight);
211
- }
212
-
213
- // 디버그 정보 업데이트
214
- function updateDebugInfo() {
215
- const debugDiv = document.getElementById('debug');
216
- debugDiv.innerHTML = `
217
- Enemies: ${enemies.length}<br>
218
- Stage: ${currentStage}<br>
219
- Enemy Positions:<br>
220
- ${enemies.map((e, i) =>
221
- `Enemy ${i}: x=${e.model.position.x.toFixed(1)},
222
- y=${e.model.position.y.toFixed(1)},
223
- z=${e.model.position.z.toFixed(1)}`
224
- ).join('<br>')}
225
- `;
226
- }
227
 
228
- // 총알 생성
229
- function createBullet(isEnemy = false) {
230
- const bulletGeometry = new THREE.SphereGeometry(0.5, 8, 8);
231
- const bulletMaterial = new THREE.MeshBasicMaterial({
232
- color: isEnemy ? 0xff0000 : 0xffff00,
233
- emissive: isEnemy ? 0xff0000 : 0xffff00,
234
- emissiveIntensity: 1
235
- });
236
- const bullet = new THREE.Mesh(bulletGeometry, bulletMaterial);
237
 
238
- if (!isEnemy) {
239
- bullet.position.copy(camera.position);
240
- const direction = new THREE.Vector3();
241
- camera.getWorldDirection(direction);
242
- bullet.velocity = direction.multiplyScalar(3);
243
- sounds.gunshot.currentTime = 0;
244
- sounds.gunshot.play();
245
- }
246
-
247
- scene.add(bullet);
248
- return bullet;
249
- }
250
 
 
 
 
 
 
 
251
 
252
- // 사막 지형 생성
253
  function createTerrain() {
254
- // 사막 텍스처 생성
255
- const textureLoader = new THREE.TextureLoader();
256
- const sandTexture = textureLoader.load('https://threejs.org/examples/textures/terrain/grasslight-big.jpg');
257
- sandTexture.wrapS = sandTexture.wrapT = THREE.RepeatWrapping;
258
- sandTexture.repeat.set(25, 25);
259
-
260
  const geometry = new THREE.PlaneGeometry(MAP_SIZE, MAP_SIZE, 200, 200);
261
  const material = new THREE.MeshStandardMaterial({
262
- color: 0xd2b48c,
263
- map: sandTexture,
264
  roughness: 0.8,
265
- metalness: 0.2,
266
- bumpScale: 0.5,
267
  });
268
 
269
- // 모래 언덕 생성
270
  const vertices = geometry.attributes.position.array;
271
  for (let i = 0; i < vertices.length; i += 3) {
272
- const x = vertices[i];
273
- const y = vertices[i + 1];
274
- vertices[i + 2] = Math.sin(x * 0.01) * Math.cos(y * 0.01) * 20 +
275
- Math.random() * 5 +
276
- Math.sin(x * 0.02 + y * 0.02) * 10;
277
  }
278
 
279
  geometry.attributes.position.needsUpdate = true;
@@ -284,26 +223,23 @@
284
  terrain.receiveShadow = true;
285
  scene.add(terrain);
286
 
287
- // 사막 바위 추가
288
- addDesertRocks();
289
- // 선인장 추가
290
- addCacti();
291
  }
292
 
293
- // 사막 바위 추가
294
- function addDesertRocks() {
295
  const rockGeometry = new THREE.DodecahedronGeometry(10);
296
  const rockMaterial = new THREE.MeshStandardMaterial({
297
- color: 0x8b4513,
298
- roughness: 0.8,
299
- metalness: 0.2
300
  });
301
 
302
- for (let i = 0; i < 50; i++) {
303
  const rock = new THREE.Mesh(rockGeometry, rockMaterial);
304
  rock.position.set(
305
  (Math.random() - 0.5) * MAP_SIZE * 0.9,
306
- Math.random() * 10 + 5,
307
  (Math.random() - 0.5) * MAP_SIZE * 0.9
308
  );
309
  rock.rotation.set(
@@ -311,198 +247,245 @@
311
  Math.random() * Math.PI,
312
  Math.random() * Math.PI
313
  );
314
- rock.scale.set(
315
- Math.random() * 2 + 1,
316
- Math.random() * 2 + 1,
317
- Math.random() * 2 + 1
318
- );
319
  rock.castShadow = true;
320
  rock.receiveShadow = true;
321
  scene.add(rock);
322
  }
323
  }
324
 
325
- // 선인장 추가
326
- function addCacti() {
327
- const trunkGeometry = new THREE.CylinderGeometry(2, 2, 15, 8);
328
- const armGeometry = new THREE.CylinderGeometry(1.5, 1.5, 10, 8);
329
- const cactusMaterial = new THREE.MeshStandardMaterial({
330
- color: 0x2f4f2f,
331
- roughness: 0.8
332
- });
333
-
334
- for (let i = 0; i < 100; i++) {
335
- const cactusGroup = new THREE.Group();
336
-
337
- // 몸통
338
- const trunk = new THREE.Mesh(trunkGeometry, cactusMaterial);
339
- cactusGroup.add(trunk);
340
-
341
- // 팔 추가
342
- const armsCount = Math.floor(Math.random() * 3) + 1;
343
- for (let j = 0; j < armsCount; j++) {
344
- const arm = new THREE.Mesh(armGeometry, cactusMaterial);
345
- arm.position.y = Math.random() * 5;
346
- arm.rotation.z = Math.PI / 2 * (Math.random() > 0.5 ? 1 : -1);
347
- arm.position.x = (Math.random() > 0.5 ? 1 : -1) * 3;
348
- cactusGroup.add(arm);
349
- }
350
-
351
- cactusGroup.position.set(
352
- (Math.random() - 0.5) * MAP_SIZE * 0.9,
353
- 7.5,
354
- (Math.random() - 0.5) * MAP_SIZE * 0.9
355
- );
356
- cactusGroup.castShadow = true;
357
- cactusGroup.receiveShadow = true;
358
- scene.add(cactusGroup);
359
- }
360
- }
361
-
362
- // 적 로드
363
  function loadEnemies() {
364
- console.log('Loading enemies...');
365
  const loader = new GLTFLoader();
366
- const enemiesCount = 3 + currentStage;
367
 
368
- for (let i = 0; i < enemiesCount; i++) {
369
  const modelPath = ENEMY_MODELS[i % ENEMY_MODELS.length];
370
- console.log(`Loading enemy model: ${modelPath}`);
371
 
372
  loader.load(modelPath, (gltf) => {
373
  const enemy = gltf.scene;
374
  enemy.scale.set(ENEMY_SCALE, ENEMY_SCALE, ENEMY_SCALE);
375
 
376
- // 랜덤 위치에 스폰하되, 플레이어로부터 최소 거리 유지
377
- let position = getRandomSpawnPosition();
378
- while (position.distanceTo(camera.position) < 100) {
379
- position = getRandomSpawnPosition();
380
- }
381
-
382
- enemy.position.copy(position);
383
-
384
- // 밝기 개선을 위한 재질 설정
385
  enemy.traverse((node) => {
386
  if (node.isMesh) {
387
- node.material = node.material.clone();
388
- node.material.metalness = 0.3;
389
- node.material.roughness = 0.5;
390
- node.material.emissive = new THREE.Color(0x666666);
391
- node.material.emissiveIntensity = 0.3;
392
  node.castShadow = true;
393
  node.receiveShadow = true;
 
 
394
  }
395
  });
396
-
397
  scene.add(enemy);
398
  enemies.push({
399
  model: enemy,
400
  health: 100,
401
- speed: 0.3 + (currentStage * 0.05),
402
- velocity: new THREE.Vector3(),
403
- lastShot: 0,
404
- moveTarget: new THREE.Vector3(),
405
- type: modelPath
406
  });
407
-
408
- console.log(`Enemy loaded: ${modelPath}, Position:`, position);
409
- updateDebugInfo();
410
- updateMoveTarget(enemies[enemies.length - 1]);
411
- },
412
- (xhr) => {
413
- console.log(`${modelPath}: ${(xhr.loaded / xhr.total) * 100}% loaded`);
414
- },
415
- (error) => {
416
- console.error(`Error loading ${modelPath}:`, error);
417
  });
418
  }
419
  }
420
 
421
- // 랜덤 스폰 위치 생성
422
- function getRandomSpawnPosition() {
423
- const angle = Math.random() * Math.PI * 2;
424
- const radius = Math.random() * (MAP_SIZE * 0.4) + 100;
425
- return new THREE.Vector3(
426
- Math.cos(angle) * radius,
427
- 10,
428
- Math.sin(angle) * radius
429
- );
430
  }
431
 
432
- // 적의 새로운 이동 목표점 설정
433
- function updateMoveTarget(enemy) {
434
- const angle = Math.random() * Math.PI * 2;
435
- const radius = Math.random() * (MAP_SIZE * 0.4);
436
- enemy.moveTarget.set(
437
- Math.cos(angle) * radius,
438
- 10,
439
- Math.sin(angle) * radius
440
- );
441
  }
442
 
443
- // 업데이트
444
- function updateEnemies() {
445
- if (!isSafePeriod) {
446
- enemies.forEach(enemy => {
447
- // AI 이동 로직
448
- const toTarget = new THREE.Vector3();
449
- toTarget.subVectors(enemy.moveTarget, enemy.model.position);
450
-
451
- if (toTarget.length() < 10) {
452
- updateMoveTarget(enemy);
453
- }
454
 
455
- // 플레이어 방향 가중치 계산
456
- const toPlayer = new THREE.Vector3();
457
- toPlayer.subVectors(camera.position, enemy.model.position);
458
- const distanceToPlayer = toPlayer.length();
459
-
460
- // 거리에 따른 가중치 조정
461
- const playerWeight = Math.min(1, 100 / distanceToPlayer);
462
-
463
- const direction = new THREE.Vector3();
464
- direction.addScaledVector(toTarget.normalize(), 1 - playerWeight);
465
- direction.addScaledVector(toPlayer.normalize(), playerWeight);
466
- direction.normalize();
467
-
468
- // 속도 및 위치 업데이트
469
- enemy.velocity.lerp(direction.multiplyScalar(enemy.speed), 0.05);
470
- enemy.model.position.add(enemy.velocity);
471
-
472
- // 적 회전
473
- enemy.model.lookAt(camera.position);
474
-
475
- // 발사 로직
476
- const now = Date.now();
477
- const baseInterval = 3000 - (currentStage * 300); // 기본 발사 간격
478
- const distanceModifier = Math.max(0.5, Math.min(1.5, distanceToPlayer / 100));
479
- const fireInterval = baseInterval * distanceModifier;
480
-
481
- if (now - enemy.lastShot > fireInterval) {
482
- enemyShoot(enemy);
483
- enemy.lastShot = now;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
484
  }
485
  });
486
 
487
- updateDebugInfo();
 
 
 
 
488
  }
489
  }
490
 
491
- // 게임 오버 처리
492
- function gameOver(won) {
493
- if (!isGameOver) {
494
- isGameOver = true;
495
- controls.unlock();
496
- sounds.bgm.pause();
497
- setTimeout(() => {
498
- alert(won ? 'Mission Accomplished! You survived in the desert!' : 'Game Over! You were eliminated!');
499
- location.reload();
500
- }, 100);
 
 
 
 
 
 
 
 
 
 
501
  }
502
  }
503
 
504
- // 게임 초기화 및 시작
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
505
  init();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
506
  </script>
507
  </body>
508
  </html>
 
7
  body {
8
  margin: 0;
9
  overflow: hidden;
10
+ background: #000;
11
  }
12
  #info {
13
  position: absolute;
 
101
  z-index: 100;
102
  border-radius: 5px;
103
  }
 
 
 
 
 
 
 
 
 
104
  </style>
105
  </head>
106
  <body>
 
109
  WASD - Move Helicopter<br>
110
  Mouse - Aim<br>
111
  Left Click - Shoot<br>
112
+ R - Reload
 
113
  </div>
114
  <div id="timer">Safe Time: 10s</div>
115
  <div id="gameTimer">Time: 3:00</div>
 
117
  <div id="healthBar"><div id="health"></div></div>
118
  <div id="ammo">Ammo: 30/30</div>
119
  <div id="stage">Stage 1</div>
 
 
 
120
 
121
  <script type="importmap">
122
  {
 
133
  import { PointerLockControls } from 'three/addons/controls/PointerLockControls.js';
134
 
135
  // 게임 상수
136
+ const GAME_DURATION = 180;
137
+ const MAP_SIZE = 2000;
138
+ const HELICOPTER_HEIGHT = 50;
139
+ const ENEMY_SCALE = 3;
 
140
  const MAX_HEALTH = 1000;
141
+ const ENEMY_MODELS = ['1.GLB', '2.GLB', '3.GLB', '4.GLB'];
142
 
143
  // 게임 변수
144
  let scene, camera, renderer, controls;
145
+ let enemies = [];
146
+ let bullets = [];
147
+ let enemyBullets = [];
 
 
148
  let playerHealth = MAX_HEALTH;
149
  let ammo = 30;
150
  let currentStage = 1;
151
+ let isGameOver = false;
152
 
153
+ // 사운드
154
  const sounds = {
155
  bgm: new Audio('Music.wav'),
156
  gunshot: new Audio('gun.wav')
157
  };
 
158
  sounds.bgm.loop = true;
159
 
160
+ function init() {
161
+ // Scene 생성
162
  scene = new THREE.Scene();
163
+ scene.background = new THREE.Color(0x87ceeb); // 하늘색
164
+ scene.fog = new THREE.Fog(0x87ceeb, 0, 1500);
165
 
166
+ // Camera 설정
167
  camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 3000);
168
  camera.position.set(0, HELICOPTER_HEIGHT, 0);
169
 
170
+ // Renderer 설정
171
  renderer = new THREE.WebGLRenderer({ antialias: true });
172
  renderer.setSize(window.innerWidth, window.innerHeight);
173
  renderer.shadowMap.enabled = true;
 
174
  document.body.appendChild(renderer.domElement);
175
 
176
+ // 조명 설정
177
+ const ambientLight = new THREE.AmbientLight(0xffffff, 1.0);
178
  scene.add(ambientLight);
179
 
180
+ const dirLight = new THREE.DirectionalLight(0xffffff, 1.0);
181
  dirLight.position.set(100, 100, 50);
182
  dirLight.castShadow = true;
 
 
 
 
 
 
 
183
  scene.add(dirLight);
184
 
185
+ // 지형 생성
186
+ createTerrain();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
187
 
188
+ // Controls 설정
189
+ controls = new PointerLockControls(camera, document.body);
 
 
 
 
 
 
 
190
 
191
+ // 이벤트 리스너
192
+ document.addEventListener('click', onClick);
193
+ document.addEventListener('keydown', onKeyDown);
194
+ document.addEventListener('keyup', onKeyUp);
 
 
 
 
 
 
 
 
195
 
196
+ // 적 로드
197
+ loadEnemies();
198
+
199
+ // 애니메이션 시작
200
+ animate();
201
+ }
202
 
 
203
  function createTerrain() {
204
+ // 지형 생성
 
 
 
 
 
205
  const geometry = new THREE.PlaneGeometry(MAP_SIZE, MAP_SIZE, 200, 200);
206
  const material = new THREE.MeshStandardMaterial({
207
+ color: 0xD2B48C, // 사막색
 
208
  roughness: 0.8,
209
+ metalness: 0.2
 
210
  });
211
 
212
+ // 지형 높낮이 설정
213
  const vertices = geometry.attributes.position.array;
214
  for (let i = 0; i < vertices.length; i += 3) {
215
+ vertices[i + 2] = Math.sin(vertices[i] * 0.01) * Math.cos(vertices[i + 1] * 0.01) * 20;
 
 
 
 
216
  }
217
 
218
  geometry.attributes.position.needsUpdate = true;
 
223
  terrain.receiveShadow = true;
224
  scene.add(terrain);
225
 
226
+ // 장애물 추가
227
+ addObstacles();
 
 
228
  }
229
 
230
+ function addObstacles() {
231
+ // 바위 생성
232
  const rockGeometry = new THREE.DodecahedronGeometry(10);
233
  const rockMaterial = new THREE.MeshStandardMaterial({
234
+ color: 0x8B4513,
235
+ roughness: 0.9
 
236
  });
237
 
238
+ for (let i = 0; i < 100; i++) {
239
  const rock = new THREE.Mesh(rockGeometry, rockMaterial);
240
  rock.position.set(
241
  (Math.random() - 0.5) * MAP_SIZE * 0.9,
242
+ Math.random() * 10,
243
  (Math.random() - 0.5) * MAP_SIZE * 0.9
244
  );
245
  rock.rotation.set(
 
247
  Math.random() * Math.PI,
248
  Math.random() * Math.PI
249
  );
 
 
 
 
 
250
  rock.castShadow = true;
251
  rock.receiveShadow = true;
252
  scene.add(rock);
253
  }
254
  }
255
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
256
  function loadEnemies() {
 
257
  const loader = new GLTFLoader();
258
+ const enemyCount = 3 + currentStage;
259
 
260
+ for (let i = 0; i < enemyCount; i++) {
261
  const modelPath = ENEMY_MODELS[i % ENEMY_MODELS.length];
 
262
 
263
  loader.load(modelPath, (gltf) => {
264
  const enemy = gltf.scene;
265
  enemy.scale.set(ENEMY_SCALE, ENEMY_SCALE, ENEMY_SCALE);
266
 
267
+ // 랜덤 위치에 배치
268
+ const angle = (i / enemyCount) * Math.PI * 2;
269
+ const radius = 200;
270
+ enemy.position.set(
271
+ Math.cos(angle) * radius,
272
+ 10,
273
+ Math.sin(angle) * radius
274
+ );
275
+
276
  enemy.traverse((node) => {
277
  if (node.isMesh) {
 
 
 
 
 
278
  node.castShadow = true;
279
  node.receiveShadow = true;
280
+ node.material.metalness = 0.2;
281
+ node.material.roughness = 0.8;
282
  }
283
  });
284
+
285
  scene.add(enemy);
286
  enemies.push({
287
  model: enemy,
288
  health: 100,
289
+ speed: 0.3 + (currentStage * 0.1)
 
 
 
 
290
  });
 
 
 
 
 
 
 
 
 
 
291
  });
292
  }
293
  }
294
 
295
+ function onClick() {
296
+ if (!controls.isLocked) {
297
+ controls.lock();
298
+ sounds.bgm.play();
299
+ } else if (ammo > 0) {
300
+ shoot();
301
+ }
 
 
302
  }
303
 
304
+ function onKeyDown(event) {
305
+ switch (event.code) {
306
+ case 'KeyR':
307
+ reload();
308
+ break;
309
+ }
 
 
 
310
  }
311
 
312
+ function onKeyUp(event) {
313
+ // 해제 처리
314
+ }
 
 
 
 
 
 
 
 
315
 
316
+ function shoot() {
317
+ if (ammo <= 0) return;
318
+
319
+ ammo--;
320
+ updateAmmoDisplay();
321
+
322
+ const bullet = createBullet();
323
+ bullets.push(bullet);
324
+
325
+ sounds.gunshot.currentTime = 0;
326
+ sounds.gunshot.play();
327
+ }
328
+
329
+ function createBullet() {
330
+ const bulletGeometry = new THREE.SphereGeometry(0.5);
331
+ const bulletMaterial = new THREE.MeshBasicMaterial({ color: 0xffff00 });
332
+ const bullet = new THREE.Mesh(bulletGeometry, bulletMaterial);
333
+
334
+ bullet.position.copy(camera.position);
335
+ const direction = new THREE.Vector3();
336
+ camera.getWorldDirection(direction);
337
+ bullet.velocity = direction.multiplyScalar(3);
338
+
339
+ scene.add(bullet);
340
+ return bullet;
341
+ }
342
+
343
+ function reload() {
344
+ ammo = 30;
345
+ updateAmmoDisplay();
346
+ }
347
+
348
+ function updateAmmoDisplay() {
349
+ document.getElementById('ammo').textContent = `Ammo: ${ammo}/30`;
350
+ }
351
+
352
+ function animate() {
353
+ requestAnimationFrame(animate);
354
+
355
+ if (controls.isLocked && !isGameOver) {
356
+ updateBullets();
357
+ updateEnemies();
358
+ checkGameStatus();
359
+ }
360
+
361
+ renderer.render(scene, camera);
362
+ }
363
+
364
+ function updateBullets() {
365
+ // 총알 업데이트 로직
366
+ for (let i = bullets.length - 1; i >= 0; i--) {
367
+ bullets[i].position.add(bullets[i].velocity);
368
+
369
+ // 적과의 충돌 체크
370
+ enemies.forEach(enemy => {
371
+ if (bullets[i].position.distanceTo(enemy.model.position) < 5) {
372
+ scene.remove(bullets[i]);
373
+ bullets.splice(i, 1);
374
+ enemy.health -= 25;
375
+
376
+ if (enemy.health <= 0) {
377
+ scene.remove(enemy.model);
378
+ enemies = enemies.filter(e => e !== enemy);
379
+ }
380
  }
381
  });
382
 
383
+ // 범위 벗어난 총알 제거
384
+ if (bullets[i] && bullets[i].position.distanceTo(camera.position) > 1000) {
385
+ scene.remove(bullets[i]);
386
+ bullets.splice(i, 1);
387
+ }
388
  }
389
  }
390
 
391
+ function updateEnemies() {
392
+ enemies.forEach(enemy => {
393
+ const direction = new THREE.Vector3();
394
+ direction.subVectors(camera.position, enemy.model.position);
395
+ direction.normalize();
396
+
397
+ enemy.model.position.add(direction.multiplyScalar(enemy.speed));
398
+ enemy.model.lookAt(camera.position);
399
+
400
+ // 플레이어와의 충돌 체크
401
+ if (enemy.model.position.distanceTo(camera.position) < 10) {
402
+ gameOver(false);
403
+ }
404
+ });
405
+ }
406
+
407
+ function checkGameStatus() {
408
+ if (enemies.length === 0 && currentStage < 5) {
409
+ currentStage++;
410
+ loadEnemies();
411
  }
412
  }
413
 
414
+ function gameOver(won) {
415
+ isGameOver = true;
416
+ controls.unlock();
417
+ sounds.bgm.pause();
418
+ alert(won ? 'Mission Complete!' : 'Game Over!');
419
+ location.reload();
420
+ }
421
+
422
+ // 화면 크기 조절 처리
423
+ window.addEventListener('resize', () => {
424
+ camera.aspect = window.innerWidth / window.innerHeight;
425
+ camera.updateProjectionMatrix();
426
+ renderer.setSize(window.innerWidth, window.innerHeight);
427
+ });
428
+
429
+ // 게임 시작
430
  init();
431
+
432
+ // 마우스 이동 속도 제어
433
+ const moveState = {
434
+ forward: false,
435
+ backward: false,
436
+ left: false,
437
+ right: false
438
+ };
439
+
440
+ // 키보드 이벤트
441
+ document.addEventListener('keydown', (event) => {
442
+ switch(event.code) {
443
+ case 'KeyW': moveState.forward = true; break;
444
+ case 'KeyS': moveState.backward = true; break;
445
+ case 'KeyA': moveState.left = true; break;
446
+ case 'KeyD': moveState.right = true; break;
447
+ }
448
+ });
449
+
450
+ document.addEventListener('keyup', (event) => {
451
+ switch(event.code) {
452
+ case 'KeyW': moveState.forward = false; break;
453
+ case 'KeyS': moveState.backward = false; break;
454
+ case 'KeyA': moveState.left = false; break;
455
+ case 'KeyD': moveState.right = false; break;
456
+ }
457
+ });
458
+
459
+ // 이동 업데이트
460
+ function updateMovement() {
461
+ if (controls.isLocked) {
462
+ const speed = 2.0;
463
+ if (moveState.forward) controls.moveForward(speed);
464
+ if (moveState.backward) controls.moveForward(-speed);
465
+ if (moveState.left) controls.moveRight(-speed);
466
+ if (moveState.right) controls.moveRight(speed);
467
+ }
468
+ }
469
+
470
+ // 메인 게임 루프에 이동 업데이트 추가
471
+ let lastTime = performance.now();
472
+ function gameLoop() {
473
+ const time = performance.now();
474
+ const delta = (time - lastTime) / 1000;
475
+ lastTime = time;
476
+
477
+ if (controls.isLocked && !isGameOver) {
478
+ updateMovement();
479
+ updateBullets();
480
+ updateEnemies();
481
+ checkGameStatus();
482
+ }
483
+
484
+ renderer.render(scene, camera);
485
+ requestAnimationFrame(gameLoop);
486
+ }
487
+
488
+ gameLoop();
489
  </script>
490
  </body>
491
  </html>