gini1 commited on
Commit
a7d011b
β€’
1 Parent(s): 3ad9530

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +199 -99
index.html CHANGED
@@ -1,7 +1,7 @@
1
  <!DOCTYPE html>
2
  <html>
3
  <head>
4
- <title>3D Shooter Game</title>
5
  <meta charset="utf-8">
6
  <style>
7
  body {
@@ -29,7 +29,7 @@
29
  background: rgba(0,0,0,0.7);
30
  padding: 10px;
31
  font-family: Arial;
32
- font-size: 14px;
33
  z-index: 100;
34
  border-radius: 5px;
35
  }
@@ -38,7 +38,7 @@
38
  top: 50%;
39
  left: 50%;
40
  transform: translate(-50%, -50%);
41
- color: rgba(255, 255, 255, 0.8);
42
  font-size: 24px;
43
  z-index: 100;
44
  user-select: none;
@@ -48,18 +48,18 @@
48
  position: absolute;
49
  bottom: 20px;
50
  left: 20px;
51
- width: 200px;
52
- height: 20px;
53
  background: rgba(0,0,0,0.5);
54
- border: 2px solid white;
55
  z-index: 100;
56
- border-radius: 10px;
57
  overflow: hidden;
58
  }
59
  #health {
60
  width: 100%;
61
  height: 100%;
62
- background: #ff3333;
63
  transition: width 0.3s;
64
  }
65
  #ammo {
@@ -70,7 +70,33 @@
70
  background: rgba(0,0,0,0.7);
71
  padding: 10px;
72
  font-family: Arial;
73
- font-size: 18px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
  z-index: 100;
75
  border-radius: 5px;
76
  }
@@ -79,15 +105,18 @@
79
  <body>
80
  <div id="info">
81
  Click to start<br>
82
- WASD - Move<br>
83
- Mouse - Look around<br>
84
  Left Click - Shoot<br>
85
- R - Reload
 
86
  </div>
87
  <div id="timer">Safe Time: 10s</div>
 
88
  <div id="crosshair">+</div>
89
  <div id="healthBar"><div id="health"></div></div>
90
  <div id="ammo">Ammo: 30/30</div>
 
91
 
92
  <script async src="https://unpkg.com/es-module-shims@1.6.3/dist/es-module-shims.js"></script>
93
 
@@ -105,19 +134,33 @@
105
  import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
106
  import { PointerLockControls } from 'three/addons/controls/PointerLockControls.js';
107
 
108
- // κΈ°λ³Έ λ³€μˆ˜
 
 
 
 
 
 
 
 
109
  let scene, camera, renderer, controls;
110
- let enemies = [], bullets = [];
111
- let enemyBullets = [];
112
  let isGameOver = false;
113
  let isSafePeriod = true;
114
  let startTime = 0;
115
- let playerHealth = 100;
 
116
  let ammo = 30;
 
117
  const maxAmmo = 30;
118
- const SAFE_TIME = 10;
119
- const raycaster = new THREE.Raycaster();
120
- const mouse = new THREE.Vector2();
 
 
 
 
 
121
 
122
  // Scene μ΄ˆκΈ°ν™”
123
  function initScene() {
@@ -126,7 +169,7 @@
126
  scene.fog = new THREE.Fog(0x87ceeb, 0, 500);
127
 
128
  camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
129
- camera.position.set(0, 5, 0);
130
 
131
  renderer = new THREE.WebGLRenderer({ antialias: true });
132
  renderer.setSize(window.innerWidth, window.innerHeight);
@@ -135,27 +178,29 @@
135
  document.body.appendChild(renderer.domElement);
136
 
137
  // μ‘°λͺ…
138
- const ambientLight = new THREE.AmbientLight(0xffffff, 1.0);
139
  scene.add(ambientLight);
140
 
141
- const dirLight = new THREE.DirectionalLight(0xffffff, 1.2);
142
  dirLight.position.set(50, 50, 0);
143
  dirLight.castShadow = true;
144
- dirLight.shadow.mapSize.width = 2048;
145
- dirLight.shadow.mapSize.height = 2048;
146
  dirLight.shadow.camera.far = 300;
147
  scene.add(dirLight);
148
 
149
  // λ°˜μ‚¬κ΄‘
150
- const hemiLight = new THREE.HemisphereLight(0xffffff, 0x444444, 0.6);
151
  scene.add(hemiLight);
152
  }
153
 
154
  // μ΄μ•Œ 생성
155
  function createBullet(isEnemy = false) {
156
- const bulletGeometry = new THREE.SphereGeometry(0.1, 8, 8);
157
  const bulletMaterial = new THREE.MeshBasicMaterial({
158
- color: isEnemy ? 0xff0000 : 0xffff00
 
 
159
  });
160
  const bullet = new THREE.Mesh(bulletGeometry, bulletMaterial);
161
 
@@ -164,6 +209,8 @@
164
  const direction = new THREE.Vector3();
165
  camera.getWorldDirection(direction);
166
  bullet.velocity = direction.multiplyScalar(2);
 
 
167
  }
168
 
169
  scene.add(bullet);
@@ -179,23 +226,29 @@
179
 
180
  const bullet = createBullet();
181
  bullets.push(bullet);
182
-
183
- // λ°œμ‚¬ μ‚¬μš΄λ“œλ‚˜ μ΄νŽ™νŠΈλ₯Ό μΆ”κ°€ν•  수 있음
184
  }
185
 
186
  // 적 λ°œμ‚¬
187
  function enemyShoot(enemy) {
 
 
188
  const bullet = createBullet(true);
189
  bullet.position.copy(enemy.model.position);
190
 
191
  const direction = new THREE.Vector3();
192
  direction.subVectors(camera.position, enemy.model.position);
193
  direction.normalize();
194
- bullet.velocity = direction.multiplyScalar(1);
195
 
196
  enemyBullets.push(bullet);
197
  }
198
 
 
 
 
 
 
 
199
  // μ΄μ•Œ μ—…λ°μ΄νŠΈ
200
  function updateBullets() {
201
  // ν”Œλ ˆμ΄μ–΄ μ΄μ•Œ
@@ -204,7 +257,7 @@
204
 
205
  // 적과의 좩돌 체크
206
  enemies.forEach(enemy => {
207
- if (bullets[i].position.distanceTo(enemy.model.position) < 2) {
208
  scene.remove(bullets[i]);
209
  bullets.splice(i, 1);
210
  enemy.health -= 25;
@@ -212,13 +265,14 @@
212
  if (enemy.health <= 0) {
213
  scene.remove(enemy.model);
214
  enemies = enemies.filter(e => e !== enemy);
 
215
  }
216
  return;
217
  }
218
  });
219
 
220
  // 수λͺ… μ œν•œ
221
- if (bullets[i] && bullets[i].position.distanceTo(camera.position) > 100) {
222
  scene.remove(bullets[i]);
223
  bullets.splice(i, 1);
224
  }
@@ -229,10 +283,10 @@
229
  enemyBullets[i].position.add(enemyBullets[i].velocity);
230
 
231
  // ν”Œλ ˆμ΄μ–΄μ™€μ˜ 좩돌 체크
232
- if (enemyBullets[i].position.distanceTo(camera.position) < 2) {
233
  scene.remove(enemyBullets[i]);
234
  enemyBullets.splice(i, 1);
235
- playerHealth -= 10;
236
  updateHealthBar();
237
 
238
  if (playerHealth <= 0) {
@@ -242,22 +296,59 @@
242
  }
243
 
244
  // 수λͺ… μ œν•œ
245
- if (enemyBullets[i] && enemyBullets[i].position.distanceTo(camera.position) > 100) {
246
  scene.remove(enemyBullets[i]);
247
  enemyBullets.splice(i, 1);
248
  }
249
  }
250
  }
251
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
252
  function updateHealthBar() {
253
  const healthBar = document.getElementById('health');
254
- healthBar.style.width = `${playerHealth}%`;
255
  }
256
 
257
  function updateAmmoDisplay() {
258
  document.getElementById('ammo').textContent = `Ammo: ${ammo}/${maxAmmo}`;
259
  }
260
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
261
  // 재μž₯μ „
262
  function reload() {
263
  ammo = maxAmmo;
@@ -266,7 +357,7 @@
266
 
267
  // μ§€ν˜• 생성
268
  function createTerrain() {
269
- const geometry = new THREE.PlaneGeometry(300, 300, 50, 50);
270
  const material = new THREE.MeshStandardMaterial({
271
  color: 0x3a8c3a,
272
  roughness: 0.6,
@@ -275,7 +366,7 @@
275
 
276
  const vertices = geometry.attributes.position.array;
277
  for (let i = 0; i < vertices.length; i += 3) {
278
- vertices[i + 2] = Math.random() * 3;
279
  }
280
  geometry.attributes.position.needsUpdate = true;
281
  geometry.computeVertexNormals();
@@ -289,29 +380,34 @@
289
  // 적 λ‘œλ“œ
290
  function loadEnemies() {
291
  const loader = new GLTFLoader();
292
- loader.load('enemy.glb', (gltf) => {
293
- console.log('Enemy model loaded successfully');
294
- const enemyModel = gltf.scene;
295
-
296
- for (let i = 0; i < 5; i++) {
297
- const enemy = enemyModel.clone();
298
- enemy.scale.set(1, 1, 1); // 크기 증가
299
 
300
- const angle = (i / 5) * Math.PI * 2;
301
- const radius = 50; // 더 κ°€κΉκ²Œ μ‹œμž‘
302
  enemy.position.set(
303
  Math.cos(angle) * radius,
304
- 2, // μ§€λ©΄μ—μ„œ μ•½κ°„ μœ„λ‘œ
305
  Math.sin(angle) * radius
306
  );
307
 
308
  enemy.traverse((node) => {
309
  if (node.isMesh) {
310
- node.material = node.material.clone(); // 재질 볡제
311
  node.material.metalness = 0.3;
312
  node.material.roughness = 0.5;
313
  node.castShadow = true;
314
- node.receiveShadow = true;
 
 
 
 
 
315
  }
316
  });
317
 
@@ -319,20 +415,24 @@
319
  enemies.push({
320
  model: enemy,
321
  health: 100,
322
- speed: 0.2,
323
  velocity: new THREE.Vector3(),
324
- lastShot: 0
 
325
  });
326
- }
327
- },
328
- // λ‘œλ”© 진행상황
329
- (xhr) => {
330
- console.log((xhr.loaded / xhr.total * 100) + '% loaded');
331
- },
332
- // μ—λŸ¬ 처리
333
- (error) => {
334
- console.error('Error loading enemy model:', error);
335
- });
 
 
 
336
  }
337
 
338
  // 컨트둀 μ΄ˆκΈ°ν™”
@@ -346,7 +446,6 @@
346
  right: false
347
  };
348
 
349
- // ν‚€λ³΄λ“œ 이벀트
350
  document.addEventListener('keydown', (event) => {
351
  switch(event.code) {
352
  case 'KeyW': moveState.forward = true; break;
@@ -366,20 +465,20 @@
366
  }
367
  });
368
 
369
- // 마우슀 이벀트
370
  document.addEventListener('click', () => {
371
  if (controls.isLocked) {
372
  shoot();
373
  } else {
374
  controls.lock();
 
 
 
 
375
  }
376
  });
377
 
378
  controls.addEventListener('lock', () => {
379
  document.getElementById('info').style.display = 'none';
380
- if (!startTime) {
381
- startTime = Date.now();
382
- }
383
  });
384
 
385
  controls.addEventListener('unlock', () => {
@@ -393,12 +492,25 @@
393
  function updateEnemies() {
394
  if (!isSafePeriod) {
395
  enemies.forEach(enemy => {
396
- // 이동 둜직
 
 
 
 
 
 
 
 
 
 
 
 
397
  const direction = new THREE.Vector3();
398
- direction.subVectors(camera.position, enemy.model.position);
399
- direction.y = 0;
400
  direction.normalize();
401
 
 
402
  enemy.velocity.lerp(direction.multiplyScalar(enemy.speed), 0.02);
403
  enemy.model.position.add(enemy.velocity);
404
 
@@ -407,22 +519,23 @@
407
 
408
  // λ°œμ‚¬ 둜직
409
  const now = Date.now();
410
- if (now - enemy.lastShot > 2000) { // 2μ΄ˆλ§ˆλ‹€ λ°œμ‚¬
 
411
  enemyShoot(enemy);
412
  enemy.lastShot = now;
413
  }
414
  });
415
  }
416
-
417
- }
418
 
419
  // κ²Œμž„ μ˜€λ²„ 처리
420
  function gameOver(won) {
421
  if (!isGameOver) {
422
  isGameOver = true;
423
  controls.unlock();
 
424
  setTimeout(() => {
425
- alert(won ? 'You won!' : 'Game Over! You were eliminated!');
426
  location.reload();
427
  }, 100);
428
  }
@@ -434,26 +547,15 @@
434
  createTerrain();
435
  loadEnemies();
436
  const moveState = initControls();
 
437
 
438
  // κ²Œμž„ 루프
439
  function animate() {
440
  requestAnimationFrame(animate);
441
 
442
  if (controls.isLocked && !isGameOver) {
443
- // μ•ˆμ „ μ‹œκ°„ 체크
444
- if (startTime) {
445
- const elapsed = Math.floor((Date.now() - startTime) / 1000);
446
- const remaining = SAFE_TIME - elapsed;
447
- if (remaining > 0) {
448
- document.getElementById('timer').textContent = `Safe Time: ${remaining}s`;
449
- } else {
450
- document.getElementById('timer').textContent = '';
451
- isSafePeriod = false;
452
- }
453
- }
454
-
455
  // 이동 처리
456
- const speed = 0.3;
457
  if (moveState.forward) controls.moveForward(speed);
458
  if (moveState.backward) controls.moveForward(-speed);
459
  if (moveState.left) controls.moveRight(-speed);
@@ -465,27 +567,25 @@
465
  // 적 μ—…λ°μ΄νŠΈ
466
  updateEnemies();
467
 
468
- // 승리 쑰건 체크
469
- if (!isSafePeriod && enemies.length === 0) {
470
- gameOver(true);
471
- }
472
  }
473
 
474
  renderer.render(scene, camera);
475
  }
476
 
 
 
 
 
 
 
 
477
  animate();
478
  }
479
 
480
- // ν™”λ©΄ 크기 쑰절 처리
481
- window.addEventListener('resize', () => {
482
- camera.aspect = window.innerWidth / window.innerHeight;
483
- camera.updateProjectionMatrix();
484
- renderer.setSize(window.innerWidth, window.innerHeight);
485
- });
486
-
487
  // κ²Œμž„ μ‹œμž‘
488
  init();
489
  </script>
490
  </body>
491
- </html>
 
1
  <!DOCTYPE html>
2
  <html>
3
  <head>
4
+ <title>Helicopter Combat Game</title>
5
  <meta charset="utf-8">
6
  <style>
7
  body {
 
29
  background: rgba(0,0,0,0.7);
30
  padding: 10px;
31
  font-family: Arial;
32
+ font-size: 20px;
33
  z-index: 100;
34
  border-radius: 5px;
35
  }
 
38
  top: 50%;
39
  left: 50%;
40
  transform: translate(-50%, -50%);
41
+ color: rgba(255, 0, 0, 0.8);
42
  font-size: 24px;
43
  z-index: 100;
44
  user-select: none;
 
48
  position: absolute;
49
  bottom: 20px;
50
  left: 20px;
51
+ width: 400px;
52
+ height: 30px;
53
  background: rgba(0,0,0,0.5);
54
+ border: 3px solid white;
55
  z-index: 100;
56
+ border-radius: 15px;
57
  overflow: hidden;
58
  }
59
  #health {
60
  width: 100%;
61
  height: 100%;
62
+ background: linear-gradient(90deg, #ff3333, #ff0000);
63
  transition: width 0.3s;
64
  }
65
  #ammo {
 
70
  background: rgba(0,0,0,0.7);
71
  padding: 10px;
72
  font-family: Arial;
73
+ font-size: 20px;
74
+ z-index: 100;
75
+ border-radius: 5px;
76
+ }
77
+ #stage {
78
+ position: absolute;
79
+ top: 50%;
80
+ left: 50%;
81
+ transform: translate(-50%, -50%);
82
+ color: white;
83
+ background: rgba(0,0,0,0.8);
84
+ padding: 20px;
85
+ font-family: Arial;
86
+ font-size: 32px;
87
+ z-index: 100;
88
+ border-radius: 10px;
89
+ display: none;
90
+ }
91
+ #gameTimer {
92
+ position: absolute;
93
+ top: 60px;
94
+ right: 10px;
95
+ color: white;
96
+ background: rgba(0,0,0,0.7);
97
+ padding: 10px;
98
+ font-family: Arial;
99
+ font-size: 20px;
100
  z-index: 100;
101
  border-radius: 5px;
102
  }
 
105
  <body>
106
  <div id="info">
107
  Click to start<br>
108
+ WASD - Move Helicopter<br>
109
+ Mouse - Aim<br>
110
  Left Click - Shoot<br>
111
+ R - Reload<br>
112
+ Survive 3 minutes!
113
  </div>
114
  <div id="timer">Safe Time: 10s</div>
115
+ <div id="gameTimer">Time: 3:00</div>
116
  <div id="crosshair">+</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 async src="https://unpkg.com/es-module-shims@1.6.3/dist/es-module-shims.js"></script>
122
 
 
134
  import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
135
  import { PointerLockControls } from 'three/addons/controls/PointerLockControls.js';
136
 
137
+ // κ²Œμž„ μƒμˆ˜
138
+ const GAME_DURATION = 180; // 3λΆ„
139
+ const MAX_STAGES = 5;
140
+ const ENEMY_MODELS = ['1.GLB', '2.GLB', '3.GLB', '4.GLB'];
141
+ const HELICOPTER_HEIGHT = 30; // ν—¬κΈ° 높이
142
+ const ENEMY_SCALE = 2.5; // 적 크기
143
+ const MAX_HEALTH = 1000; // μ¦κ°€λœ 체λ ₯
144
+
145
+ // κ²Œμž„ λ³€μˆ˜
146
  let scene, camera, renderer, controls;
147
+ let enemies = [], bullets = [], enemyBullets = [];
 
148
  let isGameOver = false;
149
  let isSafePeriod = true;
150
  let startTime = 0;
151
+ let gameStartTime = 0;
152
+ let playerHealth = MAX_HEALTH;
153
  let ammo = 30;
154
+ let currentStage = 1;
155
  const maxAmmo = 30;
156
+
157
+ // μ‚¬μš΄λ“œ μ‹œμŠ€ν…œ
158
+ const sounds = {
159
+ bgm: new Audio('Music.wav'),
160
+ gunshot: new Audio('gun.wav')
161
+ };
162
+
163
+ sounds.bgm.loop = true;
164
 
165
  // Scene μ΄ˆκΈ°ν™”
166
  function initScene() {
 
169
  scene.fog = new THREE.Fog(0x87ceeb, 0, 500);
170
 
171
  camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
172
+ camera.position.set(0, HELICOPTER_HEIGHT, 0);
173
 
174
  renderer = new THREE.WebGLRenderer({ antialias: true });
175
  renderer.setSize(window.innerWidth, window.innerHeight);
 
178
  document.body.appendChild(renderer.domElement);
179
 
180
  // μ‘°λͺ…
181
+ const ambientLight = new THREE.AmbientLight(0xffffff, 1.2);
182
  scene.add(ambientLight);
183
 
184
+ const dirLight = new THREE.DirectionalLight(0xffffff, 1.5);
185
  dirLight.position.set(50, 50, 0);
186
  dirLight.castShadow = true;
187
+ dirLight.shadow.mapSize.width = 4096;
188
+ dirLight.shadow.mapSize.height = 4096;
189
  dirLight.shadow.camera.far = 300;
190
  scene.add(dirLight);
191
 
192
  // λ°˜μ‚¬κ΄‘
193
+ const hemiLight = new THREE.HemisphereLight(0xffffff, 0x444444, 0.8);
194
  scene.add(hemiLight);
195
  }
196
 
197
  // μ΄μ•Œ 생성
198
  function createBullet(isEnemy = false) {
199
+ const bulletGeometry = new THREE.SphereGeometry(0.2, 8, 8);
200
  const bulletMaterial = new THREE.MeshBasicMaterial({
201
+ color: isEnemy ? 0xff0000 : 0xffff00,
202
+ emissive: isEnemy ? 0xff0000 : 0xffff00,
203
+ emissiveIntensity: 1
204
  });
205
  const bullet = new THREE.Mesh(bulletGeometry, bulletMaterial);
206
 
 
209
  const direction = new THREE.Vector3();
210
  camera.getWorldDirection(direction);
211
  bullet.velocity = direction.multiplyScalar(2);
212
+ sounds.gunshot.currentTime = 0;
213
+ sounds.gunshot.play();
214
  }
215
 
216
  scene.add(bullet);
 
226
 
227
  const bullet = createBullet();
228
  bullets.push(bullet);
 
 
229
  }
230
 
231
  // 적 λ°œμ‚¬
232
  function enemyShoot(enemy) {
233
+ if (Math.random() > getFireRate()) return;
234
+
235
  const bullet = createBullet(true);
236
  bullet.position.copy(enemy.model.position);
237
 
238
  const direction = new THREE.Vector3();
239
  direction.subVectors(camera.position, enemy.model.position);
240
  direction.normalize();
241
+ bullet.velocity = direction.multiplyScalar(1.5);
242
 
243
  enemyBullets.push(bullet);
244
  }
245
 
246
+ // μŠ€ν…Œμ΄μ§€λ³„ λ°œμ‚¬ ν™•λ₯ 
247
+ function getFireRate() {
248
+ const baseRate = 0.01;
249
+ return baseRate * (1 + (currentStage - 1) * 0.5);
250
+ }
251
+
252
  // μ΄μ•Œ μ—…λ°μ΄νŠΈ
253
  function updateBullets() {
254
  // ν”Œλ ˆμ΄μ–΄ μ΄μ•Œ
 
257
 
258
  // 적과의 좩돌 체크
259
  enemies.forEach(enemy => {
260
+ if (bullets[i].position.distanceTo(enemy.model.position) < 4) {
261
  scene.remove(bullets[i]);
262
  bullets.splice(i, 1);
263
  enemy.health -= 25;
 
265
  if (enemy.health <= 0) {
266
  scene.remove(enemy.model);
267
  enemies = enemies.filter(e => e !== enemy);
268
+ checkStageCompletion();
269
  }
270
  return;
271
  }
272
  });
273
 
274
  // 수λͺ… μ œν•œ
275
+ if (bullets[i] && bullets[i].position.distanceTo(camera.position) > 200) {
276
  scene.remove(bullets[i]);
277
  bullets.splice(i, 1);
278
  }
 
283
  enemyBullets[i].position.add(enemyBullets[i].velocity);
284
 
285
  // ν”Œλ ˆμ΄μ–΄μ™€μ˜ 좩돌 체크
286
+ if (enemyBullets[i].position.distanceTo(camera.position) < 3) {
287
  scene.remove(enemyBullets[i]);
288
  enemyBullets.splice(i, 1);
289
+ playerHealth -= 20;
290
  updateHealthBar();
291
 
292
  if (playerHealth <= 0) {
 
296
  }
297
 
298
  // 수λͺ… μ œν•œ
299
+ if (enemyBullets[i] && enemyBullets[i].position.distanceTo(camera.position) > 200) {
300
  scene.remove(enemyBullets[i]);
301
  enemyBullets.splice(i, 1);
302
  }
303
  }
304
  }
305
 
306
+ // μŠ€ν…Œμ΄μ§€ μ™„λ£Œ 체크
307
+ function checkStageCompletion() {
308
+ if (enemies.length === 0 && currentStage < MAX_STAGES) {
309
+ currentStage++;
310
+ showStageMessage();
311
+ loadEnemies();
312
+ }
313
+ }
314
+
315
+ // μŠ€ν…Œμ΄μ§€ λ©”μ‹œμ§€ ν‘œμ‹œ
316
+ function showStageMessage() {
317
+ const stageDiv = document.getElementById('stage');
318
+ stageDiv.textContent = `Stage ${currentStage}`;
319
+ stageDiv.style.display = 'block';
320
+ setTimeout(() => {
321
+ stageDiv.style.display = 'none';
322
+ }, 3000);
323
+ }
324
+
325
+ // UI μ—…λ°μ΄νŠΈ
326
  function updateHealthBar() {
327
  const healthBar = document.getElementById('health');
328
+ healthBar.style.width = `${(playerHealth / MAX_HEALTH) * 100}%`;
329
  }
330
 
331
  function updateAmmoDisplay() {
332
  document.getElementById('ammo').textContent = `Ammo: ${ammo}/${maxAmmo}`;
333
  }
334
 
335
+ function updateGameTimer() {
336
+ if (!gameStartTime) return;
337
+
338
+ const elapsed = Math.floor((Date.now() - gameStartTime) / 1000);
339
+ const remaining = GAME_DURATION - elapsed;
340
+
341
+ if (remaining <= 0) {
342
+ gameOver(true);
343
+ return;
344
+ }
345
+
346
+ const minutes = Math.floor(remaining / 60);
347
+ const seconds = remaining % 60;
348
+ document.getElementById('gameTimer').textContent =
349
+ `Time: ${minutes}:${seconds.toString().padStart(2, '0')}`;
350
+ }
351
+
352
  // 재μž₯μ „
353
  function reload() {
354
  ammo = maxAmmo;
 
357
 
358
  // μ§€ν˜• 생성
359
  function createTerrain() {
360
+ const geometry = new THREE.PlaneGeometry(500, 500, 100, 100);
361
  const material = new THREE.MeshStandardMaterial({
362
  color: 0x3a8c3a,
363
  roughness: 0.6,
 
366
 
367
  const vertices = geometry.attributes.position.array;
368
  for (let i = 0; i < vertices.length; i += 3) {
369
+ vertices[i + 2] = Math.cos(vertices[i] * 0.02) * Math.sin(vertices[i + 1] * 0.02) * 5;
370
  }
371
  geometry.attributes.position.needsUpdate = true;
372
  geometry.computeVertexNormals();
 
380
  // 적 λ‘œλ“œ
381
  function loadEnemies() {
382
  const loader = new GLTFLoader();
383
+ const enemiesCount = 3 + currentStage;
384
+
385
+ for (let i = 0; i < enemiesCount; i++) {
386
+ const modelPath = ENEMY_MODELS[i % ENEMY_MODELS.length];
387
+ loader.load(modelPath, (gltf) => {
388
+ const enemy = gltf.scene;
389
+ enemy.scale.set(ENEMY_SCALE, ENEMY_SCALE, ENEMY_SCALE);
390
 
391
+ const angle = (i / enemiesCount) * Math.PI * 2;
392
+ const radius = 100;
393
  enemy.position.set(
394
  Math.cos(angle) * radius,
395
+ 5,
396
  Math.sin(angle) * radius
397
  );
398
 
399
  enemy.traverse((node) => {
400
  if (node.isMesh) {
401
+ node.material = node.material.clone();
402
  node.material.metalness = 0.3;
403
  node.material.roughness = 0.5;
404
  node.castShadow = true;
405
+ node
406
+
407
+
408
+
409
+ node.material.emissive = new THREE.Color(0x444444);
410
+ node.material.emissiveIntensity = 0.2;
411
  }
412
  });
413
 
 
415
  enemies.push({
416
  model: enemy,
417
  health: 100,
418
+ speed: 0.2 + (currentStage * 0.05),
419
  velocity: new THREE.Vector3(),
420
+ lastShot: 0,
421
+ moveTarget: new THREE.Vector3()
422
  });
423
+
424
+ updateMoveTarget(enemies[enemies.length - 1]);
425
+ });
426
+ }
427
+ }
428
+
429
+ // 적의 μƒˆλ‘œμš΄ 이동 λͺ©ν‘œμ  μ„€μ •
430
+ function updateMoveTarget(enemy) {
431
+ enemy.moveTarget.set(
432
+ (Math.random() - 0.5) * 400,
433
+ 5,
434
+ (Math.random() - 0.5) * 400
435
+ );
436
  }
437
 
438
  // 컨트둀 μ΄ˆκΈ°ν™”
 
446
  right: false
447
  };
448
 
 
449
  document.addEventListener('keydown', (event) => {
450
  switch(event.code) {
451
  case 'KeyW': moveState.forward = true; break;
 
465
  }
466
  });
467
 
 
468
  document.addEventListener('click', () => {
469
  if (controls.isLocked) {
470
  shoot();
471
  } else {
472
  controls.lock();
473
+ if (!gameStartTime) {
474
+ gameStartTime = Date.now();
475
+ sounds.bgm.play();
476
+ }
477
  }
478
  });
479
 
480
  controls.addEventListener('lock', () => {
481
  document.getElementById('info').style.display = 'none';
 
 
 
482
  });
483
 
484
  controls.addEventListener('unlock', () => {
 
492
  function updateEnemies() {
493
  if (!isSafePeriod) {
494
  enemies.forEach(enemy => {
495
+ // λͺ©ν‘œμ κΉŒμ§€μ˜ λ°©ν–₯ 계산
496
+ const toTarget = new THREE.Vector3();
497
+ toTarget.subVectors(enemy.moveTarget, enemy.model.position);
498
+
499
+ // λͺ©ν‘œμ μ— 거의 λ„λ‹¬ν–ˆμœΌλ©΄ μƒˆ λͺ©ν‘œμ  μ„€μ •
500
+ if (toTarget.length() < 5) {
501
+ updateMoveTarget(enemy);
502
+ }
503
+
504
+ // 이동 λ°©ν–₯ κ²°μ • (λͺ©ν‘œμ  80%, ν”Œλ ˆμ΄μ–΄ λ°©ν–₯ 20%)
505
+ const toPlayer = new THREE.Vector3();
506
+ toPlayer.subVectors(camera.position, enemy.model.position);
507
+
508
  const direction = new THREE.Vector3();
509
+ direction.addScaledVector(toTarget.normalize(), 0.8);
510
+ direction.addScaledVector(toPlayer.normalize(), 0.2);
511
  direction.normalize();
512
 
513
+ // 속도 적용
514
  enemy.velocity.lerp(direction.multiplyScalar(enemy.speed), 0.02);
515
  enemy.model.position.add(enemy.velocity);
516
 
 
519
 
520
  // λ°œμ‚¬ 둜직
521
  const now = Date.now();
522
+ const fireInterval = 2000 - (currentStage * 200); // μŠ€ν…Œμ΄μ§€κ°€ 올라갈수둝 λ°œμ‚¬ 간격 κ°μ†Œ
523
+ if (now - enemy.lastShot > fireInterval) {
524
  enemyShoot(enemy);
525
  enemy.lastShot = now;
526
  }
527
  });
528
  }
529
+ }
 
530
 
531
  // κ²Œμž„ μ˜€λ²„ 처리
532
  function gameOver(won) {
533
  if (!isGameOver) {
534
  isGameOver = true;
535
  controls.unlock();
536
+ sounds.bgm.pause();
537
  setTimeout(() => {
538
+ alert(won ? 'Mission Accomplished! You survived!' : 'Game Over! You were eliminated!');
539
  location.reload();
540
  }, 100);
541
  }
 
547
  createTerrain();
548
  loadEnemies();
549
  const moveState = initControls();
550
+ showStageMessage();
551
 
552
  // κ²Œμž„ 루프
553
  function animate() {
554
  requestAnimationFrame(animate);
555
 
556
  if (controls.isLocked && !isGameOver) {
 
 
 
 
 
 
 
 
 
 
 
 
557
  // 이동 처리
558
+ const speed = 0.5;
559
  if (moveState.forward) controls.moveForward(speed);
560
  if (moveState.backward) controls.moveForward(-speed);
561
  if (moveState.left) controls.moveRight(-speed);
 
567
  // 적 μ—…λ°μ΄νŠΈ
568
  updateEnemies();
569
 
570
+ // κ²Œμž„ 타이머 μ—…λ°μ΄νŠΈ
571
+ updateGameTimer();
 
 
572
  }
573
 
574
  renderer.render(scene, camera);
575
  }
576
 
577
+ // ν™”λ©΄ 크기 쑰절 처리
578
+ window.addEventListener('resize', () => {
579
+ camera.aspect = window.innerWidth / window.innerHeight;
580
+ camera.updateProjectionMatrix();
581
+ renderer.setSize(window.innerWidth, window.innerHeight);
582
+ });
583
+
584
  animate();
585
  }
586
 
 
 
 
 
 
 
 
587
  // κ²Œμž„ μ‹œμž‘
588
  init();
589
  </script>
590
  </body>
591
+ </html>