Spaces:
Running
Running
cutechicken
commited on
Commit
โข
9c345a3
1
Parent(s):
ce037c6
Update game.js
Browse files
game.js
CHANGED
@@ -825,17 +825,17 @@ class Game {
|
|
825 |
mainLight.castShadow = true;
|
826 |
|
827 |
// ๊ทธ๋ฆผ์ ํ์ง ํฅ์
|
828 |
-
mainLight.shadow.mapSize.width = 4096;
|
829 |
mainLight.shadow.mapSize.height = 4096;
|
830 |
mainLight.shadow.camera.near = 0.5;
|
831 |
-
mainLight.shadow.camera.far = MAP_SIZE * 2;
|
832 |
-
mainLight.shadow.camera.left = -MAP_SIZE;
|
833 |
mainLight.shadow.camera.right = MAP_SIZE;
|
834 |
mainLight.shadow.camera.top = MAP_SIZE;
|
835 |
mainLight.shadow.camera.bottom = -MAP_SIZE;
|
836 |
mainLight.shadow.bias = -0.001;
|
837 |
mainLight.shadow.radius = 2;
|
838 |
-
mainLight.shadow.normalBias = 0.02;
|
839 |
|
840 |
this.scene.add(mainLight);
|
841 |
|
@@ -852,8 +852,8 @@ class Game {
|
|
852 |
);
|
853 |
this.scene.add(hemisphereLight);
|
854 |
|
855 |
-
// ์งํ ์์ฑ ์์
|
856 |
-
const groundGeometry = new THREE.PlaneGeometry(MAP_SIZE, MAP_SIZE,
|
857 |
const groundMaterial = new THREE.MeshStandardMaterial({
|
858 |
color: 0xD2B48C,
|
859 |
roughness: 0.8,
|
@@ -865,10 +865,41 @@ class Game {
|
|
865 |
ground.rotation.x = -Math.PI / 2;
|
866 |
ground.receiveShadow = true;
|
867 |
|
868 |
-
//
|
869 |
const vertices = ground.geometry.attributes.position.array;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
870 |
for (let i = 0; i < vertices.length; i += 3) {
|
871 |
-
vertices[i
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
872 |
}
|
873 |
|
874 |
ground.geometry.attributes.position.needsUpdate = true;
|
@@ -876,16 +907,28 @@ class Game {
|
|
876 |
this.ground = ground;
|
877 |
this.scene.add(ground);
|
878 |
|
879 |
-
//
|
880 |
-
const
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
881 |
gridHelper.material.opacity = 0.1;
|
882 |
gridHelper.material.transparent = true;
|
883 |
gridHelper.position.y = 0.1;
|
884 |
this.scene.add(gridHelper);
|
885 |
|
886 |
-
// ์ฅ์ ๋ฌผ ๋ฐฐ์ด ์ด๊ธฐํ
|
887 |
-
this.obstacles = [];
|
888 |
-
|
889 |
// ์ฌ๋ง ์ฅ์ ์ถ๊ฐ
|
890 |
await this.addDesertDecorations();
|
891 |
|
@@ -895,11 +938,26 @@ class Game {
|
|
895 |
throw new Error('Tank loading failed');
|
896 |
}
|
897 |
|
898 |
-
// ์คํฐ ์์น ๊ฒ์ฆ
|
899 |
const spawnPos = this.findValidSpawnPosition();
|
900 |
-
|
901 |
-
|
902 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
903 |
|
904 |
// ์นด๋ฉ๋ผ ์ด๊ธฐ ์์น ์ค์
|
905 |
const tankPosition = this.tank.getPosition();
|
@@ -979,88 +1037,64 @@ class Game {
|
|
979 |
}
|
980 |
|
981 |
async addDesertDecorations() {
|
982 |
-
|
983 |
-
|
984 |
-
|
985 |
-
|
986 |
-
|
987 |
-
|
988 |
-
new THREE.DodecahedronGeometry(4)
|
989 |
-
];
|
990 |
-
|
991 |
-
const rockMaterial = new THREE.MeshStandardMaterial({
|
992 |
-
color: 0x8B4513,
|
993 |
-
roughness: 0.9,
|
994 |
-
metalness: 0.1
|
995 |
-
});
|
996 |
|
997 |
-
|
998 |
-
|
999 |
-
|
1000 |
-
|
1001 |
-
const position = new THREE.Vector3(
|
1002 |
-
(Math.random() - 0.5) * MAP_SIZE * 0.9,
|
1003 |
-
Math.random() * 2,
|
1004 |
-
(Math.random() - 0.5) * MAP_SIZE * 0.9
|
1005 |
-
);
|
1006 |
-
|
1007 |
-
rock.position.copy(position);
|
1008 |
-
rock.rotation.set(
|
1009 |
-
Math.random() * Math.PI,
|
1010 |
-
Math.random() * Math.PI,
|
1011 |
-
Math.random() * Math.PI
|
1012 |
-
);
|
1013 |
-
|
1014 |
-
rock.scale.set(
|
1015 |
-
1 + Math.random() * 0.5,
|
1016 |
-
1 + Math.random() * 0.5,
|
1017 |
-
1 + Math.random() * 0.5
|
1018 |
-
);
|
1019 |
-
|
1020 |
-
rock.castShadow = true;
|
1021 |
-
rock.receiveShadow = true;
|
1022 |
-
|
1023 |
-
// ์ถฉ๋ ๋ฐ์ค ์์ฑ ๋ฐ ์ ์ฅ
|
1024 |
-
const boundingBox = new THREE.Box3().setFromObject(rock);
|
1025 |
-
this.obstacles.push({
|
1026 |
-
mesh: rock,
|
1027 |
-
boundingBox: boundingBox,
|
1028 |
-
type: 'rock'
|
1029 |
});
|
1030 |
-
|
1031 |
-
this.scene.add(rock);
|
1032 |
-
}
|
1033 |
|
1034 |
-
|
1035 |
-
|
1036 |
-
|
1037 |
-
|
1038 |
-
|
1039 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1040 |
|
1041 |
-
|
1042 |
-
|
1043 |
-
|
1044 |
-
|
1045 |
-
|
1046 |
-
|
1047 |
-
|
1048 |
-
|
1049 |
-
|
1050 |
-
|
1051 |
-
|
1052 |
-
|
1053 |
-
|
1054 |
-
|
1055 |
-
|
1056 |
-
|
1057 |
-
|
1058 |
-
|
1059 |
-
|
1060 |
-
|
1061 |
-
this.scene.add(cactus);
|
1062 |
-
}
|
1063 |
-
}
|
1064 |
|
1065 |
getHeightAtPosition(x, z) {
|
1066 |
if (!this.ground) return 0;
|
@@ -1483,123 +1517,171 @@ class Game {
|
|
1483 |
const beatSounds = ['sounds/beat1.ogg', 'sounds/beat2.ogg', 'sounds/beat3.ogg'];
|
1484 |
|
1485 |
const tankPosition = this.tank.getPosition();
|
1486 |
-
|
1487 |
-
|
1488 |
-
// ํฑํฌ์ ์ฅ์ ๋ฌผ(๋ฐ์, ์ ์ธ์ฅ) ์ถฉ๋ ์ฒดํฌ
|
1489 |
-
this.obstacles.forEach(obstacle => {
|
1490 |
-
obstacle.boundingBox.setFromObject(obstacle.mesh);
|
1491 |
-
if (tankBoundingBox.intersectsBox(obstacle.boundingBox)) {
|
1492 |
-
this.tank.body.position.copy(this.previousTankPosition);
|
1493 |
-
}
|
1494 |
-
});
|
1495 |
-
|
1496 |
-
// ์ ์ด์ ์ถฉ๋ ์ฒดํฌ
|
1497 |
this.enemies.forEach(enemy => {
|
1498 |
if (!enemy.mesh || !enemy.isLoaded) return;
|
1499 |
|
1500 |
enemy.bullets.forEach(bullet => {
|
1501 |
-
const
|
1502 |
-
|
1503 |
-
|
1504 |
-
|
1505 |
-
|
1506 |
-
|
1507 |
-
|
1508 |
-
|
1509 |
-
|
1510 |
-
bulletDestroyed = true;
|
1511 |
-
break;
|
1512 |
-
}
|
1513 |
-
}
|
1514 |
-
|
1515 |
-
// ์ด์์ด ์ฅ์ ๋ฌผ์ ๋ง์ง ์์๋ค๋ฉด ํ๋ ์ด์ด์ ์ถฉ๋ ์ฒดํฌ
|
1516 |
-
if (!bulletDestroyed) {
|
1517 |
-
const distance = bullet.position.distanceTo(tankPosition);
|
1518 |
-
if (distance < 1) {
|
1519 |
-
const randomBeatSound = beatSounds[Math.floor(Math.random() * beatSounds.length)];
|
1520 |
-
const beatAudio = new Audio(randomBeatSound);
|
1521 |
-
beatAudio.play();
|
1522 |
-
|
1523 |
-
if (this.tank.takeDamage(250)) {
|
1524 |
-
this.endGame();
|
1525 |
-
}
|
1526 |
-
this.scene.remove(bullet);
|
1527 |
-
enemy.bullets = enemy.bullets.filter(b => b !== bullet);
|
1528 |
-
this.createExplosion(bullet.position);
|
1529 |
-
document.getElementById('health').style.width =
|
1530 |
-
`${(this.tank.health / MAX_HEALTH) * 100}%`;
|
1531 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
1532 |
}
|
1533 |
});
|
1534 |
});
|
1535 |
|
1536 |
-
// ํ๋ ์ด์ด
|
1537 |
this.tank.bullets.forEach((bullet, bulletIndex) => {
|
1538 |
-
|
1539 |
-
|
1540 |
|
1541 |
-
|
1542 |
-
|
1543 |
-
|
1544 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1545 |
this.scene.remove(bullet);
|
1546 |
this.tank.bullets.splice(bulletIndex, 1);
|
1547 |
-
|
1548 |
-
break;
|
1549 |
}
|
1550 |
-
}
|
1551 |
-
|
1552 |
-
// ์ด์์ด ์ฅ์ ๋ฌผ์ ๋ง์ง ์์๋ค๋ฉด ์ ๊ณผ ์ถฉ๋ ์ฒดํฌ
|
1553 |
-
if (!bulletDestroyed) {
|
1554 |
-
this.enemies.forEach((enemy, enemyIndex) => {
|
1555 |
-
if (!enemy.mesh || !enemy.isLoaded) return;
|
1556 |
-
|
1557 |
-
const distance = bullet.position.distanceTo(enemy.mesh.position);
|
1558 |
-
if (distance < 2) {
|
1559 |
-
const randomHitSound = hitSounds[Math.floor(Math.random() * hitSounds.length)];
|
1560 |
-
const hitAudio = new Audio(randomHitSound);
|
1561 |
-
hitAudio.play();
|
1562 |
-
|
1563 |
-
if (enemy.takeDamage(50)) {
|
1564 |
-
enemy.destroy();
|
1565 |
-
this.enemies.splice(enemyIndex, 1);
|
1566 |
-
this.score += 100;
|
1567 |
-
document.getElementById('score').textContent = `Score: ${this.score}`;
|
1568 |
-
}
|
1569 |
-
this.scene.remove(bullet);
|
1570 |
-
this.tank.bullets.splice(bulletIndex, 1);
|
1571 |
-
this.createExplosion(bullet.position);
|
1572 |
-
}
|
1573 |
-
});
|
1574 |
-
}
|
1575 |
});
|
1576 |
|
1577 |
-
// ์
|
|
|
1578 |
this.enemies.forEach(enemy => {
|
1579 |
if (!enemy.mesh || !enemy.isLoaded) return;
|
1580 |
|
1581 |
-
const
|
1582 |
-
|
1583 |
-
// ์ ํฑํฌ์ ์ฅ์ ๋ฌผ ์ถฉ๋ ์ฒดํฌ
|
1584 |
-
for (const obstacle of this.obstacles) {
|
1585 |
-
if (enemyBox.intersectsBox(obstacle.boundingBox)) {
|
1586 |
-
enemy.mesh.position.copy(enemy.lastPosition || enemy.mesh.position);
|
1587 |
-
break;
|
1588 |
-
}
|
1589 |
-
}
|
1590 |
-
|
1591 |
-
// ์ ํฑํฌ์ ํ๋ ์ด์ด ํฑํฌ ์ถฉ๋ ์ฒดํฌ
|
1592 |
-
if (tankBoundingBox.intersectsBox(enemyBox)) {
|
1593 |
this.tank.body.position.copy(this.previousTankPosition);
|
1594 |
}
|
1595 |
-
|
1596 |
-
// ์ ํฑํฌ์ ํ์ฌ ์์น ์ ์ฅ
|
1597 |
-
enemy.lastPosition = enemy.mesh.position.clone();
|
1598 |
});
|
1599 |
|
1600 |
// ์ด์ ์์น ์ ์ฅ
|
1601 |
this.previousTankPosition.copy(this.tank.body.position);
|
1602 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1603 |
|
1604 |
// Start game
|
1605 |
window.startGame = function() {
|
|
|
825 |
mainLight.castShadow = true;
|
826 |
|
827 |
// ๊ทธ๋ฆผ์ ํ์ง ํฅ์
|
828 |
+
mainLight.shadow.mapSize.width = 4096; // ๊ทธ๋ฆผ์ ํด์๋ ์ฆ๊ฐ
|
829 |
mainLight.shadow.mapSize.height = 4096;
|
830 |
mainLight.shadow.camera.near = 0.5;
|
831 |
+
mainLight.shadow.camera.far = MAP_SIZE * 2; // ๊ทธ๋ฆผ์ ๊ฑฐ๋ฆฌ ์ฆ๊ฐ
|
832 |
+
mainLight.shadow.camera.left = -MAP_SIZE; // ๊ทธ๋ฆผ์ ์์ญ ํ์ฅ
|
833 |
mainLight.shadow.camera.right = MAP_SIZE;
|
834 |
mainLight.shadow.camera.top = MAP_SIZE;
|
835 |
mainLight.shadow.camera.bottom = -MAP_SIZE;
|
836 |
mainLight.shadow.bias = -0.001;
|
837 |
mainLight.shadow.radius = 2;
|
838 |
+
mainLight.shadow.normalBias = 0.02; // ๊ทธ๋ฆผ์ ์ํฐํฉํธ ๊ฐ์
|
839 |
|
840 |
this.scene.add(mainLight);
|
841 |
|
|
|
852 |
);
|
853 |
this.scene.add(hemisphereLight);
|
854 |
|
855 |
+
// ์งํ ์์ฑ ์์
|
856 |
+
const groundGeometry = new THREE.PlaneGeometry(MAP_SIZE, MAP_SIZE, 100, 100);
|
857 |
const groundMaterial = new THREE.MeshStandardMaterial({
|
858 |
color: 0xD2B48C,
|
859 |
roughness: 0.8,
|
|
|
865 |
ground.rotation.x = -Math.PI / 2;
|
866 |
ground.receiveShadow = true;
|
867 |
|
868 |
+
// ์งํ ๋์ด ์ค์
|
869 |
const vertices = ground.geometry.attributes.position.array;
|
870 |
+
const heightScale = 15;
|
871 |
+
const baseFrequency = 0.008;
|
872 |
+
|
873 |
+
// ํ์ง ์์ญ ์ ์
|
874 |
+
const flatlandRadius = MAP_SIZE * 0.3;
|
875 |
+
const transitionZone = MAP_SIZE * 0.1;
|
876 |
+
|
877 |
for (let i = 0; i < vertices.length; i += 3) {
|
878 |
+
const x = vertices[i];
|
879 |
+
const y = vertices[i + 1];
|
880 |
+
|
881 |
+
const distanceFromCenter = Math.sqrt(x * x + y * y);
|
882 |
+
|
883 |
+
if (distanceFromCenter < flatlandRadius) {
|
884 |
+
vertices[i + 2] = 0;
|
885 |
+
}
|
886 |
+
else if (distanceFromCenter < flatlandRadius + transitionZone) {
|
887 |
+
const transitionFactor = (distanceFromCenter - flatlandRadius) / transitionZone;
|
888 |
+
let height = 0;
|
889 |
+
|
890 |
+
height += Math.sin(x * baseFrequency) * Math.cos(y * baseFrequency) * heightScale;
|
891 |
+
height += Math.sin(x * baseFrequency * 2) * Math.cos(y * baseFrequency * 2) * (heightScale * 0.5);
|
892 |
+
height += Math.sin(x * baseFrequency * 4) * Math.cos(y * baseFrequency * 4) * (heightScale * 0.25);
|
893 |
+
|
894 |
+
vertices[i + 2] = height * transitionFactor;
|
895 |
+
}
|
896 |
+
else {
|
897 |
+
let height = 0;
|
898 |
+
height += Math.sin(x * baseFrequency) * Math.cos(y * baseFrequency) * heightScale;
|
899 |
+
height += Math.sin(x * baseFrequency * 2) * Math.cos(y * baseFrequency * 2) * (heightScale * 0.5);
|
900 |
+
height += Math.sin(x * baseFrequency * 4) * Math.cos(y * baseFrequency * 4) * (heightScale * 0.25);
|
901 |
+
vertices[i + 2] = height;
|
902 |
+
}
|
903 |
}
|
904 |
|
905 |
ground.geometry.attributes.position.needsUpdate = true;
|
|
|
907 |
this.ground = ground;
|
908 |
this.scene.add(ground);
|
909 |
|
910 |
+
// ๋ฑ๊ณ ์ ํจ๊ณผ
|
911 |
+
const contourMaterial = new THREE.LineBasicMaterial({
|
912 |
+
color: 0x000000,
|
913 |
+
opacity: 0.15,
|
914 |
+
transparent: true
|
915 |
+
});
|
916 |
+
|
917 |
+
const contourLines = new THREE.LineSegments(
|
918 |
+
new THREE.EdgesGeometry(groundGeometry),
|
919 |
+
contourMaterial
|
920 |
+
);
|
921 |
+
contourLines.rotation.x = -Math.PI / 2;
|
922 |
+
contourLines.position.y = 0.1;
|
923 |
+
this.scene.add(contourLines);
|
924 |
+
|
925 |
+
// ๊ฒฉ์ ํจ๊ณผ
|
926 |
+
const gridHelper = new THREE.GridHelper(flatlandRadius * 2, 50, 0x000000, 0x000000);
|
927 |
gridHelper.material.opacity = 0.1;
|
928 |
gridHelper.material.transparent = true;
|
929 |
gridHelper.position.y = 0.1;
|
930 |
this.scene.add(gridHelper);
|
931 |
|
|
|
|
|
|
|
932 |
// ์ฌ๋ง ์ฅ์ ์ถ๊ฐ
|
933 |
await this.addDesertDecorations();
|
934 |
|
|
|
938 |
throw new Error('Tank loading failed');
|
939 |
}
|
940 |
|
941 |
+
// ์คํฐ ์์น ๊ฒ์ฆ ๋ฐ ์ฌ์์ ๋ก์ง
|
942 |
const spawnPos = this.findValidSpawnPosition();
|
943 |
+
const heightAtSpawn = this.getHeightAtPosition(spawnPos.x, spawnPos.z);
|
944 |
+
const slopeCheckPoints = [
|
945 |
+
{ x: spawnPos.x + 2, z: spawnPos.z },
|
946 |
+
{ x: spawnPos.x - 2, z: spawnPos.z },
|
947 |
+
{ x: spawnPos.x, z: spawnPos.z + 2 },
|
948 |
+
{ x: spawnPos.x, z: spawnPos.z - 2 }
|
949 |
+
];
|
950 |
+
|
951 |
+
const slopes = slopeCheckPoints.map(point => {
|
952 |
+
const pointHeight = this.getHeightAtPosition(point.x, point.z);
|
953 |
+
return Math.abs(pointHeight - heightAtSpawn) / 2;
|
954 |
+
});
|
955 |
+
|
956 |
+
const maxSlope = Math.max(...slopes);
|
957 |
+
if (maxSlope > 0.3) {
|
958 |
+
location.reload();
|
959 |
+
return;
|
960 |
+
}
|
961 |
|
962 |
// ์นด๋ฉ๋ผ ์ด๊ธฐ ์์น ์ค์
|
963 |
const tankPosition = this.tank.getPosition();
|
|
|
1037 |
}
|
1038 |
|
1039 |
async addDesertDecorations() {
|
1040 |
+
// ๋ฐ์ ์์ฑ
|
1041 |
+
const rockGeometries = [
|
1042 |
+
new THREE.DodecahedronGeometry(3),
|
1043 |
+
new THREE.DodecahedronGeometry(2),
|
1044 |
+
new THREE.DodecahedronGeometry(4)
|
1045 |
+
];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1046 |
|
1047 |
+
const rockMaterial = new THREE.MeshStandardMaterial({
|
1048 |
+
color: 0x8B4513,
|
1049 |
+
roughness: 0.9,
|
1050 |
+
metalness: 0.1
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1051 |
});
|
|
|
|
|
|
|
1052 |
|
1053 |
+
for (let i = 0; i < 100; i++) {
|
1054 |
+
const rockGeometry = rockGeometries[Math.floor(Math.random() * rockGeometries.length)];
|
1055 |
+
const rock = new THREE.Mesh(rockGeometry, rockMaterial);
|
1056 |
+
rock.position.set(
|
1057 |
+
(Math.random() - 0.5) * MAP_SIZE * 0.9,
|
1058 |
+
Math.random() * 2,
|
1059 |
+
(Math.random() - 0.5) * MAP_SIZE * 0.9
|
1060 |
+
);
|
1061 |
+
|
1062 |
+
rock.rotation.set(
|
1063 |
+
Math.random() * Math.PI,
|
1064 |
+
Math.random() * Math.PI,
|
1065 |
+
Math.random() * Math.PI
|
1066 |
+
);
|
1067 |
+
|
1068 |
+
rock.scale.set(
|
1069 |
+
1 + Math.random() * 0.5,
|
1070 |
+
1 + Math.random() * 0.5,
|
1071 |
+
1 + Math.random() * 0.5
|
1072 |
+
);
|
1073 |
+
|
1074 |
+
rock.castShadow = true;
|
1075 |
+
rock.receiveShadow = true;
|
1076 |
+
this.scene.add(rock);
|
1077 |
+
}
|
1078 |
|
1079 |
+
// ์ ์ธ์ฅ ์ถ๊ฐ (๊ฐ๋จํ geometry๋ก ํํ)
|
1080 |
+
const cactusGeometry = new THREE.CylinderGeometry(0.5, 0.7, 4, 8);
|
1081 |
+
const cactusMaterial = new THREE.MeshStandardMaterial({
|
1082 |
+
color: 0x2F4F2F,
|
1083 |
+
roughness: 0.8
|
1084 |
+
});
|
1085 |
+
|
1086 |
+
for (let i = 0; i < 50; i++) {
|
1087 |
+
const cactus = new THREE.Mesh(cactusGeometry, cactusMaterial);
|
1088 |
+
cactus.position.set(
|
1089 |
+
(Math.random() - 0.5) * MAP_SIZE * 0.8,
|
1090 |
+
2,
|
1091 |
+
(Math.random() - 0.5) * MAP_SIZE * 0.8
|
1092 |
+
);
|
1093 |
+
cactus.castShadow = true;
|
1094 |
+
cactus.receiveShadow = true;
|
1095 |
+
this.scene.add(cactus);
|
1096 |
+
}
|
1097 |
+
}
|
|
|
|
|
|
|
|
|
1098 |
|
1099 |
getHeightAtPosition(x, z) {
|
1100 |
if (!this.ground) return 0;
|
|
|
1517 |
const beatSounds = ['sounds/beat1.ogg', 'sounds/beat2.ogg', 'sounds/beat3.ogg'];
|
1518 |
|
1519 |
const tankPosition = this.tank.getPosition();
|
1520 |
+
// ์ ์ด์๊ณผ ํ๋ ์ด์ด ํฑํฌ ์ถฉ๋ ์ฒดํฌ
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1521 |
this.enemies.forEach(enemy => {
|
1522 |
if (!enemy.mesh || !enemy.isLoaded) return;
|
1523 |
|
1524 |
enemy.bullets.forEach(bullet => {
|
1525 |
+
const distance = bullet.position.distanceTo(tankPosition);
|
1526 |
+
if (distance < 1) {
|
1527 |
+
// ํ๋ ์ด์ด ํผ๊ฒฉ ์ฌ์ด๋ ์ฌ์
|
1528 |
+
const randomBeatSound = beatSounds[Math.floor(Math.random() * beatSounds.length)];
|
1529 |
+
const beatAudio = new Audio(randomBeatSound);
|
1530 |
+
beatAudio.play();
|
1531 |
+
|
1532 |
+
if (this.tank.takeDamage(250)) { // ๋ฐ๋ฏธ์ง๋ฅผ 250์ผ๋ก ์์
|
1533 |
+
this.endGame();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1534 |
}
|
1535 |
+
this.scene.remove(bullet);
|
1536 |
+
enemy.bullets = enemy.bullets.filter(b => b !== bullet);
|
1537 |
+
|
1538 |
+
this.createExplosion(bullet.position);
|
1539 |
+
document.getElementById('health').style.width =
|
1540 |
+
`${(this.tank.health / MAX_HEALTH) * 100}%`;
|
1541 |
}
|
1542 |
});
|
1543 |
});
|
1544 |
|
1545 |
+
// ํ๋ ์ด์ด ์ด์๊ณผ ์ ์ถฉ๋ ์ฒดํฌ
|
1546 |
this.tank.bullets.forEach((bullet, bulletIndex) => {
|
1547 |
+
this.enemies.forEach((enemy, enemyIndex) => {
|
1548 |
+
if (!enemy.mesh || !enemy.isLoaded) return;
|
1549 |
|
1550 |
+
const distance = bullet.position.distanceTo(enemy.mesh.position);
|
1551 |
+
if (distance < 2) {
|
1552 |
+
// ์ ํผ๊ฒฉ ์ฌ์ด๋ ์ฌ์
|
1553 |
+
const randomHitSound = hitSounds[Math.floor(Math.random() * hitSounds.length)];
|
1554 |
+
const hitAudio = new Audio(randomHitSound);
|
1555 |
+
hitAudio.play();
|
1556 |
+
|
1557 |
+
if (enemy.takeDamage(50)) {
|
1558 |
+
enemy.destroy();
|
1559 |
+
this.enemies.splice(enemyIndex, 1);
|
1560 |
+
this.score += 100;
|
1561 |
+
document.getElementById('score').textContent = `Score: ${this.score}`;
|
1562 |
+
}
|
1563 |
this.scene.remove(bullet);
|
1564 |
this.tank.bullets.splice(bulletIndex, 1);
|
1565 |
+
this.createExplosion(bullet.position);
|
|
|
1566 |
}
|
1567 |
+
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1568 |
});
|
1569 |
|
1570 |
+
// ํ๋ ์ด์ด ํฑํฌ์ ์ ์ ์ฐจ ์ถฉ๋ ์ฒดํฌ
|
1571 |
+
const tankBoundingBox = new THREE.Box3().setFromObject(this.tank.body);
|
1572 |
this.enemies.forEach(enemy => {
|
1573 |
if (!enemy.mesh || !enemy.isLoaded) return;
|
1574 |
|
1575 |
+
const enemyBoundingBox = new THREE.Box3().setFromObject(enemy.mesh);
|
1576 |
+
if (tankBoundingBox.intersectsBox(enemyBoundingBox)) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1577 |
this.tank.body.position.copy(this.previousTankPosition);
|
1578 |
}
|
|
|
|
|
|
|
1579 |
});
|
1580 |
|
1581 |
// ์ด์ ์์น ์ ์ฅ
|
1582 |
this.previousTankPosition.copy(this.tank.body.position);
|
1583 |
}
|
1584 |
+
endGame() {
|
1585 |
+
if (this.isGameOver) return;
|
1586 |
+
|
1587 |
+
this.isGameOver = true;
|
1588 |
+
|
1589 |
+
// ์ฌ๋ง ์ฌ์ด๋ ์ฌ์
|
1590 |
+
const deathSounds = ['sounds/death1.ogg', 'sounds/death2.ogg'];
|
1591 |
+
const randomDeathSound = deathSounds[Math.floor(Math.random() * deathSounds.length)];
|
1592 |
+
const deathAudio = new Audio(randomDeathSound);
|
1593 |
+
deathAudio.play();
|
1594 |
+
|
1595 |
+
if (this.gameTimer) {
|
1596 |
+
clearInterval(this.gameTimer);
|
1597 |
+
}
|
1598 |
+
|
1599 |
+
if (this.animationFrameId) {
|
1600 |
+
cancelAnimationFrame(this.animationFrameId);
|
1601 |
+
}
|
1602 |
+
|
1603 |
+
document.exitPointerLock();
|
1604 |
+
|
1605 |
+
const gameOverDiv = document.createElement('div');
|
1606 |
+
gameOverDiv.style.position = 'absolute';
|
1607 |
+
gameOverDiv.style.top = '50%';
|
1608 |
+
gameOverDiv.style.left = '50%';
|
1609 |
+
gameOverDiv.style.transform = 'translate(-50%, -50%)';
|
1610 |
+
gameOverDiv.style.color = '#0f0';
|
1611 |
+
gameOverDiv.style.fontSize = '48px';
|
1612 |
+
gameOverDiv.style.backgroundColor = 'rgba(0, 20, 0, 0.7)';
|
1613 |
+
gameOverDiv.style.padding = '20px';
|
1614 |
+
gameOverDiv.style.borderRadius = '10px';
|
1615 |
+
gameOverDiv.style.textAlign = 'center';
|
1616 |
+
gameOverDiv.innerHTML = `
|
1617 |
+
Game Over<br>
|
1618 |
+
Score: ${this.score}<br>
|
1619 |
+
Time Survived: ${GAME_DURATION - this.gameTime}s<br>
|
1620 |
+
<button onclick="location.reload()"
|
1621 |
+
style="font-size: 24px; padding: 10px; margin-top: 20px;
|
1622 |
+
cursor: pointer; background: #0f0; border: none;
|
1623 |
+
color: black; border-radius: 5px;">
|
1624 |
+
Play Again
|
1625 |
+
</button>
|
1626 |
+
`;
|
1627 |
+
document.body.appendChild(gameOverDiv);
|
1628 |
+
}
|
1629 |
+
|
1630 |
+
updateUI() {
|
1631 |
+
if (!this.isGameOver) {
|
1632 |
+
const healthBar = document.getElementById('health');
|
1633 |
+
if (healthBar) {
|
1634 |
+
healthBar.style.width = `${(this.tank.health / MAX_HEALTH) * 100}%`;
|
1635 |
+
}
|
1636 |
+
|
1637 |
+
const timeElement = document.getElementById('time');
|
1638 |
+
if (timeElement) {
|
1639 |
+
timeElement.textContent = `Time: ${this.gameTime}s`;
|
1640 |
+
}
|
1641 |
+
|
1642 |
+
const scoreElement = document.getElementById('score');
|
1643 |
+
if (scoreElement) {
|
1644 |
+
scoreElement.textContent = `Score: ${this.score}`;
|
1645 |
+
}
|
1646 |
+
}
|
1647 |
+
}
|
1648 |
+
|
1649 |
+
animate() {
|
1650 |
+
if (this.isGameOver) {
|
1651 |
+
if (this.animationFrameId) {
|
1652 |
+
cancelAnimationFrame(this.animationFrameId);
|
1653 |
+
}
|
1654 |
+
return;
|
1655 |
+
}
|
1656 |
+
|
1657 |
+
this.animationFrameId = requestAnimationFrame(() => this.animate());
|
1658 |
+
|
1659 |
+
const currentTime = performance.now();
|
1660 |
+
const deltaTime = (currentTime - this.lastTime) / 1000;
|
1661 |
+
this.lastTime = currentTime;
|
1662 |
+
|
1663 |
+
if (!this.isLoading) {
|
1664 |
+
this.handleMovement();
|
1665 |
+
this.tank.update(this.mouse.x, this.mouse.y, this.scene);
|
1666 |
+
|
1667 |
+
const tankPosition = this.tank.getPosition();
|
1668 |
+
this.enemies.forEach(enemy => {
|
1669 |
+
enemy.update(tankPosition);
|
1670 |
+
|
1671 |
+
if (enemy.isLoaded && enemy.mesh.position.distanceTo(tankPosition) < ENEMY_CONFIG.ATTACK_RANGE) {
|
1672 |
+
enemy.shoot(tankPosition);
|
1673 |
+
}
|
1674 |
+
});
|
1675 |
+
|
1676 |
+
this.updateParticles();
|
1677 |
+
this.checkCollisions();
|
1678 |
+
this.updateUI();
|
1679 |
+
this.updateRadar(); // ๋ ์ด๋ ์
๋ฐ์ดํธ ์ถ๊ฐ
|
1680 |
+
}
|
1681 |
+
|
1682 |
+
this.renderer.render(this.scene, this.camera);
|
1683 |
+
}
|
1684 |
+
}
|
1685 |
|
1686 |
// Start game
|
1687 |
window.startGame = function() {
|