cutechicken commited on
Commit
161ebeb
โ€ข
1 Parent(s): 59becbb

Update game.js

Browse files
Files changed (1) hide show
  1. game.js +238 -296
game.js CHANGED
@@ -572,244 +572,200 @@ class Enemy {
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,28 +773,16 @@ class Enemy {
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,8 +802,8 @@ class Enemy {
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,56 +1078,56 @@ class Game {
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,47 +1839,45 @@ this.enemies.forEach(enemy => {
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
 
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/enemy1.glb' : '/models/enemy4.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
 
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
  }
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
 
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
  }
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