cutechicken commited on
Commit
632d61f
โ€ข
1 Parent(s): 876ef81

Update game.js

Browse files
Files changed (1) hide show
  1. game.js +193 -92
game.js CHANGED
@@ -570,6 +570,7 @@ startReload() {
570
  // Enemy ํด๋ž˜์Šค
571
  class Enemy {
572
  constructor(scene, position, type = 'tank') {
 
573
  this.scene = scene;
574
  this.position = position;
575
  this.mesh = null;
@@ -582,28 +583,213 @@ class Enemy {
582
 
583
  // AI ์ƒํƒœ ๊ด€๋ฆฌ
584
  this.aiState = {
585
- mode: 'pursue', // 'pursue', 'flank', 'retreat'
586
  lastStateChange: 0,
587
  stateChangeCooldown: 3000,
588
  lastVisibilityCheck: 0,
589
  visibilityCheckInterval: 500,
590
  canSeePlayer: false,
591
  lastKnownPlayerPosition: null,
592
- searchStartTime: null
 
 
 
 
 
593
  };
594
 
595
- // ๊ฒฝ๋กœ ํƒ์ƒ‰ ์ƒํƒœ
596
  this.pathfinding = {
597
  currentPath: [],
598
  pathUpdateInterval: 1000,
599
  lastPathUpdate: 0,
600
  isAvoidingObstacle: false,
601
  avoidanceDirection: null,
602
- obstacleCheckDistance: 10
 
 
 
 
603
  };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
604
  }
605
 
606
- async initialize(loader) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
607
  try {
608
  const modelPath = this.type === 'tank' ? '/models/t90.glb' : '/models/t90.glb';
609
  const result = await loader.loadAsync(modelPath);
@@ -630,7 +816,7 @@ class Enemy {
630
  if (!this.mesh) return false;
631
 
632
  const startPos = this.mesh.position.clone();
633
- startPos.y += 2; // ํƒฑํฌ ํฌํƒ‘ ๋†’์ด
634
  const direction = new THREE.Vector3().subVectors(playerPosition, startPos).normalize();
635
  const distance = startPos.distanceTo(playerPosition);
636
 
@@ -644,7 +830,6 @@ class Enemy {
644
  const currentTime = Date.now();
645
  const distanceToPlayer = this.mesh.position.distanceTo(playerPosition);
646
 
647
- // ์‹œ์•ผ ์ฒดํฌ
648
  if (currentTime - this.aiState.lastVisibilityCheck > this.aiState.visibilityCheckInterval) {
649
  this.aiState.canSeePlayer = this.checkLineOfSight(playerPosition);
650
  this.aiState.lastVisibilityCheck = currentTime;
@@ -655,7 +840,6 @@ class Enemy {
655
  }
656
  }
657
 
658
- // AI ์ƒํƒœ ์—…๋ฐ์ดํŠธ
659
  if (currentTime - this.aiState.lastStateChange > this.aiState.stateChangeCooldown) {
660
  if (this.health < 30) {
661
  this.aiState.mode = 'retreat';
@@ -674,12 +858,7 @@ class Enemy {
674
  return;
675
  }
676
 
677
- // ๊ฐ„๋‹จํ•œ A* ๊ฒฝ๋กœ ์ฐพ๊ธฐ ๊ตฌํ˜„
678
- const start = this.mesh.position.clone();
679
- const end = targetPosition.clone();
680
-
681
- // ์žฅ์• ๋ฌผ์„ ๊ณ ๋ คํ•œ ๊ฒฝ๋กœ์  ์ƒ์„ฑ
682
- this.pathfinding.currentPath = this.generatePathPoints(start, end);
683
  this.pathfinding.lastPathUpdate = currentTime;
684
  }
685
 
@@ -705,92 +884,14 @@ class Enemy {
705
  .subVectors(targetPoint, this.mesh.position)
706
  .normalize();
707
 
708
- // ์žฅ์• ๋ฌผ ๊ฐ์ง€ ๋ฐ ํšŒํ”ผ
709
- if (this.detectObstacle(direction)) {
710
- if (!this.pathfinding.isAvoidingObstacle) {
711
- this.pathfinding.isAvoidingObstacle = true;
712
- this.pathfinding.avoidanceDirection = this.calculateAvoidanceDirection(direction);
713
- }
714
- direction.copy(this.pathfinding.avoidanceDirection);
715
- } else {
716
- this.pathfinding.isAvoidingObstacle = false;
717
- }
718
-
719
- // ์ด๋™ ์ ์šฉ
720
  const moveVector = direction.multiplyScalar(this.moveSpeed);
721
  this.mesh.position.add(moveVector);
722
 
723
- // ๊ฒฝ๋กœ์ ์— ๋„๋‹ฌํ–ˆ๋Š”์ง€ ํ™•์ธ
724
  if (this.mesh.position.distanceTo(targetPoint) < 2) {
725
  this.pathfinding.currentPath.shift();
726
  }
727
  }
728
 
729
- detectObstacle(direction) {
730
- const raycaster = new THREE.Raycaster(
731
- this.mesh.position,
732
- direction,
733
- 0,
734
- this.pathfinding.obstacleCheckDistance
735
- );
736
- const intersects = raycaster.intersectObjects(window.gameInstance.obstacles, true);
737
- return intersects.length > 0;
738
- }
739
-
740
- calculateAvoidanceDirection(currentDirection) {
741
- const left = new THREE.Vector3(-currentDirection.z, 0, currentDirection.x);
742
- const right = new THREE.Vector3(currentDirection.z, 0, -currentDirection.x);
743
-
744
- // ์™ผ์ชฝ๊ณผ ์˜ค๋ฅธ์ชฝ ๋ฐฉํ–ฅ ์ค‘ ์žฅ์• ๋ฌผ์ด ์—†๋Š” ๋ฐฉํ–ฅ ์„ ํƒ
745
- if (!this.detectObstacle(left)) return left;
746
- if (!this.detectObstacle(right)) return right;
747
-
748
- // ๋‘˜ ๋‹ค ๋ง‰ํ˜€์žˆ์œผ๋ฉด ๋’ค๋กœ
749
- return currentDirection.multiplyScalar(-1);
750
- }
751
-
752
- update(playerPosition) {
753
- if (!this.mesh || !this.isLoaded) return;
754
-
755
- this.updateAIState(playerPosition);
756
-
757
- let targetPosition = playerPosition;
758
- if (!this.aiState.canSeePlayer && this.aiState.lastKnownPlayerPosition) {
759
- targetPosition = this.aiState.lastKnownPlayerPosition;
760
- }
761
-
762
- // AI ๋ชจ๋“œ์— ๋”ฐ๋ฅธ ํ–‰๋™
763
- switch (this.aiState.mode) {
764
- case 'pursue':
765
- this.findPathToTarget(targetPosition);
766
- this.moveAlongPath();
767
- break;
768
-
769
- case 'flank':
770
- const flankPosition = this.calculateFlankPosition(playerPosition);
771
- this.findPathToTarget(flankPosition);
772
- this.moveAlongPath();
773
- break;
774
-
775
- case 'retreat':
776
- const retreatPosition = this.calculateRetreatPosition(playerPosition);
777
- this.findPathToTarget(retreatPosition);
778
- this.moveAlongPath();
779
- break;
780
- }
781
-
782
- // ์ด์•Œ ์—…๋ฐ์ดํŠธ
783
- this.updateBullets();
784
-
785
- // ํ”Œ๋ ˆ์ด์–ด๊ฐ€ ์‹œ์•ผ์— ์žˆ์œผ๋ฉด ๋ฐœ์‚ฌ
786
- if (this.aiState.canSeePlayer) {
787
- this.shoot(playerPosition);
788
- }
789
-
790
- // ์ง€ํ˜•์— ๋”ฐ๋ฅธ ํƒฑํฌ ๊ธฐ์šธ๊ธฐ ์กฐ์ •
791
- this.adjustTankTilt();
792
- }
793
-
794
  calculateFlankPosition(playerPosition) {
795
  const angle = Math.random() * Math.PI * 2;
796
  const radius = 40;
 
570
  // Enemy ํด๋ž˜์Šค
571
  class Enemy {
572
  constructor(scene, position, type = 'tank') {
573
+ // ๊ธฐ๋ณธ ์†์„ฑ
574
  this.scene = scene;
575
  this.position = position;
576
  this.mesh = null;
 
583
 
584
  // AI ์ƒํƒœ ๊ด€๋ฆฌ
585
  this.aiState = {
586
+ mode: 'pursue',
587
  lastStateChange: 0,
588
  stateChangeCooldown: 3000,
589
  lastVisibilityCheck: 0,
590
  visibilityCheckInterval: 500,
591
  canSeePlayer: false,
592
  lastKnownPlayerPosition: null,
593
+ searchStartTime: null,
594
+ targetRotation: 0,
595
+ currentRotation: 0,
596
+ isAiming: false,
597
+ aimingTime: 0,
598
+ requiredAimTime: 1000 // ์กฐ์ค€์— ํ•„์š”ํ•œ ์‹œ๊ฐ„
599
  };
600
 
601
+ // ๊ฒฝ๋กœ ํƒ์ƒ‰ ๋ฐ ํšŒํ”ผ ์‹œ์Šคํ…œ
602
  this.pathfinding = {
603
  currentPath: [],
604
  pathUpdateInterval: 1000,
605
  lastPathUpdate: 0,
606
  isAvoidingObstacle: false,
607
  avoidanceDirection: null,
608
+ obstacleCheckDistance: 10,
609
+ avoidanceTime: 0,
610
+ maxAvoidanceTime: 3000, // ์ตœ๋Œ€ ํšŒํ”ผ ์‹œ๊ฐ„
611
+ sensorAngles: [-45, 0, 45], // ์ „๋ฐฉ ๊ฐ์ง€ ๊ฐ๋„
612
+ sensorDistance: 15 // ๊ฐ์ง€ ๊ฑฐ๋ฆฌ
613
  };
614
+
615
+ // ์ „ํˆฌ ์‹œ์Šคํ…œ
616
+ this.combat = {
617
+ minEngagementRange: 30,
618
+ maxEngagementRange: 150,
619
+ optimalRange: 80,
620
+ aimThreshold: 0.1, // ์กฐ์ค€ ์ •ํ™•๋„ ์ž„๊ณ„๊ฐ’
621
+ lastShotAccuracy: 0,
622
+ consecutiveHits: 0,
623
+ maxConsecutiveHits: 3
624
+ };
625
+ }
626
+
627
+ // ์žฅ์• ๋ฌผ ๊ฐ์ง€ ์‹œ์Šคํ…œ
628
+ detectObstacles() {
629
+ const obstacles = [];
630
+ const position = this.mesh.position.clone();
631
+ position.y += 1; // ์„ผ์„œ ๋†’์ด ์กฐ์ •
632
+
633
+ this.pathfinding.sensorAngles.forEach(angle => {
634
+ const direction = new THREE.Vector3(0, 0, 1)
635
+ .applyQuaternion(this.mesh.quaternion)
636
+ .applyAxisAngle(new THREE.Vector3(0, 1, 0), angle * Math.PI / 180);
637
+
638
+ const raycaster = new THREE.Raycaster(position, direction, 0, this.pathfinding.sensorDistance);
639
+ const intersects = raycaster.intersectObjects(window.gameInstance.obstacles, true);
640
+
641
+ if (intersects.length > 0) {
642
+ obstacles.push({
643
+ angle: angle,
644
+ distance: intersects[0].distance,
645
+ point: intersects[0].point
646
+ });
647
+ }
648
+ });
649
+
650
+ return obstacles;
651
+ }
652
+
653
+ // ํšŒํ”ผ ๋ฐฉํ–ฅ ๊ณ„์‚ฐ
654
+ calculateAvoidanceDirection(obstacles) {
655
+ if (obstacles.length === 0) return null;
656
+
657
+ // ๋ชจ๋“  ์žฅ์• ๋ฌผ์˜ ๋ฐฉํ–ฅ์„ ๊ณ ๋ คํ•˜์—ฌ ์ตœ์ ์˜ ํšŒํ”ผ ๋ฐฉํ–ฅ ๊ณ„์‚ฐ
658
+ const avoidanceVector = new THREE.Vector3();
659
+ obstacles.forEach(obstacle => {
660
+ const avoidDir = new THREE.Vector3()
661
+ .subVectors(this.mesh.position, obstacle.point)
662
+ .normalize()
663
+ .multiplyScalar(1 / obstacle.distance); // ๊ฑฐ๋ฆฌ์— ๋ฐ˜๋น„๋ก€ํ•˜๋Š” ๊ฐ€์ค‘์น˜
664
+ avoidanceVector.add(avoidDir);
665
+ });
666
+
667
+ return avoidanceVector.normalize();
668
+ }
669
+
670
+ // ์กฐ์ค€ ์‹œ์Šคํ…œ
671
+ updateAiming(playerPosition) {
672
+ const targetDirection = new THREE.Vector3()
673
+ .subVectors(playerPosition, this.mesh.position)
674
+ .normalize();
675
+
676
+ // ๋ชฉํ‘œ ํšŒ์ „๊ฐ ๊ณ„์‚ฐ
677
+ this.aiState.targetRotation = Math.atan2(targetDirection.x, targetDirection.z);
678
+
679
+ // ํ˜„์žฌ ํšŒ์ „๊ฐ ๋ถ€๋“œ๋Ÿฝ๊ฒŒ ์กฐ์ •
680
+ const rotationDiff = this.aiState.targetRotation - this.aiState.currentRotation;
681
+ let rotationStep = Math.sign(rotationDiff) * Math.min(Math.abs(rotationDiff), 0.05);
682
+ this.aiState.currentRotation += rotationStep;
683
+
684
+ // ๋ฉ”์‹œ ํšŒ์ „ ์ ์šฉ
685
+ this.mesh.rotation.y = this.aiState.currentRotation;
686
+
687
+ // ์กฐ์ค€ ์ •ํ™•๋„ ๊ณ„์‚ฐ
688
+ const aimAccuracy = 1 - Math.abs(rotationDiff) / Math.PI;
689
+ return aimAccuracy > this.combat.aimThreshold;
690
+ }
691
+
692
+ // ์ „ํˆฌ ๊ฑฐ๋ฆฌ ๊ด€๋ฆฌ
693
+ maintainCombatDistance(playerPosition) {
694
+ const distanceToPlayer = this.mesh.position.distanceTo(playerPosition);
695
+ let moveDirection = new THREE.Vector3();
696
+
697
+ if (distanceToPlayer < this.combat.minEngagementRange) {
698
+ // ๋„ˆ๋ฌด ๊ฐ€๊นŒ์šฐ๋ฉด ํ›„์ง„
699
+ moveDirection.subVectors(this.mesh.position, playerPosition).normalize();
700
+ } else if (distanceToPlayer > this.combat.maxEngagementRange) {
701
+ // ๋„ˆ๋ฌด ๋ฉ€๋ฉด ์ „์ง„
702
+ moveDirection.subVectors(playerPosition, this.mesh.position).normalize();
703
+ } else if (Math.abs(distanceToPlayer - this.combat.optimalRange) > 10) {
704
+ // ์ตœ์  ๊ฑฐ๋ฆฌ๋กœ ์กฐ์ •
705
+ const targetDistance = this.combat.optimalRange;
706
+ moveDirection.subVectors(playerPosition, this.mesh.position).normalize();
707
+ if (distanceToPlayer > targetDistance) {
708
+ moveDirection.multiplyScalar(1);
709
+ } else {
710
+ moveDirection.multiplyScalar(-1);
711
+ }
712
+ }
713
+
714
+ return moveDirection;
715
+ }
716
+
717
+ // ๋ฐœ์‚ฌ ์กฐ๊ฑด ํ™•์ธ
718
+ canShoot(playerPosition) {
719
+ const distance = this.mesh.position.distanceTo(playerPosition);
720
+ const hasLineOfSight = this.checkLineOfSight(playerPosition);
721
+ const isAimed = this.updateAiming(playerPosition);
722
+
723
+ return distance <= this.combat.maxEngagementRange &&
724
+ distance >= this.combat.minEngagementRange &&
725
+ hasLineOfSight &&
726
+ isAimed;
727
  }
728
 
729
+ // ๋ฉ”์ธ ์—…๋ฐ์ดํŠธ ํ•จ์ˆ˜
730
+ update(playerPosition) {
731
+ if (!this.mesh || !this.isLoaded) return;
732
+
733
+ // AI ์ƒํƒœ ์—…๋ฐ์ดํŠธ
734
+ this.updateAIState(playerPosition);
735
+
736
+ // ์žฅ์• ๋ฌผ ๊ฐ์ง€
737
+ const obstacles = this.detectObstacles();
738
+
739
+ // ์ด๋™ ๋ฐ ํšŒํ”ผ ๋กœ์ง
740
+ if (obstacles.length > 0 && !this.pathfinding.isAvoidingObstacle) {
741
+ this.pathfinding.isAvoidingObstacle = true;
742
+ this.pathfinding.avoidanceDirection = this.calculateAvoidanceDirection(obstacles);
743
+ this.pathfinding.avoidanceTime = 0;
744
+ }
745
+
746
+ // ํšŒํ”ผ ๋™์ž‘ ์ˆ˜ํ–‰
747
+ if (this.pathfinding.isAvoidingObstacle) {
748
+ this.pathfinding.avoidanceTime += 16; // ์•ฝ 16ms per frame
749
+ if (this.pathfinding.avoidanceTime >= this.pathfinding.maxAvoidanceTime) {
750
+ this.pathfinding.isAvoidingObstacle = false;
751
+ } else {
752
+ const avoidMove = this.pathfinding.avoidanceDirection.multiplyScalar(this.moveSpeed);
753
+ this.mesh.position.add(avoidMove);
754
+ }
755
+ } else {
756
+ // ์ผ๋ฐ˜ ์ด๋™ ๋กœ์ง
757
+ switch (this.aiState.mode) {
758
+ case 'pursue':
759
+ this.findPathToTarget(playerPosition);
760
+ this.moveAlongPath();
761
+ break;
762
+ case 'flank':
763
+ const flankPosition = this.calculateFlankPosition(playerPosition);
764
+ this.findPathToTarget(flankPosition);
765
+ this.moveAlongPath();
766
+ break;
767
+ case 'retreat':
768
+ const retreatPosition = this.calculateRetreatPosition(playerPosition);
769
+ this.findPathToTarget(retreatPosition);
770
+ this.moveAlongPath();
771
+ break;
772
+ }
773
+ }
774
+
775
+ // ์ „ํˆฌ ๊ฑฐ๋ฆฌ ์กฐ์ •
776
+ const combatMove = this.maintainCombatDistance(playerPosition);
777
+ if (combatMove.length() > 0) {
778
+ this.mesh.position.add(combatMove.multiplyScalar(this.moveSpeed));
779
+ }
780
+
781
+ // ๋ฐœ์‚ฌ ์ฒ˜๋ฆฌ
782
+ if (this.canShoot(playerPosition)) {
783
+ this.shoot(playerPosition);
784
+ }
785
+
786
+ // ์ด์•Œ ์—…๋ฐ์ดํŠธ
787
+ this.updateBullets();
788
+
789
+ // ํƒฑํฌ ๊ธฐ์šธ๊ธฐ ์กฐ์ •
790
+ this.adjustTankTilt();
791
+ }
792
+ async initialize(loader) {
793
  try {
794
  const modelPath = this.type === 'tank' ? '/models/t90.glb' : '/models/t90.glb';
795
  const result = await loader.loadAsync(modelPath);
 
816
  if (!this.mesh) return false;
817
 
818
  const startPos = this.mesh.position.clone();
819
+ startPos.y += 2;
820
  const direction = new THREE.Vector3().subVectors(playerPosition, startPos).normalize();
821
  const distance = startPos.distanceTo(playerPosition);
822
 
 
830
  const currentTime = Date.now();
831
  const distanceToPlayer = this.mesh.position.distanceTo(playerPosition);
832
 
 
833
  if (currentTime - this.aiState.lastVisibilityCheck > this.aiState.visibilityCheckInterval) {
834
  this.aiState.canSeePlayer = this.checkLineOfSight(playerPosition);
835
  this.aiState.lastVisibilityCheck = currentTime;
 
840
  }
841
  }
842
 
 
843
  if (currentTime - this.aiState.lastStateChange > this.aiState.stateChangeCooldown) {
844
  if (this.health < 30) {
845
  this.aiState.mode = 'retreat';
 
858
  return;
859
  }
860
 
861
+ this.pathfinding.currentPath = this.generatePathPoints(this.mesh.position.clone(), targetPosition);
 
 
 
 
 
862
  this.pathfinding.lastPathUpdate = currentTime;
863
  }
864
 
 
884
  .subVectors(targetPoint, this.mesh.position)
885
  .normalize();
886
 
 
 
 
 
 
 
 
 
 
 
 
 
887
  const moveVector = direction.multiplyScalar(this.moveSpeed);
888
  this.mesh.position.add(moveVector);
889
 
 
890
  if (this.mesh.position.distanceTo(targetPoint) < 2) {
891
  this.pathfinding.currentPath.shift();
892
  }
893
  }
894
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
895
  calculateFlankPosition(playerPosition) {
896
  const angle = Math.random() * Math.PI * 2;
897
  const radius = 40;