Spaces:
Running
Running
Update game.js
Browse files
game.js
CHANGED
|
@@ -579,65 +579,30 @@ class Enemy {
|
|
| 579 |
this.bullets = [];
|
| 580 |
this.isLoaded = false;
|
| 581 |
this.moveSpeed = type === 'tank' ? ENEMY_MOVE_SPEED : ENEMY_MOVE_SPEED * 0.7;
|
| 582 |
-
|
| 583 |
-
|
| 584 |
-
|
| 585 |
-
|
| 586 |
-
|
| 587 |
-
|
| 588 |
-
|
| 589 |
-
|
| 590 |
-
|
| 591 |
-
|
| 592 |
-
|
| 593 |
-
|
| 594 |
-
});
|
| 595 |
-
const flame = new THREE.Mesh(flameGeometry, flameMaterial);
|
| 596 |
-
flame.scale.set(2, 2, 3);
|
| 597 |
-
flashGroup.add(flame);
|
| 598 |
|
| 599 |
-
|
| 600 |
-
|
| 601 |
-
|
| 602 |
-
|
| 603 |
-
|
| 604 |
-
|
| 605 |
-
|
| 606 |
-
|
| 607 |
-
|
| 608 |
-
const smoke = new THREE.Mesh(smokeGeometry, smokeMaterial);
|
| 609 |
-
smoke.position.set(
|
| 610 |
-
Math.random() * 1 - 0.5,
|
| 611 |
-
Math.random() * 1 - 0.5,
|
| 612 |
-
-1 - Math.random()
|
| 613 |
-
);
|
| 614 |
-
smoke.scale.set(1.5, 1.5, 1.5);
|
| 615 |
-
flashGroup.add(smoke);
|
| 616 |
}
|
| 617 |
|
| 618 |
-
// ํฌ๊ตฌ ์์น ๊ณ์ฐ
|
| 619 |
-
const muzzleOffset = new THREE.Vector3(0, 0.5, 4);
|
| 620 |
-
const muzzlePosition = new THREE.Vector3();
|
| 621 |
-
const meshWorldQuaternion = new THREE.Quaternion();
|
| 622 |
-
|
| 623 |
-
this.mesh.getWorldPosition(muzzlePosition);
|
| 624 |
-
this.mesh.getWorldQuaternion(meshWorldQuaternion);
|
| 625 |
-
|
| 626 |
-
muzzleOffset.applyQuaternion(meshWorldQuaternion);
|
| 627 |
-
muzzlePosition.add(muzzleOffset);
|
| 628 |
-
|
| 629 |
-
flashGroup.position.copy(muzzlePosition);
|
| 630 |
-
flashGroup.quaternion.copy(meshWorldQuaternion);
|
| 631 |
-
|
| 632 |
-
this.scene.add(flashGroup);
|
| 633 |
-
|
| 634 |
-
// ์ดํํธ ์ง์ ์๊ฐ ์ฆ๊ฐ
|
| 635 |
-
setTimeout(() => {
|
| 636 |
-
this.scene.remove(flashGroup);
|
| 637 |
-
}, 500);
|
| 638 |
-
}
|
| 639 |
-
|
| 640 |
-
|
| 641 |
async initialize(loader) {
|
| 642 |
try {
|
| 643 |
const modelPath = this.type === 'tank' ? '/models/t90.glb' : '/models/t90.glb';
|
|
@@ -661,107 +626,189 @@ class Enemy {
|
|
| 661 |
}
|
| 662 |
}
|
| 663 |
|
| 664 |
-
|
| 665 |
-
|
| 666 |
|
| 667 |
-
|
| 668 |
-
.
|
| 669 |
-
.normalize();
|
| 670 |
-
|
| 671 |
-
|
| 672 |
-
|
| 673 |
-
|
| 674 |
-
|
| 675 |
-
|
| 676 |
-
|
| 677 |
-
|
| 678 |
-
|
| 679 |
-
const
|
| 680 |
-
|
| 681 |
-
|
| 682 |
-
|
| 683 |
-
|
| 684 |
-
|
| 685 |
-
|
| 686 |
-
|
| 687 |
-
|
| 688 |
-
|
| 689 |
-
|
| 690 |
-
this.mesh.position.copy(newPosition);
|
| 691 |
-
|
| 692 |
-
// ์ฅ์ ๋ฌผ๊ณผ ์ถฉ๋ ์ฒดํฌ
|
| 693 |
-
const enemyBox = new THREE.Box3().setFromObject(this.mesh);
|
| 694 |
-
let hasCollision = false;
|
| 695 |
-
|
| 696 |
-
// ๋ชจ๋ ์ฅ์ ๋ฌผ์ ๋ํด ์ถฉ๋ ๊ฒ์ฌ
|
| 697 |
-
for (const obstacle of window.gameInstance.obstacles) {
|
| 698 |
-
const obstacleBox = new THREE.Box3().setFromObject(obstacle);
|
| 699 |
-
if (enemyBox.intersectsBox(obstacleBox)) {
|
| 700 |
-
hasCollision = true;
|
| 701 |
-
break;
|
| 702 |
}
|
| 703 |
}
|
| 704 |
-
|
| 705 |
-
//
|
| 706 |
-
if (
|
| 707 |
-
|
| 708 |
-
|
| 709 |
-
|
| 710 |
-
|
| 711 |
-
|
| 712 |
-
|
| 713 |
-
}
|
| 714 |
-
}
|
| 715 |
}
|
|
|
|
| 716 |
}
|
| 717 |
-
|
| 718 |
-
|
| 719 |
-
|
| 720 |
-
|
| 721 |
-
|
| 722 |
-
|
| 723 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 724 |
|
| 725 |
-
//
|
| 726 |
-
|
| 727 |
-
|
| 728 |
-
|
| 729 |
-
|
| 730 |
-
|
| 731 |
-
|
| 732 |
-
|
| 733 |
-
|
| 734 |
-
|
| 735 |
-
|
| 736 |
-
|
| 737 |
-
|
| 738 |
-
|
| 739 |
-
|
| 740 |
-
|
| 741 |
-
|
| 742 |
-
|
| 743 |
-
|
| 744 |
-
|
| 745 |
-
|
| 746 |
-
|
| 747 |
-
|
| 748 |
-
|
| 749 |
-
|
| 750 |
-
|
| 751 |
-
|
| 752 |
-
|
| 753 |
-
|
| 754 |
-
|
| 755 |
-
|
| 756 |
-
|
| 757 |
-
} else {
|
| 758 |
-
// ์ฐํ๋ ๋ถ๊ฐ๋ฅํ๋ฉด ์ด์ ์์น๋ก ๋ณต๊ท
|
| 759 |
-
this.mesh.position.copy(previousPosition);
|
| 760 |
-
}
|
| 761 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 762 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 763 |
|
| 764 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 765 |
const forwardVector = new THREE.Vector3(0, 0, 1).applyQuaternion(this.mesh.quaternion);
|
| 766 |
const rightVector = new THREE.Vector3(1, 0, 0).applyQuaternion(this.mesh.quaternion);
|
| 767 |
|
|
@@ -785,21 +832,15 @@ class Enemy {
|
|
| 785 |
const pitch = Math.atan2(frontHeight - backHeight, 2);
|
| 786 |
const roll = Math.atan2(rightHeight - leftHeight, 2);
|
| 787 |
|
| 788 |
-
// ํ์ฌ ํ์ ์ ์งํ๋ฉด์ ๊ธฐ์ธ๊ธฐ๋ง ์ ์ฉ
|
| 789 |
const currentRotation = this.mesh.rotation.y;
|
| 790 |
this.mesh.rotation.set(pitch, currentRotation, roll);
|
| 791 |
}
|
| 792 |
-
|
| 793 |
-
|
| 794 |
-
this.mesh.lookAt(playerPosition);
|
| 795 |
-
|
| 796 |
-
// ์ด์ ์
๋ฐ์ดํธ
|
| 797 |
-
if (this.bullets) {
|
| 798 |
for (let i = this.bullets.length - 1; i >= 0; i--) {
|
| 799 |
const bullet = this.bullets[i];
|
| 800 |
bullet.position.add(bullet.velocity);
|
| 801 |
|
| 802 |
-
// ์ด์์ด ๋งต ๋ฐ์ผ๋ก ๋๊ฐ๊ฑฐ๋ ์ฅ์ ๋ฌผ๊ณผ ์ถฉ๋ํ๋ฉด ์ ๊ฑฐ
|
| 803 |
if (Math.abs(bullet.position.x) > MAP_SIZE / 2 ||
|
| 804 |
Math.abs(bullet.position.z) > MAP_SIZE / 2) {
|
| 805 |
this.scene.remove(bullet);
|
|
@@ -807,7 +848,6 @@ class Enemy {
|
|
| 807 |
continue;
|
| 808 |
}
|
| 809 |
|
| 810 |
-
// ์ด์๊ณผ ์ฅ์ ๋ฌผ ์ถฉ๋ ์ฒดํฌ
|
| 811 |
const bulletBox = new THREE.Box3().setFromObject(bullet);
|
| 812 |
for (const obstacle of window.gameInstance.obstacles) {
|
| 813 |
const obstacleBox = new THREE.Box3().setFromObject(obstacle);
|
|
@@ -819,72 +859,116 @@ class Enemy {
|
|
| 819 |
}
|
| 820 |
}
|
| 821 |
}
|
| 822 |
-
}
|
| 823 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 824 |
|
| 825 |
shoot(playerPosition) {
|
| 826 |
-
|
| 827 |
-
|
| 828 |
-
|
| 829 |
-
|
| 830 |
|
| 831 |
-
|
| 832 |
|
| 833 |
-
|
| 834 |
-
this.createMuzzleFlash();
|
| 835 |
|
| 836 |
-
|
| 837 |
-
|
| 838 |
-
|
| 839 |
-
enemyFireSound.play();
|
| 840 |
|
| 841 |
-
|
| 842 |
-
|
| 843 |
-
|
| 844 |
-
|
| 845 |
-
|
| 846 |
-
|
| 847 |
-
|
| 848 |
-
const bullet = new THREE.Mesh(bulletGeometry, bulletMaterial);
|
| 849 |
|
| 850 |
-
|
| 851 |
-
|
| 852 |
-
|
| 853 |
-
|
| 854 |
-
|
| 855 |
-
|
| 856 |
-
|
| 857 |
-
|
| 858 |
-
|
| 859 |
-
|
| 860 |
-
|
| 861 |
-
|
| 862 |
-
|
| 863 |
-
.
|
| 864 |
-
|
| 865 |
-
|
| 866 |
-
|
| 867 |
-
|
| 868 |
-
|
| 869 |
-
|
| 870 |
-
|
| 871 |
-
|
| 872 |
-
|
| 873 |
-
|
| 874 |
-
|
| 875 |
-
|
| 876 |
-
|
| 877 |
-
|
| 878 |
-
|
| 879 |
-
|
| 880 |
-
|
| 881 |
-
|
| 882 |
-
|
| 883 |
-
|
| 884 |
-
this.scene.add(bullet);
|
| 885 |
-
this.bullets.push(bullet);
|
| 886 |
-
this.lastAttackTime = currentTime;
|
| 887 |
-
}
|
| 888 |
|
| 889 |
takeDamage(damage) {
|
| 890 |
this.health -= damage;
|
|
|
|
| 579 |
this.bullets = [];
|
| 580 |
this.isLoaded = false;
|
| 581 |
this.moveSpeed = type === 'tank' ? ENEMY_MOVE_SPEED : ENEMY_MOVE_SPEED * 0.7;
|
| 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';
|
|
|
|
| 626 |
}
|
| 627 |
}
|
| 628 |
|
| 629 |
+
checkLineOfSight(playerPosition) {
|
| 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 |
+
|
| 637 |
+
const raycaster = new THREE.Raycaster(startPos, direction, 0, distance);
|
| 638 |
+
const intersects = raycaster.intersectObjects(window.gameInstance.obstacles, true);
|
| 639 |
+
|
| 640 |
+
return intersects.length === 0;
|
| 641 |
+
}
|
| 642 |
+
|
| 643 |
+
updateAIState(playerPosition) {
|
| 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;
|
| 651 |
+
|
| 652 |
+
if (this.aiState.canSeePlayer) {
|
| 653 |
+
this.aiState.lastKnownPlayerPosition = playerPosition.clone();
|
| 654 |
+
this.aiState.searchStartTime = null;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 655 |
}
|
| 656 |
}
|
| 657 |
+
|
| 658 |
+
// AI ์ํ ์
๋ฐ์ดํธ
|
| 659 |
+
if (currentTime - this.aiState.lastStateChange > this.aiState.stateChangeCooldown) {
|
| 660 |
+
if (this.health < 30) {
|
| 661 |
+
this.aiState.mode = 'retreat';
|
| 662 |
+
} else if (distanceToPlayer < 30 && this.aiState.canSeePlayer) {
|
| 663 |
+
this.aiState.mode = 'flank';
|
| 664 |
+
} else {
|
| 665 |
+
this.aiState.mode = 'pursue';
|
|
|
|
|
|
|
| 666 |
}
|
| 667 |
+
this.aiState.lastStateChange = currentTime;
|
| 668 |
}
|
| 669 |
+
}
|
| 670 |
+
|
| 671 |
+
findPathToTarget(targetPosition) {
|
| 672 |
+
const currentTime = Date.now();
|
| 673 |
+
if (currentTime - this.pathfinding.lastPathUpdate < this.pathfinding.pathUpdateInterval) {
|
| 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 |
+
|
| 686 |
+
generatePathPoints(start, end) {
|
| 687 |
+
const points = [];
|
| 688 |
+
const direction = new THREE.Vector3().subVectors(end, start).normalize();
|
| 689 |
+
const distance = start.distanceTo(end);
|
| 690 |
+
const steps = Math.ceil(distance / 10);
|
| 691 |
+
|
| 692 |
+
for (let i = 0; i <= steps; i++) {
|
| 693 |
+
const point = start.clone().add(direction.multiplyScalar(i * 10));
|
| 694 |
+
points.push(point);
|
| 695 |
+
}
|
| 696 |
+
|
| 697 |
+
return points;
|
| 698 |
+
}
|
| 699 |
+
|
| 700 |
+
moveAlongPath() {
|
| 701 |
+
if (this.pathfinding.currentPath.length === 0) return;
|
| 702 |
+
|
| 703 |
+
const targetPoint = this.pathfinding.currentPath[0];
|
| 704 |
+
const direction = new THREE.Vector3()
|
| 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;
|
| 797 |
+
return new THREE.Vector3(
|
| 798 |
+
playerPosition.x + Math.cos(angle) * radius,
|
| 799 |
+
playerPosition.y,
|
| 800 |
+
playerPosition.z + Math.sin(angle) * radius
|
| 801 |
+
);
|
| 802 |
+
}
|
| 803 |
+
|
| 804 |
+
calculateRetreatPosition(playerPosition) {
|
| 805 |
+
const direction = new THREE.Vector3()
|
| 806 |
+
.subVectors(this.mesh.position, playerPosition)
|
| 807 |
+
.normalize();
|
| 808 |
+
return this.mesh.position.clone().add(direction.multiplyScalar(50));
|
| 809 |
+
}
|
| 810 |
+
|
| 811 |
+
adjustTankTilt() {
|
| 812 |
const forwardVector = new THREE.Vector3(0, 0, 1).applyQuaternion(this.mesh.quaternion);
|
| 813 |
const rightVector = new THREE.Vector3(1, 0, 0).applyQuaternion(this.mesh.quaternion);
|
| 814 |
|
|
|
|
| 832 |
const pitch = Math.atan2(frontHeight - backHeight, 2);
|
| 833 |
const roll = Math.atan2(rightHeight - leftHeight, 2);
|
| 834 |
|
|
|
|
| 835 |
const currentRotation = this.mesh.rotation.y;
|
| 836 |
this.mesh.rotation.set(pitch, currentRotation, roll);
|
| 837 |
}
|
| 838 |
+
|
| 839 |
+
updateBullets() {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 840 |
for (let i = this.bullets.length - 1; i >= 0; i--) {
|
| 841 |
const bullet = this.bullets[i];
|
| 842 |
bullet.position.add(bullet.velocity);
|
| 843 |
|
|
|
|
| 844 |
if (Math.abs(bullet.position.x) > MAP_SIZE / 2 ||
|
| 845 |
Math.abs(bullet.position.z) > MAP_SIZE / 2) {
|
| 846 |
this.scene.remove(bullet);
|
|
|
|
| 848 |
continue;
|
| 849 |
}
|
| 850 |
|
|
|
|
| 851 |
const bulletBox = new THREE.Box3().setFromObject(bullet);
|
| 852 |
for (const obstacle of window.gameInstance.obstacles) {
|
| 853 |
const obstacleBox = new THREE.Box3().setFromObject(obstacle);
|
|
|
|
| 859 |
}
|
| 860 |
}
|
| 861 |
}
|
|
|
|
| 862 |
|
| 863 |
+
createMuzzleFlash() {
|
| 864 |
+
if (!this.mesh) return;
|
| 865 |
+
|
| 866 |
+
const flashGroup = new THREE.Group();
|
| 867 |
+
|
| 868 |
+
const flameGeometry = new THREE.SphereGeometry(1.0, 8, 8);
|
| 869 |
+
const flameMaterial = new THREE.MeshBasicMaterial({
|
| 870 |
+
color: 0xffa500,
|
| 871 |
+
transparent: true,
|
| 872 |
+
opacity: 0.8
|
| 873 |
+
});
|
| 874 |
+
const flame = new THREE.Mesh(flameGeometry, flameMaterial);
|
| 875 |
+
flame.scale.set(2, 2, 3);
|
| 876 |
+
flashGroup.add(flame);
|
| 877 |
+
|
| 878 |
+
const smokeGeometry = new THREE.SphereGeometry(0.8, 8, 8);
|
| 879 |
+
const smokeMaterial = new THREE.MeshBasicMaterial({
|
| 880 |
+
color: 0x555555,
|
| 881 |
+
transparent: true,
|
| 882 |
+
opacity: 0.5
|
| 883 |
+
});
|
| 884 |
+
|
| 885 |
+
for (let i = 0; i < 5; i++) {
|
| 886 |
+
const smoke = new THREE.Mesh(smokeGeometry, smokeMaterial);
|
| 887 |
+
smoke.position.set(
|
| 888 |
+
Math.random() * 1 - 0.5,
|
| 889 |
+
Math.random() * 1 - 0.5,
|
| 890 |
+
-1 - Math.random()
|
| 891 |
+
);
|
| 892 |
+
smoke.scale.set(1.5, 1.5, 1.5);
|
| 893 |
+
flashGroup.add(smoke);
|
| 894 |
+
}
|
| 895 |
+
|
| 896 |
+
const muzzleOffset = new THREE.Vector3(0, 0.5, 4);
|
| 897 |
+
const muzzlePosition = new THREE.Vector3();
|
| 898 |
+
const meshWorldQuaternion = new THREE.Quaternion();
|
| 899 |
+
|
| 900 |
+
this.mesh.getWorldPosition(muzzlePosition);
|
| 901 |
+
this.mesh.getWorldQuaternion(meshWorldQuaternion);
|
| 902 |
+
|
| 903 |
+
muzzleOffset.applyQuaternion(meshWorldQuaternion);
|
| 904 |
+
muzzlePosition.add(muzzleOffset);
|
| 905 |
+
|
| 906 |
+
flashGroup.position.copy(muzzlePosition);
|
| 907 |
+
flashGroup.quaternion.copy(meshWorldQuaternion);
|
| 908 |
+
|
| 909 |
+
this.scene.add(flashGroup);
|
| 910 |
+
|
| 911 |
+
setTimeout(() => {
|
| 912 |
+
this.scene.remove(flashGroup);
|
| 913 |
+
}, 500);
|
| 914 |
+
}
|
| 915 |
|
| 916 |
shoot(playerPosition) {
|
| 917 |
+
const currentTime = Date.now();
|
| 918 |
+
const attackInterval = this.type === 'tank' ?
|
| 919 |
+
ENEMY_CONFIG.ATTACK_INTERVAL :
|
| 920 |
+
ENEMY_CONFIG.ATTACK_INTERVAL * 1.5;
|
| 921 |
|
| 922 |
+
if (currentTime - this.lastAttackTime < attackInterval) return;
|
| 923 |
|
| 924 |
+
this.createMuzzleFlash();
|
|
|
|
| 925 |
|
| 926 |
+
const enemyFireSound = new Audio('sounds/mbtfire5.ogg');
|
| 927 |
+
enemyFireSound.volume = 0.3;
|
| 928 |
+
enemyFireSound.play();
|
|
|
|
| 929 |
|
| 930 |
+
const bulletGeometry = new THREE.CylinderGeometry(0.2, 0.2, 2, 8);
|
| 931 |
+
const bulletMaterial = new THREE.MeshBasicMaterial({
|
| 932 |
+
color: 0xff0000,
|
| 933 |
+
emissive: 0xff0000,
|
| 934 |
+
emissiveIntensity: 0.5
|
| 935 |
+
});
|
| 936 |
+
const bullet = new THREE.Mesh(bulletGeometry, bulletMaterial);
|
|
|
|
| 937 |
|
| 938 |
+
const muzzleOffset = new THREE.Vector3(0, 0.5, 4);
|
| 939 |
+
const muzzlePosition = new THREE.Vector3();
|
| 940 |
+
this.mesh.getWorldPosition(muzzlePosition);
|
| 941 |
+
muzzleOffset.applyQuaternion(this.mesh.quaternion);
|
| 942 |
+
muzzlePosition.add(muzzleOffset);
|
| 943 |
+
|
| 944 |
+
bullet.position.copy(muzzlePosition);
|
| 945 |
+
bullet.quaternion.copy(this.mesh.quaternion);
|
| 946 |
+
|
| 947 |
+
const direction = new THREE.Vector3()
|
| 948 |
+
.subVectors(playerPosition, muzzlePosition)
|
| 949 |
+
.normalize();
|
| 950 |
+
|
| 951 |
+
const bulletSpeed = this.type === 'tank' ?
|
| 952 |
+
ENEMY_CONFIG.BULLET_SPEED :
|
| 953 |
+
ENEMY_CONFIG.BULLET_SPEED * 0.8;
|
| 954 |
+
|
| 955 |
+
bullet.velocity = direction.multiplyScalar(bulletSpeed);
|
| 956 |
+
|
| 957 |
+
const trailGeometry = new THREE.CylinderGeometry(0.1, 0.1, 1, 8);
|
| 958 |
+
const trailMaterial = new THREE.MeshBasicMaterial({
|
| 959 |
+
color: 0xff4444,
|
| 960 |
+
transparent: true,
|
| 961 |
+
opacity: 0.5
|
| 962 |
+
});
|
| 963 |
+
|
| 964 |
+
const trail = new THREE.Mesh(trailGeometry, trailMaterial);
|
| 965 |
+
trail.position.z = -1;
|
| 966 |
+
bullet.add(trail);
|
| 967 |
+
|
| 968 |
+
this.scene.add(bullet);
|
| 969 |
+
this.bullets.push(bullet);
|
| 970 |
+
this.lastAttackTime = currentTime;
|
| 971 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 972 |
|
| 973 |
takeDamage(damage) {
|
| 974 |
this.health -= damage;
|