cutechicken commited on
Commit
6fd82de
โ€ข
1 Parent(s): 328bda1

Update game.js

Browse files
Files changed (1) hide show
  1. game.js +149 -50
game.js CHANGED
@@ -5,7 +5,7 @@ import { PointerLockControls } from 'three/addons/controls/PointerLockControls.j
5
  // ๊ฒŒ์ž„ ์ƒ์ˆ˜
6
  const GAME_DURATION = 180;
7
  const MAP_SIZE = 2000;
8
- const HELICOPTER_HEIGHT = 30;
9
  const ENEMY_GROUND_HEIGHT = 0;
10
  const ENEMY_SCALE = 10;
11
  const MAX_HEALTH = 1000;
@@ -19,8 +19,89 @@ const ENEMY_CONFIG = {
19
  BULLET_SPEED: 2
20
  };
21
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
  // ๊ฒŒ์ž„ ๋ณ€์ˆ˜
23
  let scene, camera, renderer, controls;
 
24
  let enemies = [];
25
  let bullets = [];
26
  let enemyBullets = [];
@@ -30,7 +111,6 @@ let currentStage = 1;
30
  let isGameOver = false;
31
  let lastTime = performance.now();
32
  let lastRender = 0;
33
-
34
  // ์˜ค์‹ค๋ ˆ์ดํ„ฐ ๊ธฐ๋ฐ˜ ์ด์†Œ๋ฆฌ ์ƒ์„ฑ๊ธฐ
35
  class GunSoundGenerator {
36
  constructor() {
@@ -110,7 +190,7 @@ async function init() {
110
 
111
  // Camera ์„ค์ •
112
  camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
113
- camera.position.set(0, HELICOPTER_HEIGHT, 0);
114
 
115
  // ๊ธฐ๋ณธ ์กฐ๋ช…
116
  scene.add(new THREE.AmbientLight(0xffffff, 0.6));
@@ -128,9 +208,10 @@ async function init() {
128
  // ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ
129
  setupEventListeners();
130
 
131
- // ๋ชจ๋ธ ํ…Œ์ŠคํŠธ ๋จผ์ € ์‹คํ–‰
132
- await testModelLoading();
133
-
 
134
  // ๊ฒŒ์ž„ ์š”์†Œ ์ดˆ๊ธฐํ™”
135
  await Promise.all([
136
  createTerrain(),
@@ -154,9 +235,18 @@ function setupEventListeners() {
154
  document.addEventListener('click', onClick);
155
  document.addEventListener('keydown', onKeyDown);
156
  document.addEventListener('keyup', onKeyUp);
 
157
  window.addEventListener('resize', onWindowResize);
158
  }
159
 
 
 
 
 
 
 
 
 
160
  async function testModelLoading() {
161
  const loader = new GLTFLoader();
162
  try {
@@ -241,7 +331,6 @@ async function createEnemies() {
241
  scene.add(tempEnemy.model);
242
  enemies.push(tempEnemy);
243
 
244
- // GLB ๋ชจ๋ธ ๋กœ๋“œ
245
  try {
246
  const modelIndex = i % 4 + 1;
247
  const modelPath = `models/enemy${modelIndex}.glb`;
@@ -250,11 +339,9 @@ async function createEnemies() {
250
  const gltf = await loader.loadAsync(modelPath);
251
  const model = gltf.scene;
252
 
253
- // ๋ชจ๋ธ ์„ค์ •
254
  model.scale.set(ENEMY_SCALE, ENEMY_SCALE, ENEMY_SCALE);
255
  model.position.copy(position);
256
 
257
- // ๋ชจ๋ธ ์žฌ์งˆ ๋ฐ ๊ทธ๋ฆผ์ž ์„ค์ •
258
  model.traverse((node) => {
259
  if (node.isMesh) {
260
  node.castShadow = true;
@@ -264,7 +351,6 @@ async function createEnemies() {
264
  }
265
  });
266
 
267
- // ์ž„์‹œ ๋ชจ๋ธ ๊ต์ฒด
268
  scene.remove(tempEnemy.model);
269
  scene.add(model);
270
  enemies[enemies.indexOf(tempEnemy)].model = model;
@@ -296,7 +382,6 @@ function createTemporaryEnemy(position) {
296
  lastAttackTime: 0
297
  };
298
  }
299
-
300
  function createExplosion(position) {
301
  const particles = [];
302
  for (let i = 0; i < PARTICLE_COUNT; i++) {
@@ -355,11 +440,27 @@ function onClick() {
355
  }
356
 
357
  function onKeyDown(event) {
 
 
358
  switch(event.code) {
359
- case 'KeyW': moveState.forward = true; break;
360
- case 'KeyS': moveState.backward = true; break;
361
- case 'KeyA': moveState.left = true; break;
362
- case 'KeyD': moveState.right = true; break;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
363
  case 'KeyR': reload(); break;
364
  }
365
  }
@@ -388,7 +489,7 @@ const moveState = {
388
  };
389
 
390
  function shoot() {
391
- if (ammo <= 0) return;
392
 
393
  ammo--;
394
  updateAmmoDisplay();
@@ -400,7 +501,7 @@ function shoot() {
400
 
401
  // ์ด๊ตฌ ํ™”์—ผ ํšจ๊ณผ
402
  const muzzleFlash = new THREE.PointLight(0xffff00, 3, 10);
403
- muzzleFlash.position.copy(camera.position);
404
  scene.add(muzzleFlash);
405
  setTimeout(() => scene.remove(muzzleFlash), 50);
406
  }
@@ -415,15 +516,15 @@ function createBullet() {
415
  })
416
  );
417
 
418
- bullet.position.copy(camera.position);
419
- const direction = new THREE.Vector3();
420
- camera.getWorldDirection(direction);
 
421
  bullet.velocity = direction.multiplyScalar(5);
422
 
423
  scene.add(bullet);
424
  return bullet;
425
  }
426
-
427
  function createEnemyBullet(enemy) {
428
  const bullet = new THREE.Mesh(
429
  new THREE.SphereGeometry(0.5),
@@ -438,7 +539,7 @@ function createEnemyBullet(enemy) {
438
  bullet.position.y += 5;
439
 
440
  const direction = new THREE.Vector3();
441
- direction.subVectors(camera.position, enemy.model.position).normalize();
442
  bullet.velocity = direction.multiplyScalar(ENEMY_CONFIG.BULLET_SPEED);
443
 
444
  scene.add(bullet);
@@ -446,19 +547,14 @@ function createEnemyBullet(enemy) {
446
  }
447
 
448
  function updateMovement() {
449
- if (controls.isLocked) {
450
- const speed = 2.0;
451
- if (moveState.forward) controls.moveForward(speed);
452
- if (moveState.backward) controls.moveForward(-speed);
453
- if (moveState.left) controls.moveRight(-speed);
454
- if (moveState.right) controls.moveRight(speed);
455
-
456
- // ๊ณ ๋„ ์ œํ•œ
457
- if (camera.position.y < HELICOPTER_HEIGHT) {
458
- camera.position.y = HELICOPTER_HEIGHT;
459
- } else if (camera.position.y > HELICOPTER_HEIGHT + 10) {
460
- camera.position.y = HELICOPTER_HEIGHT + 10;
461
- }
462
  }
463
  }
464
 
@@ -490,7 +586,7 @@ function updateBullets() {
490
  }
491
 
492
  // ๋ฒ”์œ„ ๋ฒ—์–ด๋‚œ ์ด์•Œ ์ œ๊ฑฐ
493
- if (bullets[i] && bullets[i].position.distanceTo(camera.position) > 1000) {
494
  scene.remove(bullets[i]);
495
  bullets.splice(i, 1);
496
  }
@@ -503,7 +599,7 @@ function updateEnemyBullets() {
503
 
504
  enemyBullets[i].position.add(enemyBullets[i].velocity);
505
 
506
- if (enemyBullets[i].position.distanceTo(camera.position) < 3) {
507
  playerHealth -= 10;
508
  updateHealthBar();
509
  createExplosion(enemyBullets[i].position.clone());
@@ -516,7 +612,7 @@ function updateEnemyBullets() {
516
  continue;
517
  }
518
 
519
- if (enemyBullets[i].position.distanceTo(camera.position) > 1000) {
520
  scene.remove(enemyBullets[i]);
521
  enemyBullets.splice(i, 1);
522
  }
@@ -531,7 +627,7 @@ function updateEnemies() {
531
 
532
  // ์  ์ด๋™ ๋กœ์ง
533
  const direction = new THREE.Vector3();
534
- direction.subVectors(camera.position, enemy.model.position);
535
  direction.y = 0;
536
  direction.normalize();
537
 
@@ -542,13 +638,13 @@ function updateEnemies() {
542
 
543
  // ์ ์ด ํ”Œ๋ ˆ์ด์–ด๋ฅผ ๋ฐ”๋ผ๋ณด๋„๋ก ์„ค์ •
544
  enemy.model.lookAt(new THREE.Vector3(
545
- camera.position.x,
546
  enemy.model.position.y,
547
- camera.position.z
548
  ));
549
 
550
  // ๊ณต๊ฒฉ ๋กœ์ง
551
- const distanceToPlayer = enemy.model.position.distanceTo(camera.position);
552
  if (distanceToPlayer < ENEMY_CONFIG.ATTACK_RANGE &&
553
  currentTime - enemy.lastAttackTime > ENEMY_CONFIG.ATTACK_INTERVAL) {
554
 
@@ -563,7 +659,6 @@ function updateEnemies() {
563
  }
564
  });
565
  }
566
-
567
  function reload() {
568
  ammo = 30;
569
  updateAmmoDisplay();
@@ -579,9 +674,11 @@ function updateHealthBar() {
579
  healthElement.style.width = `${healthPercentage}%`;
580
  }
581
 
582
- function updateHelicopterHUD() {
 
 
583
  document.querySelector('#altitude-indicator span').textContent =
584
- Math.round(camera.position.y);
585
 
586
  const speed = Math.round(
587
  Math.sqrt(
@@ -592,7 +689,7 @@ function updateHelicopterHUD() {
592
  document.querySelector('#speed-indicator span').textContent = speed;
593
 
594
  const heading = Math.round(
595
- (camera.rotation.y * (180 / Math.PI) + 360) % 360
596
  );
597
  document.querySelector('#compass span').textContent = heading;
598
 
@@ -600,17 +697,19 @@ function updateHelicopterHUD() {
600
  }
601
 
602
  function updateRadar() {
 
 
603
  const radarTargets = document.querySelector('.radar-targets');
604
  radarTargets.innerHTML = '';
605
 
606
  enemies.forEach(enemy => {
607
  if (!enemy || !enemy.model) return;
608
 
609
- const relativePos = enemy.model.position.clone().sub(camera.position);
610
  const distance = relativePos.length();
611
 
612
  if (distance < 500) {
613
- const playerAngle = camera.rotation.y;
614
  const enemyAngle = Math.atan2(relativePos.x, relativePos.z);
615
  const relativeAngle = enemyAngle - playerAngle;
616
 
@@ -665,7 +764,6 @@ function gameOver(won) {
665
  function gameLoop(timestamp) {
666
  requestAnimationFrame(gameLoop);
667
 
668
- // ํ”„๋ ˆ์ž„ ์ œํ•œ (60fps)
669
  if (timestamp - lastRender < 16) {
670
  return;
671
  }
@@ -676,7 +774,7 @@ function gameLoop(timestamp) {
676
  updateBullets();
677
  updateEnemies();
678
  updateEnemyBullets();
679
- updateHelicopterHUD();
680
  checkGameStatus();
681
  }
682
 
@@ -725,5 +823,6 @@ window.debugGame = {
725
  camera,
726
  enemies,
727
  gunSound,
 
728
  reloadEnemies: createEnemies
729
  };
 
5
  // ๊ฒŒ์ž„ ์ƒ์ˆ˜
6
  const GAME_DURATION = 180;
7
  const MAP_SIZE = 2000;
8
+ const TANK_HEIGHT = 2.0; // ์ „์ฐจ ๋†’์ด๋กœ ๋ณ€๊ฒฝ
9
  const ENEMY_GROUND_HEIGHT = 0;
10
  const ENEMY_SCALE = 10;
11
  const MAX_HEALTH = 1000;
 
19
  BULLET_SPEED: 2
20
  };
21
 
22
+ // TankPlayer ํด๋ž˜์Šค ์ •์˜
23
+ class TankPlayer {
24
+ constructor() {
25
+ this.body = null; // ์ „์ฐจ ๋ชธ์ฒด
26
+ this.turret = null; // ์ „์ฐจ ํฌํƒ‘
27
+ this.position = new THREE.Vector3(0, 0, 0);
28
+ this.rotation = new THREE.Euler(0, 0, 0);
29
+ this.turretRotation = 0;
30
+ this.moveSpeed = 0.5;
31
+ this.turnSpeed = 0.03;
32
+ }
33
+
34
+ async initialize(scene, loader) {
35
+ try {
36
+ // ๋ชธ์ฒด ๋กœ๋“œ
37
+ const bodyResult = await loader.loadAsync('/models/abramsBody.glb');
38
+ this.body = bodyResult.scene;
39
+ this.body.position.copy(this.position);
40
+
41
+ // ํฌํƒ‘ ๋กœ๋“œ
42
+ const turretResult = await loader.loadAsync('/models/abramsTurret.glb');
43
+ this.turret = turretResult.scene;
44
+
45
+ // ํฌํƒ‘ ์œ„์น˜ ์กฐ์ • (๋ชธ์ฒด ์œ„์— ์˜ฌ๋ฆผ)
46
+ this.turret.position.y = TANK_HEIGHT;
47
+
48
+ // ๊ทธ๋ฆผ์ž ์„ค์ •
49
+ this.body.traverse((child) => {
50
+ if (child.isMesh) {
51
+ child.castShadow = true;
52
+ child.receiveShadow = true;
53
+ }
54
+ });
55
+
56
+ this.turret.traverse((child) => {
57
+ if (child.isMesh) {
58
+ child.castShadow = true;
59
+ child.receiveShadow = true;
60
+ }
61
+ });
62
+
63
+ // ์”ฌ์— ์ถ”๊ฐ€
64
+ this.body.add(this.turret);
65
+ scene.add(this.body);
66
+
67
+ } catch (error) {
68
+ console.error('Error loading tank models:', error);
69
+ }
70
+ }
71
+
72
+ update(mouseX, mouseY) {
73
+ if (!this.body || !this.turret) return;
74
+
75
+ // ๋งˆ์šฐ์Šค ์œ„์น˜๋ฅผ ์ด์šฉํ•œ ํฌํƒ‘ ํšŒ์ „ ๊ณ„์‚ฐ
76
+ const mousePosition = new THREE.Vector2(mouseX, mouseY);
77
+ const targetAngle = Math.atan2(mousePosition.x, mousePosition.y);
78
+
79
+ // ํฌํƒ‘ ๋ถ€๋“œ๋Ÿฌ์šด ํšŒ์ „
80
+ const currentRotation = this.turret.rotation.y;
81
+ const rotationDiff = targetAngle - currentRotation;
82
+ this.turret.rotation.y += rotationDiff * 0.1;
83
+ }
84
+
85
+ move(direction) {
86
+ if (!this.body) return;
87
+
88
+ const moveVector = new THREE.Vector3();
89
+ moveVector.x = direction.x * this.moveSpeed;
90
+ moveVector.z = direction.z * this.moveSpeed;
91
+
92
+ moveVector.applyEuler(this.body.rotation);
93
+ this.body.position.add(moveVector);
94
+ }
95
+
96
+ rotate(angle) {
97
+ if (!this.body) return;
98
+ this.body.rotation.y += angle * this.turnSpeed;
99
+ }
100
+ }
101
+
102
  // ๊ฒŒ์ž„ ๋ณ€์ˆ˜
103
  let scene, camera, renderer, controls;
104
+ let player; // ์ „์ฐจ ํ”Œ๋ ˆ์ด์–ด ์ธ์Šคํ„ด์Šค
105
  let enemies = [];
106
  let bullets = [];
107
  let enemyBullets = [];
 
111
  let isGameOver = false;
112
  let lastTime = performance.now();
113
  let lastRender = 0;
 
114
  // ์˜ค์‹ค๋ ˆ์ดํ„ฐ ๊ธฐ๋ฐ˜ ์ด์†Œ๋ฆฌ ์ƒ์„ฑ๊ธฐ
115
  class GunSoundGenerator {
116
  constructor() {
 
190
 
191
  // Camera ์„ค์ •
192
  camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
193
+ camera.position.set(0, TANK_HEIGHT + 5, -10); // ์นด๋ฉ”๋ผ ์œ„์น˜ ์ „์ฐจ์— ๋งž๊ฒŒ ์กฐ์ •
194
 
195
  // ๊ธฐ๋ณธ ์กฐ๋ช…
196
  scene.add(new THREE.AmbientLight(0xffffff, 0.6));
 
208
  // ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ
209
  setupEventListeners();
210
 
211
+ // ํ”Œ๋ ˆ์ด์–ด ์ดˆ๊ธฐํ™”
212
+ player = new TankPlayer();
213
+ await player.initialize(scene, new GLTFLoader());
214
+
215
  // ๊ฒŒ์ž„ ์š”์†Œ ์ดˆ๊ธฐํ™”
216
  await Promise.all([
217
  createTerrain(),
 
235
  document.addEventListener('click', onClick);
236
  document.addEventListener('keydown', onKeyDown);
237
  document.addEventListener('keyup', onKeyUp);
238
+ document.addEventListener('mousemove', onMouseMove);
239
  window.addEventListener('resize', onWindowResize);
240
  }
241
 
242
+ // ๋งˆ์šฐ์Šค ์ด๋™ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ ์ถ”๊ฐ€
243
+ function onMouseMove(event) {
244
+ if (controls.isLocked && player) {
245
+ const mouseX = (event.clientX / window.innerWidth) * 2 - 1;
246
+ const mouseY = -(event.clientY / window.innerHeight) * 2 + 1;
247
+ player.update(mouseX, mouseY);
248
+ }
249
+ }
250
  async function testModelLoading() {
251
  const loader = new GLTFLoader();
252
  try {
 
331
  scene.add(tempEnemy.model);
332
  enemies.push(tempEnemy);
333
 
 
334
  try {
335
  const modelIndex = i % 4 + 1;
336
  const modelPath = `models/enemy${modelIndex}.glb`;
 
339
  const gltf = await loader.loadAsync(modelPath);
340
  const model = gltf.scene;
341
 
 
342
  model.scale.set(ENEMY_SCALE, ENEMY_SCALE, ENEMY_SCALE);
343
  model.position.copy(position);
344
 
 
345
  model.traverse((node) => {
346
  if (node.isMesh) {
347
  node.castShadow = true;
 
351
  }
352
  });
353
 
 
354
  scene.remove(tempEnemy.model);
355
  scene.add(model);
356
  enemies[enemies.indexOf(tempEnemy)].model = model;
 
382
  lastAttackTime: 0
383
  };
384
  }
 
385
  function createExplosion(position) {
386
  const particles = [];
387
  for (let i = 0; i < PARTICLE_COUNT; i++) {
 
440
  }
441
 
442
  function onKeyDown(event) {
443
+ if (!player) return;
444
+
445
  switch(event.code) {
446
+ case 'KeyW':
447
+ moveState.forward = true;
448
+ const forwardDir = new THREE.Vector3(0, 0, -1);
449
+ player.move(forwardDir);
450
+ break;
451
+ case 'KeyS':
452
+ moveState.backward = true;
453
+ const backwardDir = new THREE.Vector3(0, 0, 1);
454
+ player.move(backwardDir);
455
+ break;
456
+ case 'KeyA':
457
+ moveState.left = true;
458
+ player.rotate(1);
459
+ break;
460
+ case 'KeyD':
461
+ moveState.right = true;
462
+ player.rotate(-1);
463
+ break;
464
  case 'KeyR': reload(); break;
465
  }
466
  }
 
489
  };
490
 
491
  function shoot() {
492
+ if (ammo <= 0 || !player || !player.turret) return;
493
 
494
  ammo--;
495
  updateAmmoDisplay();
 
501
 
502
  // ์ด๊ตฌ ํ™”์—ผ ํšจ๊ณผ
503
  const muzzleFlash = new THREE.PointLight(0xffff00, 3, 10);
504
+ muzzleFlash.position.copy(player.turret.position);
505
  scene.add(muzzleFlash);
506
  setTimeout(() => scene.remove(muzzleFlash), 50);
507
  }
 
516
  })
517
  );
518
 
519
+ // ํฌํƒ‘ ์œ„์น˜์—์„œ ์ด์•Œ ๋ฐœ์‚ฌ
520
+ bullet.position.copy(player.turret.position);
521
+ const direction = new THREE.Vector3(0, 0, -1);
522
+ direction.applyQuaternion(player.turret.quaternion);
523
  bullet.velocity = direction.multiplyScalar(5);
524
 
525
  scene.add(bullet);
526
  return bullet;
527
  }
 
528
  function createEnemyBullet(enemy) {
529
  const bullet = new THREE.Mesh(
530
  new THREE.SphereGeometry(0.5),
 
539
  bullet.position.y += 5;
540
 
541
  const direction = new THREE.Vector3();
542
+ direction.subVectors(player.body.position, enemy.model.position).normalize();
543
  bullet.velocity = direction.multiplyScalar(ENEMY_CONFIG.BULLET_SPEED);
544
 
545
  scene.add(bullet);
 
547
  }
548
 
549
  function updateMovement() {
550
+ if (controls.isLocked && !isGameOver && player) {
551
+ // ์นด๋ฉ”๋ผ ์œ„์น˜ ์—…๋ฐ์ดํŠธ
552
+ const cameraOffset = new THREE.Vector3(0, TANK_HEIGHT + 5, -15);
553
+ cameraOffset.applyQuaternion(player.body.quaternion);
554
+ camera.position.copy(player.body.position).add(cameraOffset);
555
+
556
+ // ์นด๋ฉ”๋ผ๊ฐ€ ํ”Œ๋ ˆ์ด์–ด๋ฅผ ๋ฐ”๋ผ๋ณด๋„๋ก ์„ค์ •
557
+ camera.lookAt(player.body.position);
 
 
 
 
 
558
  }
559
  }
560
 
 
586
  }
587
 
588
  // ๋ฒ”์œ„ ๋ฒ—์–ด๋‚œ ์ด์•Œ ์ œ๊ฑฐ
589
+ if (bullets[i] && bullets[i].position.distanceTo(player.body.position) > 1000) {
590
  scene.remove(bullets[i]);
591
  bullets.splice(i, 1);
592
  }
 
599
 
600
  enemyBullets[i].position.add(enemyBullets[i].velocity);
601
 
602
+ if (enemyBullets[i].position.distanceTo(player.body.position) < 3) {
603
  playerHealth -= 10;
604
  updateHealthBar();
605
  createExplosion(enemyBullets[i].position.clone());
 
612
  continue;
613
  }
614
 
615
+ if (enemyBullets[i].position.distanceTo(player.body.position) > 1000) {
616
  scene.remove(enemyBullets[i]);
617
  enemyBullets.splice(i, 1);
618
  }
 
627
 
628
  // ์  ์ด๋™ ๋กœ์ง
629
  const direction = new THREE.Vector3();
630
+ direction.subVectors(player.body.position, enemy.model.position);
631
  direction.y = 0;
632
  direction.normalize();
633
 
 
638
 
639
  // ์ ์ด ํ”Œ๋ ˆ์ด์–ด๋ฅผ ๋ฐ”๋ผ๋ณด๋„๋ก ์„ค์ •
640
  enemy.model.lookAt(new THREE.Vector3(
641
+ player.body.position.x,
642
  enemy.model.position.y,
643
+ player.body.position.z
644
  ));
645
 
646
  // ๊ณต๊ฒฉ ๋กœ์ง
647
+ const distanceToPlayer = enemy.model.position.distanceTo(player.body.position);
648
  if (distanceToPlayer < ENEMY_CONFIG.ATTACK_RANGE &&
649
  currentTime - enemy.lastAttackTime > ENEMY_CONFIG.ATTACK_INTERVAL) {
650
 
 
659
  }
660
  });
661
  }
 
662
  function reload() {
663
  ammo = 30;
664
  updateAmmoDisplay();
 
674
  healthElement.style.width = `${healthPercentage}%`;
675
  }
676
 
677
+ function updateTankHUD() {
678
+ if (!player || !player.body) return;
679
+
680
  document.querySelector('#altitude-indicator span').textContent =
681
+ Math.round(player.body.position.y);
682
 
683
  const speed = Math.round(
684
  Math.sqrt(
 
689
  document.querySelector('#speed-indicator span').textContent = speed;
690
 
691
  const heading = Math.round(
692
+ (player.body.rotation.y * (180 / Math.PI) + 360) % 360
693
  );
694
  document.querySelector('#compass span').textContent = heading;
695
 
 
697
  }
698
 
699
  function updateRadar() {
700
+ if (!player || !player.body) return;
701
+
702
  const radarTargets = document.querySelector('.radar-targets');
703
  radarTargets.innerHTML = '';
704
 
705
  enemies.forEach(enemy => {
706
  if (!enemy || !enemy.model) return;
707
 
708
+ const relativePos = enemy.model.position.clone().sub(player.body.position);
709
  const distance = relativePos.length();
710
 
711
  if (distance < 500) {
712
+ const playerAngle = player.body.rotation.y;
713
  const enemyAngle = Math.atan2(relativePos.x, relativePos.z);
714
  const relativeAngle = enemyAngle - playerAngle;
715
 
 
764
  function gameLoop(timestamp) {
765
  requestAnimationFrame(gameLoop);
766
 
 
767
  if (timestamp - lastRender < 16) {
768
  return;
769
  }
 
774
  updateBullets();
775
  updateEnemies();
776
  updateEnemyBullets();
777
+ updateTankHUD();
778
  checkGameStatus();
779
  }
780
 
 
823
  camera,
824
  enemies,
825
  gunSound,
826
+ player,
827
  reloadEnemies: createEnemies
828
  };