cutechicken commited on
Commit
485200d
β€’
1 Parent(s): bf18167

Update game.js

Browse files
Files changed (1) hide show
  1. game.js +297 -239
game.js CHANGED
@@ -7,7 +7,7 @@ const GAME_DURATION = 180;
7
  const MAP_SIZE = 1000;
8
  const TANK_HEIGHT = 0.5;
9
  const ENEMY_GROUND_HEIGHT = 0;
10
- const ENEMY_SCALE = 10;
11
  const MAX_HEALTH = 1000;
12
  const ENEMY_MOVE_SPEED = 0.1;
13
  const ENEMY_COUNT_MAX = 3;
@@ -572,200 +572,244 @@ class Enemy {
572
  constructor(scene, position, type = 'tank') {
573
  this.scene = scene;
574
  this.position = position;
575
- this.mesh = null;
576
  this.type = type;
577
  this.health = type === 'tank' ? 100 : 200;
578
  this.lastAttackTime = 0;
579
  this.bullets = [];
580
  this.isLoaded = false;
581
  this.moveSpeed = type === 'tank' ? ENEMY_MOVE_SPEED : ENEMY_MOVE_SPEED * 0.7;
 
 
 
 
 
582
  }
583
 
584
  async initialize(loader) {
585
- try {
586
- const modelPath = this.type === 'tank' ? '/models/t90.glb' : '/models/t90.glb';
587
- const result = await loader.loadAsync(modelPath);
588
- this.mesh = result.scene;
589
- this.mesh.position.copy(this.position);
590
- this.mesh.scale.set(ENEMY_SCALE, ENEMY_SCALE, ENEMY_SCALE);
591
-
592
- this.mesh.traverse((child) => {
593
- if (child.isMesh) {
594
- child.castShadow = true;
595
- child.receiveShadow = true;
596
- }
597
- });
598
-
599
- this.scene.add(this.mesh);
600
- this.isLoaded = true;
601
- } catch (error) {
602
- console.error('Error loading enemy model:', error);
603
- this.isLoaded = false;
604
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
605
  }
 
606
 
607
  update(playerPosition) {
608
- if (!this.mesh || !this.isLoaded) return;
609
 
610
- const direction = new THREE.Vector3()
611
- .subVectors(playerPosition, this.mesh.position)
612
- .normalize();
613
-
614
- const distanceToPlayer = this.mesh.position.distanceTo(playerPosition);
615
- const minDistance = 50;
616
-
617
- // 이전 μœ„μΉ˜ μ €μž₯
618
- const previousPosition = this.mesh.position.clone();
619
-
620
- if (distanceToPlayer > minDistance) {
621
- const moveVector = direction.multiplyScalar(this.moveSpeed);
622
- const newPosition = this.mesh.position.clone().add(moveVector);
623
-
624
- // μ§€ν˜• 높이 κ°€μ Έμ˜€κΈ°
625
- const heightAtNewPos = window.gameInstance.getHeightAtPosition(
626
- newPosition.x,
627
- newPosition.z
628
- );
629
- newPosition.y = heightAtNewPos + TANK_HEIGHT;
630
 
631
- // μž„μ‹œλ‘œ μœ„μΉ˜ μ΄λ™ν•˜μ—¬ 좩돌 체크
632
- const originalPosition = this.mesh.position.clone();
633
- this.mesh.position.copy(newPosition);
634
 
635
- // μž₯μ• λ¬Όκ³Ό 좩돌 체크
636
- const enemyBox = new THREE.Box3().setFromObject(this.mesh);
637
- let hasCollision = false;
638
 
639
- // λͺ¨λ“  μž₯애물에 λŒ€ν•΄ 좩돌 검사
640
- for (const obstacle of window.gameInstance.obstacles) {
641
- const obstacleBox = new THREE.Box3().setFromObject(obstacle);
642
- if (enemyBox.intersectsBox(obstacleBox)) {
643
- hasCollision = true;
644
- break;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
645
  }
646
- }
647
-
648
- // λ‹€λ₯Έ 적 νƒ±ν¬μ™€μ˜ 좩돌 검사
649
- if (!hasCollision) {
650
- for (const otherEnemy of window.gameInstance.enemies) {
651
- if (otherEnemy !== this && otherEnemy.mesh) {
652
- const otherEnemyBox = new THREE.Box3().setFromObject(otherEnemy.mesh);
653
- if (enemyBox.intersectsBox(otherEnemyBox)) {
654
- hasCollision = true;
655
- break;
656
  }
657
  }
658
  }
659
- }
660
-
661
- // 맡 경계 체크
662
- const mapBoundary = MAP_SIZE / 2;
663
- if (Math.abs(newPosition.x) > mapBoundary ||
664
- Math.abs(newPosition.z) > mapBoundary) {
665
- hasCollision = true;
666
- }
667
-
668
- // 좩돌이 있으면 이전 μœ„μΉ˜λ‘œ 볡귀, μ—†μœΌλ©΄ μƒˆ μœ„μΉ˜ μœ μ§€
669
- if (hasCollision) {
670
- this.mesh.position.copy(previousPosition);
671
 
672
- // 좩돌 μ‹œ 우회 경둜 μ‹œλ„
673
- const alternateDirections = [
674
- new THREE.Vector3(-direction.z, 0, direction.x), // μ™Όμͺ½μœΌλ‘œ 90도
675
- new THREE.Vector3(direction.z, 0, -direction.x), // 였λ₯Έμͺ½μœΌλ‘œ 90도
676
- new THREE.Vector3(-direction.x, 0, -direction.z) // 180도 νšŒμ „
677
- ];
678
 
679
- for (const altDirection of alternateDirections) {
680
- const altMoveVector = altDirection.multiplyScalar(this.moveSpeed);
681
- const altNewPosition = previousPosition.clone().add(altMoveVector);
682
-
683
- this.mesh.position.copy(altNewPosition);
684
- const altEnemyBox = new THREE.Box3().setFromObject(this.mesh);
685
 
686
- let altHasCollision = false;
 
 
 
 
 
687
 
688
- // μƒˆλ‘œμš΄ λ°©ν–₯에 λŒ€ν•œ 좩돌 검사
689
- for (const obstacle of window.gameInstance.obstacles) {
690
- const obstacleBox = new THREE.Box3().setFromObject(obstacle);
691
- if (altEnemyBox.intersectsBox(obstacleBox)) {
692
- altHasCollision = true;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
693
  break;
 
 
 
694
  }
695
  }
696
-
697
- if (!altHasCollision) {
698
- // 우회 κ²½λ‘œκ°€ κ°€λŠ₯ν•˜λ©΄ κ·Έ λ°©ν–₯으둜 이동
699
- break;
700
- } else {
701
- // μš°νšŒλ„ λΆˆκ°€λŠ₯ν•˜λ©΄ 이전 μœ„μΉ˜λ‘œ 볡귀
702
- this.mesh.position.copy(previousPosition);
703
- }
704
  }
705
- }
706
-
707
- // μ§€ν˜•μ— λ”°λ₯Έ 기울기 μ‘°μ •
708
- const forwardVector = new THREE.Vector3(0, 0, 1).applyQuaternion(this.mesh.quaternion);
709
- const rightVector = new THREE.Vector3(1, 0, 0).applyQuaternion(this.mesh.quaternion);
710
-
711
- const frontHeight = window.gameInstance.getHeightAtPosition(
712
- this.mesh.position.x + forwardVector.x,
713
- this.mesh.position.z + forwardVector.z
714
- );
715
- const backHeight = window.gameInstance.getHeightAtPosition(
716
- this.mesh.position.x - forwardVector.x,
717
- this.mesh.position.z - forwardVector.z
718
- );
719
- const rightHeight = window.gameInstance.getHeightAtPosition(
720
- this.mesh.position.x + rightVector.x,
721
- this.mesh.position.z + rightVector.z
722
- );
723
- const leftHeight = window.gameInstance.getHeightAtPosition(
724
- this.mesh.position.x - rightVector.x,
725
- this.mesh.position.z - rightVector.z
726
- );
727
-
728
- const pitch = Math.atan2(frontHeight - backHeight, 2);
729
- const roll = Math.atan2(rightHeight - leftHeight, 2);
730
-
731
- // ν˜„μž¬ νšŒμ „ μœ μ§€ν•˜λ©΄μ„œ 기울기만 적용
732
- const currentRotation = this.mesh.rotation.y;
733
- this.mesh.rotation.set(pitch, currentRotation, roll);
734
- }
735
-
736
- // ν”Œλ ˆμ΄μ–΄λ₯Ό ν–₯ν•΄ 포탑 νšŒμ „
737
- this.mesh.lookAt(playerPosition);
738
-
739
- // μ΄μ•Œ μ—…λ°μ΄νŠΈ
740
- if (this.bullets) {
741
- for (let i = this.bullets.length - 1; i >= 0; i--) {
742
- const bullet = this.bullets[i];
743
- bullet.position.add(bullet.velocity);
744
 
745
- // μ΄μ•Œμ΄ 맡 λ°–μœΌλ‘œ λ‚˜κ°€κ±°λ‚˜ μž₯μ• λ¬Όκ³Ό μΆ©λŒν•˜λ©΄ 제거
746
- if (Math.abs(bullet.position.x) > MAP_SIZE / 2 ||
747
- Math.abs(bullet.position.z) > MAP_SIZE / 2) {
748
- this.scene.remove(bullet);
749
- this.bullets.splice(i, 1);
750
- continue;
751
- }
752
 
753
- // μ΄μ•Œκ³Ό μž₯μ• λ¬Ό 좩돌 체크
754
- const bulletBox = new THREE.Box3().setFromObject(bullet);
755
- for (const obstacle of window.gameInstance.obstacles) {
756
- const obstacleBox = new THREE.Box3().setFromObject(obstacle);
757
- if (bulletBox.intersectsBox(obstacleBox)) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
758
  this.scene.remove(bullet);
759
  this.bullets.splice(i, 1);
760
- break;
 
 
 
 
 
 
 
 
 
 
 
761
  }
762
  }
763
  }
764
  }
765
- }
766
-
767
 
768
  shoot(playerPosition) {
 
 
769
  const currentTime = Date.now();
770
  const attackInterval = this.type === 'tank' ?
771
  ENEMY_CONFIG.ATTACK_INTERVAL :
@@ -773,16 +817,28 @@ class Enemy {
773
 
774
  if (currentTime - this.lastAttackTime < attackInterval) return;
775
 
 
776
  const bulletGeometry = new THREE.SphereGeometry(this.type === 'tank' ? 0.2 : 0.3);
777
  const bulletMaterial = new THREE.MeshBasicMaterial({
778
  color: this.type === 'tank' ? 0xff0000 : 0xff6600
779
  });
780
  const bullet = new THREE.Mesh(bulletGeometry, bulletMaterial);
781
 
782
- bullet.position.copy(this.mesh.position);
 
 
 
 
 
 
 
 
 
 
 
783
 
784
  const direction = new THREE.Vector3()
785
- .subVectors(playerPosition, this.mesh.position)
786
  .normalize();
787
 
788
  const bulletSpeed = this.type === 'tank' ?
@@ -802,8 +858,8 @@ class Enemy {
802
  }
803
 
804
  destroy() {
805
- if (this.mesh) {
806
- this.scene.remove(this.mesh);
807
  this.bullets.forEach(bullet => this.scene.remove(bullet));
808
  this.bullets = [];
809
  this.isLoaded = false;
@@ -1078,56 +1134,56 @@ class Game {
1078
 
1079
  // λ ˆμ΄λ” μ—…λ°μ΄νŠΈ λ©”μ„œλ“œ μΆ”κ°€
1080
  updateRadar() {
1081
- const currentTime = Date.now();
1082
- if (currentTime - this.lastRadarUpdate < this.radarUpdateInterval) return;
1083
-
1084
- const radar = document.getElementById('radar');
1085
- const radarRect = radar.getBoundingClientRect();
1086
- const radarCenter = {
1087
- x: radarRect.width / 2,
1088
- y: radarRect.height / 2
1089
- };
1090
 
1091
- // κΈ°μ‘΄ 적 λ„νŠΈ 제거
1092
- const oldDots = radar.getElementsByClassName('enemy-dot');
1093
- while (oldDots[0]) {
1094
- oldDots[0].remove();
1095
- }
1096
 
1097
- // 탱크 μœ„μΉ˜ κ°€μ Έμ˜€κΈ°
1098
- const tankPos = this.tank.getPosition();
1099
 
1100
- // λͺ¨λ“  적에 λŒ€ν•΄ λ ˆμ΄λ”μ— ν‘œμ‹œ
1101
- this.enemies.forEach(enemy => {
1102
- if (!enemy.mesh || !enemy.isLoaded) return;
1103
 
1104
- const enemyPos = enemy.mesh.position;
1105
- const distance = tankPos.distanceTo(enemyPos);
1106
 
1107
- // λ ˆμ΄λ” λ²”μœ„ 내에 μžˆλŠ” 경우만 ν‘œμ‹œ
1108
- if (distance <= this.radarRange) {
1109
- // 탱크 κΈ°μ€€ μƒλŒ€ 각도 계산
1110
- const angle = Math.atan2(
1111
- enemyPos.x - tankPos.x,
1112
- enemyPos.z - tankPos.z
1113
- );
1114
 
1115
- // μƒλŒ€ 거리λ₯Ό λ ˆμ΄λ” 크기에 맞게 μŠ€μΌ€μΌλ§
1116
- const relativeDistance = distance / this.radarRange;
1117
- const dotX = radarCenter.x + Math.sin(angle) * (radarCenter.x * relativeDistance);
1118
- const dotY = radarCenter.y + Math.cos(angle) * (radarCenter.y * relativeDistance);
1119
-
1120
- // 적 λ„νŠΈ 생성 및 μΆ”κ°€
1121
- const dot = document.createElement('div');
1122
- dot.className = 'enemy-dot';
1123
- dot.style.left = `${dotX}px`;
1124
- dot.style.top = `${dotY}px`;
1125
- radar.appendChild(dot);
1126
- }
1127
- });
1128
 
1129
- this.lastRadarUpdate = currentTime;
1130
- }
1131
 
1132
  async addDesertDecorations() {
1133
  if (!this.obstacles) {
@@ -1839,45 +1895,47 @@ this.enemies.forEach(enemy => {
1839
  }
1840
 
1841
  animate() {
1842
- if (this.isGameOver) {
1843
- if (this.animationFrameId) {
1844
- cancelAnimationFrame(this.animationFrameId);
1845
- }
1846
- return;
1847
- }
1848
-
1849
- this.animationFrameId = requestAnimationFrame(() => this.animate());
1850
- // κ²Œμž„μ΄ μ‹œμž‘λ˜μ§€ μ•Šμ•˜μœΌλ©΄ λ Œλ”λ§λ§Œ μˆ˜ν–‰
1851
- if (!this.isStarted) {
1852
- this.renderer.render(this.scene, this.camera);
1853
- return;
1854
  }
 
 
1855
 
1856
- const currentTime = performance.now();
1857
- const deltaTime = (currentTime - this.lastTime) / 1000;
1858
- this.lastTime = currentTime;
 
 
 
1859
 
1860
- if (!this.isLoading) {
1861
- this.handleMovement();
1862
- this.tank.update(this.mouse.x, this.mouse.y, this.scene);
1863
-
1864
- const tankPosition = this.tank.getPosition();
1865
- this.enemies.forEach(enemy => {
1866
- enemy.update(tankPosition);
1867
-
1868
- if (enemy.isLoaded && enemy.mesh.position.distanceTo(tankPosition) < ENEMY_CONFIG.ATTACK_RANGE) {
1869
- enemy.shoot(tankPosition);
1870
- }
1871
- });
1872
 
1873
- this.updateParticles();
1874
- this.checkCollisions();
1875
- this.updateUI();
1876
- this.updateRadar(); // λ ˆμ΄λ” μ—…λ°μ΄νŠΈ μΆ”κ°€
1877
- }
1878
-
1879
- this.renderer.render(this.scene, this.camera);
1880
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1881
  }
1882
 
1883
  // Start game
 
7
  const MAP_SIZE = 1000;
8
  const TANK_HEIGHT = 0.5;
9
  const ENEMY_GROUND_HEIGHT = 0;
10
+ const ENEMY_SCALE = 1;
11
  const MAX_HEALTH = 1000;
12
  const ENEMY_MOVE_SPEED = 0.1;
13
  const ENEMY_COUNT_MAX = 3;
 
572
  constructor(scene, position, type = 'tank') {
573
  this.scene = scene;
574
  this.position = position;
 
575
  this.type = type;
576
  this.health = type === 'tank' ? 100 : 200;
577
  this.lastAttackTime = 0;
578
  this.bullets = [];
579
  this.isLoaded = false;
580
  this.moveSpeed = type === 'tank' ? ENEMY_MOVE_SPEED : ENEMY_MOVE_SPEED * 0.7;
581
+
582
+ // Add new properties for body and turret
583
+ this.body = null;
584
+ this.turret = null;
585
+ this.turretGroup = new THREE.Group();
586
  }
587
 
588
  async initialize(loader) {
589
+ console.log('Starting enemy tank initialization...');
590
+ try {
591
+ // Load body
592
+ console.log('Loading t90Body.glb...');
593
+ const bodyResult = await loader.loadAsync('/models/t90Body.glb');
594
+ console.log('t90Body.glb loaded successfully');
595
+ this.body = bodyResult.scene;
596
+
597
+ // Load turret
598
+ console.log('Loading t90Turret.glb...');
599
+ const turretResult = await loader.loadAsync('/models/t90Turret.glb');
600
+ console.log('t90Turret.glb loaded successfully');
601
+ this.turret = turretResult.scene;
602
+
603
+ console.log('Setting up turret group...');
604
+ this.turretGroup.position.y = 0.2;
605
+ this.turretGroup.add(this.turret);
606
+ this.body.add(this.turretGroup);
607
+
608
+ console.log('Applying transforms...');
609
+ this.body.position.copy(this.position);
610
+ this.body.scale.set(ENEMY_SCALE, ENEMY_SCALE, ENEMY_SCALE);
611
+
612
+ // Setup shadows
613
+ console.log('Setting up shadows...');
614
+ this.body.traverse((child) => {
615
+ if (child.isMesh) {
616
+ child.castShadow = true;
617
+ child.receiveShadow = true;
618
+ child.material.shadowSide = THREE.BackSide;
619
+ }
620
+ });
621
+
622
+ this.turret.traverse((child) => {
623
+ if (child.isMesh) {
624
+ child.castShadow = true;
625
+ child.receiveShadow = true;
626
+ child.material.shadowSide = THREE.BackSide;
627
+ }
628
+ });
629
+
630
+ console.log('Adding to scene...');
631
+ this.scene.add(this.body);
632
+ this.isLoaded = true;
633
+ console.log('Enemy tank initialization complete!');
634
+ } catch (error) {
635
+ console.error('Error loading enemy tank model:', error);
636
+ console.error('Error details:', {
637
+ message: error.message,
638
+ stack: error.stack,
639
+ name: error.name
640
+ });
641
+ this.isLoaded = false;
642
  }
643
+ }
644
 
645
  update(playerPosition) {
646
+ if (!this.body || !this.isLoaded) return;
647
 
648
+ const direction = new THREE.Vector3()
649
+ .subVectors(playerPosition, this.body.position)
650
+ .normalize();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
651
 
652
+ const distanceToPlayer = this.body.position.distanceTo(playerPosition);
653
+ const minDistance = 50;
 
654
 
655
+ // Store previous position
656
+ const previousPosition = this.body.position.clone();
 
657
 
658
+ if (distanceToPlayer > minDistance) {
659
+ const moveVector = direction.multiplyScalar(this.moveSpeed);
660
+ const newPosition = this.body.position.clone().add(moveVector);
661
+
662
+ // Get terrain height
663
+ const heightAtNewPos = window.gameInstance.getHeightAtPosition(
664
+ newPosition.x,
665
+ newPosition.z
666
+ );
667
+ newPosition.y = heightAtNewPos + TANK_HEIGHT;
668
+
669
+ // Move tank body
670
+ const originalPosition = this.body.position.clone();
671
+ this.body.position.copy(newPosition);
672
+
673
+ // Collision checks
674
+ const enemyBox = new THREE.Box3().setFromObject(this.body);
675
+ let hasCollision = false;
676
+
677
+ // Check collisions with obstacles
678
+ for (const obstacle of window.gameInstance.obstacles) {
679
+ const obstacleBox = new THREE.Box3().setFromObject(obstacle);
680
+ if (enemyBox.intersectsBox(obstacleBox)) {
681
+ hasCollision = true;
682
+ break;
683
+ }
684
  }
685
+
686
+ // Check collisions with other enemies
687
+ if (!hasCollision) {
688
+ for (const otherEnemy of window.gameInstance.enemies) {
689
+ if (otherEnemy !== this && otherEnemy.body) {
690
+ const otherEnemyBox = new THREE.Box3().setFromObject(otherEnemy.body);
691
+ if (enemyBox.intersectsBox(otherEnemyBox)) {
692
+ hasCollision = true;
693
+ break;
694
+ }
695
  }
696
  }
697
  }
 
 
 
 
 
 
 
 
 
 
 
 
698
 
699
+ // Check map boundaries
700
+ const mapBoundary = MAP_SIZE / 2;
701
+ if (Math.abs(newPosition.x) > mapBoundary ||
702
+ Math.abs(newPosition.z) > mapBoundary) {
703
+ hasCollision = true;
704
+ }
705
 
706
+ // Handle collision response
707
+ if (hasCollision) {
708
+ this.body.position.copy(previousPosition);
 
 
 
709
 
710
+ // Try alternate paths when collision occurs
711
+ const alternateDirections = [
712
+ new THREE.Vector3(-direction.z, 0, direction.x), // Left 90 degrees
713
+ new THREE.Vector3(direction.z, 0, -direction.x), // Right 90 degrees
714
+ new THREE.Vector3(-direction.x, 0, -direction.z) // 180 degrees
715
+ ];
716
 
717
+ for (const altDirection of alternateDirections) {
718
+ const altMoveVector = altDirection.multiplyScalar(this.moveSpeed);
719
+ const altNewPosition = previousPosition.clone().add(altMoveVector);
720
+
721
+ this.body.position.copy(altNewPosition);
722
+ const altEnemyBox = new THREE.Box3().setFromObject(this.body);
723
+
724
+ let altHasCollision = false;
725
+
726
+ // Check alternate path collisions
727
+ for (const obstacle of window.gameInstance.obstacles) {
728
+ const obstacleBox = new THREE.Box3().setFromObject(obstacle);
729
+ if (altEnemyBox.intersectsBox(obstacleBox)) {
730
+ altHasCollision = true;
731
+ break;
732
+ }
733
+ }
734
+
735
+ if (!altHasCollision) {
736
+ // Use alternate path if no collision
737
  break;
738
+ } else {
739
+ // Revert to previous position if alternate path also collides
740
+ this.body.position.copy(previousPosition);
741
  }
742
  }
 
 
 
 
 
 
 
 
743
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
744
 
745
+ // Adjust tank orientation based on terrain
746
+ const forwardVector = new THREE.Vector3(0, 0, 1).applyQuaternion(this.body.quaternion);
747
+ const rightVector = new THREE.Vector3(1, 0, 0).applyQuaternion(this.body.quaternion);
 
 
 
 
748
 
749
+ const frontHeight = window.gameInstance.getHeightAtPosition(
750
+ this.body.position.x + forwardVector.x,
751
+ this.body.position.z + forwardVector.z
752
+ );
753
+ const backHeight = window.gameInstance.getHeightAtPosition(
754
+ this.body.position.x - forwardVector.x,
755
+ this.body.position.z - forwardVector.z
756
+ );
757
+ const rightHeight = window.gameInstance.getHeightAtPosition(
758
+ this.body.position.x + rightVector.x,
759
+ this.body.position.z + rightVector.z
760
+ );
761
+ const leftHeight = window.gameInstance.getHeightAtPosition(
762
+ this.body.position.x - rightVector.x,
763
+ this.body.position.z - rightVector.z
764
+ );
765
+
766
+ const pitch = Math.atan2(frontHeight - backHeight, 2);
767
+ const roll = Math.atan2(rightHeight - leftHeight, 2);
768
+
769
+ // Apply rotation while maintaining current heading
770
+ const currentRotation = this.body.rotation.y;
771
+ this.body.rotation.set(pitch, currentRotation, roll);
772
+ }
773
+
774
+ // Update turret rotation to face player
775
+ const turretDirection = new THREE.Vector2(
776
+ playerPosition.x - this.body.position.x,
777
+ playerPosition.z - this.body.position.z
778
+ );
779
+ const turretAngle = Math.atan2(turretDirection.x, turretDirection.y);
780
+ this.turretGroup.rotation.y = turretAngle - this.body.rotation.y;
781
+
782
+ // Update bullets
783
+ if (this.bullets) {
784
+ for (let i = this.bullets.length - 1; i >= 0; i--) {
785
+ const bullet = this.bullets[i];
786
+ bullet.position.add(bullet.velocity);
787
+
788
+ // Check bullet bounds and collisions
789
+ if (Math.abs(bullet.position.x) > MAP_SIZE / 2 ||
790
+ Math.abs(bullet.position.z) > MAP_SIZE / 2) {
791
  this.scene.remove(bullet);
792
  this.bullets.splice(i, 1);
793
+ continue;
794
+ }
795
+
796
+ // Check bullet collisions with obstacles
797
+ const bulletBox = new THREE.Box3().setFromObject(bullet);
798
+ for (const obstacle of window.gameInstance.obstacles) {
799
+ const obstacleBox = new THREE.Box3().setFromObject(obstacle);
800
+ if (bulletBox.intersectsBox(obstacleBox)) {
801
+ this.scene.remove(bullet);
802
+ this.bullets.splice(i, 1);
803
+ break;
804
+ }
805
  }
806
  }
807
  }
808
  }
 
 
809
 
810
  shoot(playerPosition) {
811
+ if (!this.turret) return;
812
+
813
  const currentTime = Date.now();
814
  const attackInterval = this.type === 'tank' ?
815
  ENEMY_CONFIG.ATTACK_INTERVAL :
 
817
 
818
  if (currentTime - this.lastAttackTime < attackInterval) return;
819
 
820
+ // Create bullet
821
  const bulletGeometry = new THREE.SphereGeometry(this.type === 'tank' ? 0.2 : 0.3);
822
  const bulletMaterial = new THREE.MeshBasicMaterial({
823
  color: this.type === 'tank' ? 0xff0000 : 0xff6600
824
  });
825
  const bullet = new THREE.Mesh(bulletGeometry, bulletMaterial);
826
 
827
+ // Get turret world position for bullet spawn
828
+ const muzzleOffset = new THREE.Vector3(0, 0.5, 4);
829
+ const muzzlePosition = new THREE.Vector3();
830
+ const turretWorldQuaternion = new THREE.Quaternion();
831
+
832
+ this.turret.getWorldPosition(muzzlePosition);
833
+ this.turret.getWorldQuaternion(turretWorldQuaternion);
834
+
835
+ muzzleOffset.applyQuaternion(turretWorldQuaternion);
836
+ muzzlePosition.add(muzzleOffset);
837
+
838
+ bullet.position.copy(muzzlePosition);
839
 
840
  const direction = new THREE.Vector3()
841
+ .subVectors(playerPosition, muzzlePosition)
842
  .normalize();
843
 
844
  const bulletSpeed = this.type === 'tank' ?
 
858
  }
859
 
860
  destroy() {
861
+ if (this.body) {
862
+ this.scene.remove(this.body);
863
  this.bullets.forEach(bullet => this.scene.remove(bullet));
864
  this.bullets = [];
865
  this.isLoaded = false;
 
1134
 
1135
  // λ ˆμ΄λ” μ—…λ°μ΄νŠΈ λ©”μ„œλ“œ μΆ”κ°€
1136
  updateRadar() {
1137
+ const currentTime = Date.now();
1138
+ if (currentTime - this.lastRadarUpdate < this.radarUpdateInterval) return;
1139
+
1140
+ const radar = document.getElementById('radar');
1141
+ const radarRect = radar.getBoundingClientRect();
1142
+ const radarCenter = {
1143
+ x: radarRect.width / 2,
1144
+ y: radarRect.height / 2
1145
+ };
1146
 
1147
+ // κΈ°μ‘΄ 적 λ„νŠΈ 제거
1148
+ const oldDots = radar.getElementsByClassName('enemy-dot');
1149
+ while (oldDots[0]) {
1150
+ oldDots[0].remove();
1151
+ }
1152
 
1153
+ // 탱크 μœ„μΉ˜ κ°€μ Έμ˜€κΈ°
1154
+ const tankPos = this.tank.getPosition();
1155
 
1156
+ // λͺ¨λ“  적에 λŒ€ν•΄ λ ˆμ΄λ”μ— ν‘œμ‹œ
1157
+ this.enemies.forEach(enemy => {
1158
+ if (!enemy.isLoaded || !enemy.body) return;
1159
 
1160
+ const enemyPos = enemy.body.position;
1161
+ const distance = tankPos.distanceTo(enemyPos);
1162
 
1163
+ // λ ˆμ΄λ” λ²”μœ„ 내에 μžˆλŠ” 경우만 ν‘œμ‹œ
1164
+ if (distance <= this.radarRange) {
1165
+ // 탱크 κΈ°μ€€ μƒλŒ€ 각도 계산
1166
+ const angle = Math.atan2(
1167
+ enemyPos.x - tankPos.x,
1168
+ enemyPos.z - tankPos.z
1169
+ );
1170
 
1171
+ // μƒλŒ€ 거리λ₯Ό λ ˆμ΄λ” 크기에 맞게 μŠ€μΌ€μΌλ§
1172
+ const relativeDistance = distance / this.radarRange;
1173
+ const dotX = radarCenter.x + Math.sin(angle) * (radarCenter.x * relativeDistance);
1174
+ const dotY = radarCenter.y + Math.cos(angle) * (radarCenter.y * relativeDistance);
1175
+
1176
+ // 적 λ„νŠΈ 생성 및 μΆ”κ°€
1177
+ const dot = document.createElement('div');
1178
+ dot.className = 'enemy-dot';
1179
+ dot.style.left = `${dotX}px`;
1180
+ dot.style.top = `${dotY}px`;
1181
+ radar.appendChild(dot);
1182
+ }
1183
+ });
1184
 
1185
+ this.lastRadarUpdate = currentTime;
1186
+ }
1187
 
1188
  async addDesertDecorations() {
1189
  if (!this.obstacles) {
 
1895
  }
1896
 
1897
  animate() {
1898
+ if (this.isGameOver) {
1899
+ if (this.animationFrameId) {
1900
+ cancelAnimationFrame(this.animationFrameId);
 
 
 
 
 
 
 
 
 
1901
  }
1902
+ return;
1903
+ }
1904
 
1905
+ this.animationFrameId = requestAnimationFrame(() => this.animate());
1906
+ // κ²Œμž„μ΄ μ‹œμž‘λ˜μ§€ μ•Šμ•˜μœΌλ©΄ λ Œλ”λ§λ§Œ μˆ˜ν–‰
1907
+ if (!this.isStarted) {
1908
+ this.renderer.render(this.scene, this.camera);
1909
+ return;
1910
+ }
1911
 
1912
+ const currentTime = performance.now();
1913
+ const deltaTime = (currentTime - this.lastTime) / 1000;
1914
+ this.lastTime = currentTime;
 
 
 
 
 
 
 
 
 
1915
 
1916
+ if (!this.isLoading) {
1917
+ this.handleMovement();
1918
+ this.tank.update(this.mouse.x, this.mouse.y, this.scene);
1919
+
1920
+ const tankPosition = this.tank.getPosition();
1921
+ this.enemies.forEach(enemy => {
1922
+ if (enemy && enemy.isLoaded && enemy.body) { // body 쑴재 μ—¬λΆ€ 확인
1923
+ enemy.update(tankPosition);
1924
+
1925
+ const distanceToPlayer = enemy.body.position.distanceTo(tankPosition);
1926
+ if (distanceToPlayer < ENEMY_CONFIG.ATTACK_RANGE) {
1927
+ enemy.shoot(tankPosition);
1928
+ }
1929
+ }
1930
+ });
1931
+
1932
+ this.updateParticles();
1933
+ this.checkCollisions();
1934
+ this.updateUI();
1935
+ this.updateRadar();
1936
+ }
1937
+
1938
+ this.renderer.render(this.scene, this.camera);
1939
  }
1940
 
1941
  // Start game