Spaces:
Running
Running
cutechicken
commited on
Commit
โข
876ef81
1
Parent(s):
d825a32
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;
|