Spaces:
Running
Running
Update game.js
Browse files
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',
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 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;
|