Spaces:
Running
Running
Update game.js
Browse files
game.js
CHANGED
@@ -691,16 +691,37 @@ class EnemyFighter {
|
|
691 |
this.velocity = new THREE.Vector3(0, 0, 120);
|
692 |
this.rotation = new THREE.Euler(0, 0, 0);
|
693 |
this.health = GAME_CONSTANTS.MAX_HEALTH; // ์ฒด๋ ฅ 1000
|
694 |
-
this.speed =
|
695 |
this.bullets = [];
|
696 |
this.lastShootTime = 0;
|
697 |
|
|
|
698 |
this.aiState = 'patrol';
|
699 |
this.targetPosition = position.clone();
|
700 |
this.patrolCenter = position.clone();
|
701 |
-
this.patrolRadius =
|
702 |
this.lastStateChange = 0;
|
703 |
this.playerFighter = null; // ํ๋ ์ด์ด ์ฐธ์กฐ ์ ์ฅ์ฉ
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
704 |
}
|
705 |
|
706 |
async initialize(loader) {
|
@@ -757,52 +778,86 @@ class EnemyFighter {
|
|
757 |
const currentTime = Date.now();
|
758 |
const distanceToPlayer = this.position.distanceTo(playerPosition);
|
759 |
|
760 |
-
|
761 |
-
|
762 |
-
|
763 |
-
.
|
764 |
-
|
765 |
-
|
766 |
-
|
767 |
-
|
768 |
-
|
769 |
-
this.
|
770 |
}
|
771 |
} else {
|
772 |
-
if (this.
|
773 |
-
|
774 |
-
this.
|
775 |
-
new THREE.Vector3(
|
776 |
-
Math.cos(angle) * this.patrolRadius,
|
777 |
-
(Math.random() - 0.5) * 1000,
|
778 |
-
Math.sin(angle) * this.patrolRadius
|
779 |
-
)
|
780 |
-
);
|
781 |
}
|
782 |
-
|
783 |
-
const direction = new THREE.Vector3()
|
784 |
-
.subVectors(this.targetPosition, this.position)
|
785 |
-
.normalize();
|
786 |
-
|
787 |
-
this.velocity = direction.multiplyScalar(this.speed * 0.7);
|
788 |
-
this.rotation.y = Math.atan2(direction.x, direction.z);
|
789 |
}
|
790 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
791 |
this.position.add(this.velocity.clone().multiplyScalar(deltaTime));
|
792 |
|
793 |
-
|
794 |
-
|
|
|
|
|
795 |
}
|
796 |
-
if (this.position.y > GAME_CONSTANTS.MAX_ALTITUDE) {
|
797 |
-
this.position.y = GAME_CONSTANTS.MAX_ALTITUDE;
|
|
|
798 |
}
|
799 |
|
|
|
800 |
const mapLimit = GAME_CONSTANTS.MAP_SIZE / 2;
|
801 |
-
if (this.position.x > mapLimit
|
802 |
-
|
803 |
-
|
804 |
-
|
|
|
|
|
805 |
|
|
|
806 |
this.mesh.position.copy(this.position);
|
807 |
this.mesh.rotation.x = this.rotation.x;
|
808 |
this.mesh.rotation.y = this.rotation.y + 3 * Math.PI / 2;
|
@@ -810,6 +865,137 @@ class EnemyFighter {
|
|
810 |
|
811 |
this.updateBullets(deltaTime);
|
812 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
813 |
|
814 |
shoot() {
|
815 |
this.lastShootTime = Date.now();
|
@@ -1861,6 +2047,11 @@ class Game {
|
|
1861 |
}
|
1862 |
|
1863 |
if (this.isStarted) {
|
|
|
|
|
|
|
|
|
|
|
1864 |
this.enemies.forEach(enemy => {
|
1865 |
enemy.update(this.fighter.position, deltaTime);
|
1866 |
});
|
|
|
691 |
this.velocity = new THREE.Vector3(0, 0, 120);
|
692 |
this.rotation = new THREE.Euler(0, 0, 0);
|
693 |
this.health = GAME_CONSTANTS.MAX_HEALTH; // ์ฒด๋ ฅ 1000
|
694 |
+
this.speed = 386; // 750kt in m/s (750 * 0.514444)
|
695 |
this.bullets = [];
|
696 |
this.lastShootTime = 0;
|
697 |
|
698 |
+
// ๊ฐ์ ๋ AI ์ํ
|
699 |
this.aiState = 'patrol';
|
700 |
this.targetPosition = position.clone();
|
701 |
this.patrolCenter = position.clone();
|
702 |
+
this.patrolRadius = 3000;
|
703 |
this.lastStateChange = 0;
|
704 |
this.playerFighter = null; // ํ๋ ์ด์ด ์ฐธ์กฐ ์ ์ฅ์ฉ
|
705 |
+
|
706 |
+
// ํ์ค์ ์ธ ๋นํ ๋ฌผ๋ฆฌ
|
707 |
+
this.targetRotation = new THREE.Euler(0, 0, 0);
|
708 |
+
this.currentRoll = 0;
|
709 |
+
this.currentPitch = 0;
|
710 |
+
this.currentYaw = 0;
|
711 |
+
this.throttle = 0.75; // ๊ธฐ๋ณธ ์ค๋กํ 75%
|
712 |
+
|
713 |
+
// ํํผ ๊ธฐ๋
|
714 |
+
this.evasionTimer = 0;
|
715 |
+
this.evasionPattern = 0;
|
716 |
+
|
717 |
+
// ๋ค๋ฅธ ์ ๊ธฐ๋ค๊ณผ์ ์ถฉ๋ ํํผ
|
718 |
+
this.nearbyEnemies = [];
|
719 |
+
this.separationRadius = 300; // ์ต์ ๊ฑฐ๋ฆฌ 300m
|
720 |
+
|
721 |
+
// ๊ณต๊ฒฉ ํจํด
|
722 |
+
this.attackAngle = 0;
|
723 |
+
this.attackDistance = 0;
|
724 |
+
this.lastAttackTime = 0;
|
725 |
}
|
726 |
|
727 |
async initialize(loader) {
|
|
|
778 |
const currentTime = Date.now();
|
779 |
const distanceToPlayer = this.position.distanceTo(playerPosition);
|
780 |
|
781 |
+
// AI ์ํ ๊ฒฐ์
|
782 |
+
if (distanceToPlayer < 3000) {
|
783 |
+
if (this.aiState !== 'combat') {
|
784 |
+
this.aiState = 'combat';
|
785 |
+
this.lastStateChange = currentTime;
|
786 |
+
}
|
787 |
+
} else if (distanceToPlayer < 6000) {
|
788 |
+
if (this.aiState !== 'approach') {
|
789 |
+
this.aiState = 'approach';
|
790 |
+
this.lastStateChange = currentTime;
|
791 |
}
|
792 |
} else {
|
793 |
+
if (this.aiState !== 'patrol') {
|
794 |
+
this.aiState = 'patrol';
|
795 |
+
this.lastStateChange = currentTime;
|
|
|
|
|
|
|
|
|
|
|
|
|
796 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
797 |
}
|
798 |
|
799 |
+
// ์ํ๋ณ ํ๋
|
800 |
+
switch(this.aiState) {
|
801 |
+
case 'combat':
|
802 |
+
this.updateCombatBehavior(playerPosition, deltaTime, currentTime);
|
803 |
+
break;
|
804 |
+
case 'approach':
|
805 |
+
this.updateApproachBehavior(playerPosition, deltaTime);
|
806 |
+
break;
|
807 |
+
case 'patrol':
|
808 |
+
this.updatePatrolBehavior(deltaTime);
|
809 |
+
break;
|
810 |
+
}
|
811 |
+
|
812 |
+
// ๋ค๋ฅธ ์ ๊ธฐ์์ ์ถฉ๋ ํํผ
|
813 |
+
this.avoidOtherEnemies(deltaTime);
|
814 |
+
|
815 |
+
// ๋ถ๋๋ฌ์ด ํ์ ๋ณด๊ฐ
|
816 |
+
this.rotation.x = THREE.MathUtils.lerp(this.rotation.x, this.targetRotation.x, deltaTime * 2.0);
|
817 |
+
this.rotation.y = THREE.MathUtils.lerp(this.rotation.y, this.targetRotation.y, deltaTime * 3.0);
|
818 |
+
this.rotation.z = THREE.MathUtils.lerp(this.rotation.z, this.targetRotation.z, deltaTime * 2.5);
|
819 |
+
|
820 |
+
// ์๋ ๊ณ์ฐ (์ค๋กํ ๊ธฐ๋ฐ)
|
821 |
+
const targetSpeed = this.speed * this.throttle;
|
822 |
+
const currentSpeed = this.velocity.length();
|
823 |
+
const newSpeed = THREE.MathUtils.lerp(currentSpeed, targetSpeed, deltaTime * 0.5);
|
824 |
+
|
825 |
+
// ๋ฐฉํฅ ๋ฒกํฐ ๊ณ์ฐ
|
826 |
+
const forward = new THREE.Vector3(0, 0, 1);
|
827 |
+
forward.applyEuler(this.rotation);
|
828 |
+
|
829 |
+
// ์ค๋ ฅ๊ณผ ์๋ ฅ ํจ๊ณผ
|
830 |
+
const liftFactor = Math.min(1.0, newSpeed / this.speed);
|
831 |
+
const gravityEffect = GAME_CONSTANTS.GRAVITY * deltaTime * 0.15;
|
832 |
+
const lift = gravityEffect * liftFactor * 0.8;
|
833 |
+
|
834 |
+
// ์๋ ๋ฒกํฐ ์
๋ฐ์ดํธ
|
835 |
+
this.velocity = forward.multiplyScalar(newSpeed);
|
836 |
+
this.velocity.y += (lift - gravityEffect);
|
837 |
+
|
838 |
+
// ์์น ์
๋ฐ์ดํธ
|
839 |
this.position.add(this.velocity.clone().multiplyScalar(deltaTime));
|
840 |
|
841 |
+
// ๊ณ ๋ ์ ํ
|
842 |
+
if (this.position.y < 500) {
|
843 |
+
this.position.y = 500;
|
844 |
+
this.targetRotation.x = Math.max(this.targetRotation.x, -0.2); // ๊ธฐ์๋ฅผ ๋ค์ด ์์น
|
845 |
}
|
846 |
+
if (this.position.y > GAME_CONSTANTS.MAX_ALTITUDE - 500) {
|
847 |
+
this.position.y = GAME_CONSTANTS.MAX_ALTITUDE - 500;
|
848 |
+
this.targetRotation.x = Math.min(this.targetRotation.x, 0.2); // ๊ธฐ์๋ฅผ ๋ด๋ ค ํ๊ฐ
|
849 |
}
|
850 |
|
851 |
+
// ๋งต ๊ฒฝ๊ณ ์ฒ๋ฆฌ
|
852 |
const mapLimit = GAME_CONSTANTS.MAP_SIZE / 2;
|
853 |
+
if (Math.abs(this.position.x) > mapLimit * 0.8 || Math.abs(this.position.z) > mapLimit * 0.8) {
|
854 |
+
// ๋งต ๊ฒฝ๊ณ์ ๊ฐ๊น์์ง๋ฉด ์ค์์ผ๋ก ์ ํ
|
855 |
+
const centerDirection = this.position.clone().negate().normalize();
|
856 |
+
const angle = Math.atan2(centerDirection.x, centerDirection.z);
|
857 |
+
this.targetRotation.y = angle;
|
858 |
+
}
|
859 |
|
860 |
+
// ๋ฉ์ ์
๋ฐ์ดํธ
|
861 |
this.mesh.position.copy(this.position);
|
862 |
this.mesh.rotation.x = this.rotation.x;
|
863 |
this.mesh.rotation.y = this.rotation.y + 3 * Math.PI / 2;
|
|
|
865 |
|
866 |
this.updateBullets(deltaTime);
|
867 |
}
|
868 |
+
|
869 |
+
updateCombatBehavior(playerPosition, deltaTime, currentTime) {
|
870 |
+
const toPlayer = playerPosition.clone().sub(this.position);
|
871 |
+
const distance = toPlayer.length();
|
872 |
+
toPlayer.normalize();
|
873 |
+
|
874 |
+
// ๊ณต๊ฒฉ ๊ฐ๋ ๊ณ์ฐ
|
875 |
+
const angle = Math.atan2(toPlayer.x, toPlayer.z);
|
876 |
+
const pitchAngle = Math.atan2(-toPlayer.y, Math.sqrt(toPlayer.x * toPlayer.x + toPlayer.z * toPlayer.z));
|
877 |
+
|
878 |
+
// BFM (Basic Fighter Maneuvers) ๊ตฌํ
|
879 |
+
if (distance < 1000) {
|
880 |
+
// ๋๋ฌด ๊ฐ๊น์ฐ๋ฉด ํํผ ๊ธฐ๋
|
881 |
+
this.evasionTimer += deltaTime;
|
882 |
+
if (this.evasionTimer > 2.0) {
|
883 |
+
this.evasionTimer = 0;
|
884 |
+
this.evasionPattern = Math.floor(Math.random() * 3);
|
885 |
+
}
|
886 |
+
|
887 |
+
switch(this.evasionPattern) {
|
888 |
+
case 0: // ๋ฐฐ๋ด ๋กค
|
889 |
+
this.targetRotation.z = Math.sin(this.evasionTimer * Math.PI) * 1.5;
|
890 |
+
this.targetRotation.x = pitchAngle + Math.sin(this.evasionTimer * Math.PI * 2) * 0.3;
|
891 |
+
break;
|
892 |
+
case 1: // ์ด๋ฉ๋ง ํด
|
893 |
+
this.targetRotation.x = -0.5 + this.evasionTimer * 0.5;
|
894 |
+
this.targetRotation.z = this.evasionTimer * Math.PI;
|
895 |
+
break;
|
896 |
+
case 2: // ์คํ๋ฆฟ S
|
897 |
+
this.targetRotation.x = 0.5 - this.evasionTimer * 0.5;
|
898 |
+
this.targetRotation.z = Math.PI;
|
899 |
+
break;
|
900 |
+
}
|
901 |
+
this.throttle = 1.0; // ์ต๋ ์๋๋ก ํํผ
|
902 |
+
} else if (distance < 2000) {
|
903 |
+
// ๊ณต๊ฒฉ ์์น ์ ์
|
904 |
+
this.targetRotation.y = angle;
|
905 |
+
this.targetRotation.x = pitchAngle;
|
906 |
+
this.targetRotation.z = 0;
|
907 |
+
|
908 |
+
// ๋ฆฌ๋ ์ต๊ธ ๊ณ์ฐ (์์ธก ์ฌ๊ฒฉ)
|
909 |
+
const playerVelocity = this.playerFighter ? this.playerFighter.velocity : new THREE.Vector3();
|
910 |
+
const bulletTime = distance / 1200; // ํํ ์๋
|
911 |
+
const leadPosition = playerPosition.clone().add(playerVelocity.clone().multiplyScalar(bulletTime));
|
912 |
+
const leadDirection = leadPosition.sub(this.position).normalize();
|
913 |
+
const leadAngle = Math.atan2(leadDirection.x, leadDirection.z);
|
914 |
+
|
915 |
+
this.targetRotation.y = leadAngle;
|
916 |
+
|
917 |
+
// ์ฌ๊ฒฉ
|
918 |
+
if (currentTime - this.lastShootTime > 800) {
|
919 |
+
this.shoot();
|
920 |
+
}
|
921 |
+
|
922 |
+
this.throttle = 0.9;
|
923 |
+
} else {
|
924 |
+
// ์ถ์
|
925 |
+
this.targetRotation.y = angle;
|
926 |
+
this.targetRotation.x = pitchAngle * 0.5; // ๋ถ๋๋ฌ์ด ํผ์น ์กฐ์
|
927 |
+
this.targetRotation.z = 0;
|
928 |
+
this.throttle = 0.85;
|
929 |
+
}
|
930 |
+
}
|
931 |
+
|
932 |
+
updateApproachBehavior(playerPosition, deltaTime) {
|
933 |
+
const toPlayer = playerPosition.clone().sub(this.position);
|
934 |
+
const angle = Math.atan2(toPlayer.x, toPlayer.z);
|
935 |
+
|
936 |
+
// ๋ถ๋๋ฌ์ด ์ ๊ทผ
|
937 |
+
this.targetRotation.y = angle;
|
938 |
+
this.targetRotation.x = 0;
|
939 |
+
this.targetRotation.z = Math.sin(Date.now() * 0.001) * 0.3; // ์ฝ๊ฐ์ ๋กค๋ง
|
940 |
+
this.throttle = 0.8;
|
941 |
+
}
|
942 |
+
|
943 |
+
updatePatrolBehavior(deltaTime) {
|
944 |
+
// ์์ฐฐ ํจํด
|
945 |
+
if (this.position.distanceTo(this.targetPosition) < 500) {
|
946 |
+
// ์๋ก์ด ์์ฐฐ ์ง์ ์ค์
|
947 |
+
const angle = Math.random() * Math.PI * 2;
|
948 |
+
const distance = this.patrolRadius * (0.5 + Math.random() * 0.5);
|
949 |
+
this.targetPosition = this.patrolCenter.clone().add(
|
950 |
+
new THREE.Vector3(
|
951 |
+
Math.cos(angle) * distance,
|
952 |
+
(Math.random() - 0.5) * 1000,
|
953 |
+
Math.sin(angle) * distance
|
954 |
+
)
|
955 |
+
);
|
956 |
+
}
|
957 |
+
|
958 |
+
const toTarget = this.targetPosition.clone().sub(this.position);
|
959 |
+
const angle = Math.atan2(toTarget.x, toTarget.z);
|
960 |
+
|
961 |
+
this.targetRotation.y = angle;
|
962 |
+
this.targetRotation.x = 0;
|
963 |
+
this.targetRotation.z = Math.sin(Date.now() * 0.0005) * 0.2; // ์๋งํ ๋กค๋ง
|
964 |
+
this.throttle = 0.7;
|
965 |
+
}
|
966 |
+
|
967 |
+
avoidOtherEnemies(deltaTime) {
|
968 |
+
if (!this.nearbyEnemies) return;
|
969 |
+
|
970 |
+
let avoidanceVector = new THREE.Vector3();
|
971 |
+
let tooClose = false;
|
972 |
+
|
973 |
+
this.nearbyEnemies.forEach(enemy => {
|
974 |
+
if (enemy === this || !enemy.position) return;
|
975 |
+
|
976 |
+
const distance = this.position.distanceTo(enemy.position);
|
977 |
+
if (distance < this.separationRadius && distance > 0) {
|
978 |
+
// ๋ฐ๋ฐ๋ ฅ ๊ณ์ฐ
|
979 |
+
const force = this.position.clone().sub(enemy.position).normalize();
|
980 |
+
const strength = 1 - (distance / this.separationRadius);
|
981 |
+
avoidanceVector.add(force.multiplyScalar(strength));
|
982 |
+
tooClose = true;
|
983 |
+
}
|
984 |
+
});
|
985 |
+
|
986 |
+
if (tooClose) {
|
987 |
+
// ํํผ ๋ฐฉํฅ์ผ๋ก ์ ํ
|
988 |
+
const avoidAngle = Math.atan2(avoidanceVector.x, avoidanceVector.z);
|
989 |
+
this.targetRotation.y = THREE.MathUtils.lerp(this.targetRotation.y, avoidAngle, deltaTime * 5);
|
990 |
+
|
991 |
+
// ๊ณ ๋ ๋ณ๊ฒฝ์ผ๋ก ์ถ๊ฐ ํํผ
|
992 |
+
if (avoidanceVector.y > 0.5) {
|
993 |
+
this.targetRotation.x = -0.3; // ์์น
|
994 |
+
} else if (avoidanceVector.y < -0.5) {
|
995 |
+
this.targetRotation.x = 0.3; // ํ๊ฐ
|
996 |
+
}
|
997 |
+
}
|
998 |
+
}
|
999 |
|
1000 |
shoot() {
|
1001 |
this.lastShootTime = Date.now();
|
|
|
2047 |
}
|
2048 |
|
2049 |
if (this.isStarted) {
|
2050 |
+
// ์ ๊ธฐ๋ค์๊ฒ ์๋ก์ ์ฐธ์กฐ ์ ๋ฌ (์ถฉ๋ ํํผ์ฉ)
|
2051 |
+
this.enemies.forEach(enemy => {
|
2052 |
+
enemy.nearbyEnemies = this.enemies;
|
2053 |
+
});
|
2054 |
+
|
2055 |
this.enemies.forEach(enemy => {
|
2056 |
enemy.update(this.fighter.position, deltaTime);
|
2057 |
});
|