Spaces:
Running
Running
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() {
|