Spaces:
Running
Running
Update game.js
Browse files
game.js
CHANGED
|
@@ -1195,46 +1195,32 @@ class Game {
|
|
| 1195 |
}
|
| 1196 |
|
| 1197 |
setupScene() {
|
| 1198 |
-
//
|
| 1199 |
-
this.
|
| 1200 |
-
this.scene.fog = new THREE.Fog(
|
| 1201 |
|
| 1202 |
-
//
|
| 1203 |
-
const ambientLight = new THREE.AmbientLight(0xffffff, 0.
|
| 1204 |
this.scene.add(ambientLight);
|
| 1205 |
|
| 1206 |
-
const directionalLight = new THREE.DirectionalLight(
|
| 1207 |
-
directionalLight.position.set(
|
| 1208 |
directionalLight.castShadow = true;
|
| 1209 |
-
directionalLight.shadow.mapSize.width =
|
| 1210 |
-
directionalLight.shadow.mapSize.height =
|
| 1211 |
directionalLight.shadow.camera.near = 0.5;
|
| 1212 |
-
directionalLight.shadow.camera.far =
|
| 1213 |
-
directionalLight.shadow.camera.left = -
|
| 1214 |
-
directionalLight.shadow.camera.right =
|
| 1215 |
-
directionalLight.shadow.camera.top =
|
| 1216 |
-
directionalLight.shadow.camera.bottom = -
|
| 1217 |
this.scene.add(directionalLight);
|
| 1218 |
|
| 1219 |
-
//
|
| 1220 |
-
|
| 1221 |
-
fillLight.position.set(-5000, 8000, -5000);
|
| 1222 |
-
this.scene.add(fillLight);
|
| 1223 |
|
| 1224 |
-
//
|
| 1225 |
-
|
| 1226 |
-
const groundMaterial = new THREE.MeshLambertMaterial({
|
| 1227 |
-
color: 0x4a5d23,
|
| 1228 |
-
transparent: true,
|
| 1229 |
-
opacity: 0.9
|
| 1230 |
-
});
|
| 1231 |
-
const ground = new THREE.Mesh(groundGeometry, groundMaterial);
|
| 1232 |
-
ground.rotation.x = -Math.PI / 2;
|
| 1233 |
-
ground.receiveShadow = true;
|
| 1234 |
-
this.scene.add(ground);
|
| 1235 |
-
|
| 1236 |
-
// field.glb ๋ชจ๋ธ๋ก ์งํ ์ฑ์ฐ๊ธฐ
|
| 1237 |
-
this.loadFieldTerrain();
|
| 1238 |
|
| 1239 |
// ๊ฐ์ ๋ ๊ตฌ๋ฆ ์ถ๊ฐ
|
| 1240 |
this.addClouds();
|
|
@@ -1243,102 +1229,9 @@ class Game {
|
|
| 1243 |
this.addAtmosphericEffects();
|
| 1244 |
}
|
| 1245 |
|
| 1246 |
-
createSkybox() {
|
| 1247 |
-
// ๊ทธ๋ผ๋ฐ์ด์
ํ๋ ์์ฑ
|
| 1248 |
-
const skyGeo = new THREE.SphereGeometry(40000, 32, 32);
|
| 1249 |
-
const skyMat = new THREE.ShaderMaterial({
|
| 1250 |
-
uniforms: {
|
| 1251 |
-
topColor: { value: new THREE.Color(0x0077ff) },
|
| 1252 |
-
bottomColor: { value: new THREE.Color(0xffffff) },
|
| 1253 |
-
offset: { value: 33 },
|
| 1254 |
-
exponent: { value: 0.6 }
|
| 1255 |
-
},
|
| 1256 |
-
vertexShader: `
|
| 1257 |
-
varying vec3 vWorldPosition;
|
| 1258 |
-
void main() {
|
| 1259 |
-
vec4 worldPosition = modelMatrix * vec4(position, 1.0);
|
| 1260 |
-
vWorldPosition = worldPosition.xyz;
|
| 1261 |
-
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
| 1262 |
-
}
|
| 1263 |
-
`,
|
| 1264 |
-
fragmentShader: `
|
| 1265 |
-
uniform vec3 topColor;
|
| 1266 |
-
uniform vec3 bottomColor;
|
| 1267 |
-
uniform float offset;
|
| 1268 |
-
uniform float exponent;
|
| 1269 |
-
varying vec3 vWorldPosition;
|
| 1270 |
-
void main() {
|
| 1271 |
-
float h = normalize(vWorldPosition + offset).y;
|
| 1272 |
-
gl_FragColor = vec4(mix(bottomColor, topColor, max(pow(max(h, 0.0), exponent), 0.0)), 1.0);
|
| 1273 |
-
}
|
| 1274 |
-
`,
|
| 1275 |
-
side: THREE.BackSide
|
| 1276 |
-
});
|
| 1277 |
-
|
| 1278 |
-
const sky = new THREE.Mesh(skyGeo, skyMat);
|
| 1279 |
-
this.scene.add(sky);
|
| 1280 |
-
|
| 1281 |
-
// ํ์ ์ถ๊ฐ
|
| 1282 |
-
const sunGeometry = new THREE.SphereGeometry(800, 32, 32);
|
| 1283 |
-
const sunMaterial = new THREE.MeshBasicMaterial({
|
| 1284 |
-
color: 0xffffaa,
|
| 1285 |
-
emissive: 0xffffaa,
|
| 1286 |
-
emissiveIntensity: 2
|
| 1287 |
-
});
|
| 1288 |
-
const sun = new THREE.Mesh(sunGeometry, sunMaterial);
|
| 1289 |
-
sun.position.set(10000, 15000, 10000);
|
| 1290 |
-
this.scene.add(sun);
|
| 1291 |
-
|
| 1292 |
-
// ํ์ ๊ด์ ํจ๊ณผ
|
| 1293 |
-
const sunlight = new THREE.PointLight(0xffffaa, 0.5, 30000);
|
| 1294 |
-
sunlight.position.copy(sun.position);
|
| 1295 |
-
this.scene.add(sunlight);
|
| 1296 |
-
}
|
| 1297 |
-
|
| 1298 |
-
async loadFieldTerrain() {
|
| 1299 |
-
try {
|
| 1300 |
-
// field.glb๋ฅผ ํ ๋ฒ๋ง ๋ก๋ํ์ฌ ์ ์ฒด ๋งต ํฌ๊ธฐ๋ก ์ค์ผ์ผ
|
| 1301 |
-
const result = await this.loader.loadAsync('models/field.glb');
|
| 1302 |
-
const fieldModel = result.scene;
|
| 1303 |
-
|
| 1304 |
-
// ๋ชจ๋ธ์ ์ค์ ํฌ๊ธฐ ๊ณ์ฐ
|
| 1305 |
-
const box = new THREE.Box3().setFromObject(fieldModel);
|
| 1306 |
-
const size = box.getSize(new THREE.Vector3());
|
| 1307 |
-
|
| 1308 |
-
// ์ ์ฒด ๋งต ํฌ๊ธฐ์ ๋ง๊ฒ ์ค์ผ์ผ ๊ณ์ฐ
|
| 1309 |
-
const scaleX = GAME_CONSTANTS.MAP_SIZE / size.x;
|
| 1310 |
-
const scaleZ = GAME_CONSTANTS.MAP_SIZE / size.z;
|
| 1311 |
-
const scale = Math.max(scaleX, scaleZ); // ๋งต์ ์์ ํ ์ฑ์ฐ๋๋ก ๋ ํฐ ์ค์ผ์ผ ์ฌ์ฉ
|
| 1312 |
-
|
| 1313 |
-
fieldModel.scale.set(scale, scale, scale);
|
| 1314 |
-
fieldModel.position.set(0, 0, 0);
|
| 1315 |
-
|
| 1316 |
-
// ๊ทธ๋ฆผ์ ์ค์
|
| 1317 |
-
fieldModel.traverse((child) => {
|
| 1318 |
-
if (child.isMesh) {
|
| 1319 |
-
child.receiveShadow = true;
|
| 1320 |
-
child.castShadow = true;
|
| 1321 |
-
|
| 1322 |
-
// ์งํ ๋จธํฐ๋ฆฌ์ผ ๊ฐ์
|
| 1323 |
-
if (child.material) {
|
| 1324 |
-
child.material.side = THREE.DoubleSide;
|
| 1325 |
-
}
|
| 1326 |
-
}
|
| 1327 |
-
});
|
| 1328 |
-
|
| 1329 |
-
this.scene.add(fieldModel);
|
| 1330 |
-
console.log(`Field terrain loaded and scaled to ${scale}x`);
|
| 1331 |
-
|
| 1332 |
-
} catch (error) {
|
| 1333 |
-
console.error('Field terrain loading failed:', error);
|
| 1334 |
-
// ํด๋ฐฑ: ํ๋ก์์ ๋ด ์งํ ์์ฑ
|
| 1335 |
-
this.createProceduralTerrain();
|
| 1336 |
-
}
|
| 1337 |
-
}
|
| 1338 |
-
|
| 1339 |
createProceduralTerrain() {
|
| 1340 |
-
//
|
| 1341 |
-
const segments =
|
| 1342 |
const terrainGeometry = new THREE.PlaneGeometry(
|
| 1343 |
GAME_CONSTANTS.MAP_SIZE,
|
| 1344 |
GAME_CONSTANTS.MAP_SIZE,
|
|
@@ -1348,32 +1241,319 @@ class Game {
|
|
| 1348 |
|
| 1349 |
// ๋ฒํ
์ค์ ๋
ธ์ด์ฆ ์ถ๊ฐํ์ฌ ์ธ๋ ํจ๊ณผ
|
| 1350 |
const vertices = terrainGeometry.attributes.position.array;
|
|
|
|
|
|
|
| 1351 |
for (let i = 0; i < vertices.length; i += 3) {
|
| 1352 |
const x = vertices[i];
|
| 1353 |
const y = vertices[i + 1];
|
| 1354 |
-
|
| 1355 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1356 |
}
|
| 1357 |
|
| 1358 |
terrainGeometry.computeVertexNormals();
|
| 1359 |
|
|
|
|
| 1360 |
const terrainMaterial = new THREE.MeshLambertMaterial({
|
| 1361 |
-
|
| 1362 |
-
flatShading:
|
| 1363 |
});
|
| 1364 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1365 |
const terrain = new THREE.Mesh(terrainGeometry, terrainMaterial);
|
| 1366 |
terrain.rotation.x = -Math.PI / 2;
|
| 1367 |
terrain.receiveShadow = true;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1368 |
this.scene.add(terrain);
|
| 1369 |
}
|
| 1370 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1371 |
addAtmosphericEffects() {
|
| 1372 |
// ๊ฐ์ ๋ ์ฐ ์งํ ์์คํ
|
| 1373 |
this.createMountainRanges();
|
| 1374 |
-
|
| 1375 |
-
// ๋๊ธฐ ์ฐ๋ ํจ๊ณผ
|
| 1376 |
-
this.addAtmosphericScattering();
|
| 1377 |
}
|
| 1378 |
|
| 1379 |
createMountainRanges() {
|
|
@@ -1491,7 +1671,7 @@ class Game {
|
|
| 1491 |
// ์ฝ๊ฐ์ ๋๋ค ํ์
|
| 1492 |
mountainGroup.rotation.y = Math.random() * Math.PI;
|
| 1493 |
|
| 1494 |
-
//
|
| 1495 |
mountainGroup.layers.set(0);
|
| 1496 |
|
| 1497 |
this.scene.add(mountainGroup);
|
|
@@ -1555,25 +1735,6 @@ class Game {
|
|
| 1555 |
this.scene.add(silhouette);
|
| 1556 |
}
|
| 1557 |
}
|
| 1558 |
-
|
| 1559 |
-
addAtmosphericScattering() {
|
| 1560 |
-
// ๋๊ธฐ ์ฐ๋ ํจ๊ณผ๋ฅผ ์ํ ๋ฐํฌ๋ช
๊ตฌ์ฒด๋ค
|
| 1561 |
-
const scatteringLayers = 5;
|
| 1562 |
-
for (let i = 0; i < scatteringLayers; i++) {
|
| 1563 |
-
const radius = 20000 + i * 5000;
|
| 1564 |
-
const geometry = new THREE.SphereGeometry(radius, 32, 32);
|
| 1565 |
-
const material = new THREE.MeshBasicMaterial({
|
| 1566 |
-
color: 0x87CEEB,
|
| 1567 |
-
transparent: true,
|
| 1568 |
-
opacity: 0.02,
|
| 1569 |
-
side: THREE.BackSide,
|
| 1570 |
-
depthWrite: false
|
| 1571 |
-
});
|
| 1572 |
-
|
| 1573 |
-
const atmosphere = new THREE.Mesh(geometry, material);
|
| 1574 |
-
this.scene.add(atmosphere);
|
| 1575 |
-
}
|
| 1576 |
-
}
|
| 1577 |
|
| 1578 |
addClouds() {
|
| 1579 |
// ๋ค์ธต ๊ตฌ๋ฆ ์์คํ
|
|
|
|
| 1195 |
}
|
| 1196 |
|
| 1197 |
setupScene() {
|
| 1198 |
+
// ๊ธฐ๋ณธ ํ๋ ์ค์
|
| 1199 |
+
this.scene.background = new THREE.Color(0x87CEEB);
|
| 1200 |
+
this.scene.fog = new THREE.Fog(0x87CEEB, 1000, 30000);
|
| 1201 |
|
| 1202 |
+
// ๊ธฐ๋ณธ ์กฐ๋ช
์ค์
|
| 1203 |
+
const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
|
| 1204 |
this.scene.add(ambientLight);
|
| 1205 |
|
| 1206 |
+
const directionalLight = new THREE.DirectionalLight(0xffffff, 1.0);
|
| 1207 |
+
directionalLight.position.set(5000, 8000, 5000);
|
| 1208 |
directionalLight.castShadow = true;
|
| 1209 |
+
directionalLight.shadow.mapSize.width = 2048;
|
| 1210 |
+
directionalLight.shadow.mapSize.height = 2048;
|
| 1211 |
directionalLight.shadow.camera.near = 0.5;
|
| 1212 |
+
directionalLight.shadow.camera.far = 15000;
|
| 1213 |
+
directionalLight.shadow.camera.left = -8000;
|
| 1214 |
+
directionalLight.shadow.camera.right = 8000;
|
| 1215 |
+
directionalLight.shadow.camera.top = 8000;
|
| 1216 |
+
directionalLight.shadow.camera.bottom = -8000;
|
| 1217 |
this.scene.add(directionalLight);
|
| 1218 |
|
| 1219 |
+
// ํ๋ก์์ ๋ด ์งํ ์์ฑ
|
| 1220 |
+
this.createProceduralTerrain();
|
|
|
|
|
|
|
| 1221 |
|
| 1222 |
+
// ์ด๋ชฉ ๋ฐฐ์น
|
| 1223 |
+
this.addVegetation();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1224 |
|
| 1225 |
// ๊ฐ์ ๋ ๊ตฌ๋ฆ ์ถ๊ฐ
|
| 1226 |
this.addClouds();
|
|
|
|
| 1229 |
this.addAtmosphericEffects();
|
| 1230 |
}
|
| 1231 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1232 |
createProceduralTerrain() {
|
| 1233 |
+
// ์งํ ์์ฑ์ ์ํ ์ธ๊ทธ๋จผํธ
|
| 1234 |
+
const segments = 100;
|
| 1235 |
const terrainGeometry = new THREE.PlaneGeometry(
|
| 1236 |
GAME_CONSTANTS.MAP_SIZE,
|
| 1237 |
GAME_CONSTANTS.MAP_SIZE,
|
|
|
|
| 1241 |
|
| 1242 |
// ๋ฒํ
์ค์ ๋
ธ์ด์ฆ ์ถ๊ฐํ์ฌ ์ธ๋ ํจ๊ณผ
|
| 1243 |
const vertices = terrainGeometry.attributes.position.array;
|
| 1244 |
+
const heightMap = [];
|
| 1245 |
+
|
| 1246 |
for (let i = 0; i < vertices.length; i += 3) {
|
| 1247 |
const x = vertices[i];
|
| 1248 |
const y = vertices[i + 1];
|
| 1249 |
+
|
| 1250 |
+
// ๋ค์ค ์ค์ผ์ผ ๋
ธ์ด์ฆ๋ก ์์ฐ์ค๋ฌ์ด ์งํ ์์ฑ
|
| 1251 |
+
let height = 0;
|
| 1252 |
+
|
| 1253 |
+
// ํฐ ์ค์ผ์ผ ์ธ๋ (์ฃผ์ ์งํ)
|
| 1254 |
+
height += Math.sin(x * 0.0001) * Math.cos(y * 0.0001) * 300;
|
| 1255 |
+
|
| 1256 |
+
// ์ค๊ฐ ์ค์ผ์ผ ์ธ๋
|
| 1257 |
+
height += Math.sin(x * 0.0003) * Math.cos(y * 0.0003) * 150;
|
| 1258 |
+
|
| 1259 |
+
// ์์ ์ค์ผ์ผ ๋ํ
์ผ
|
| 1260 |
+
height += Math.sin(x * 0.001) * Math.cos(y * 0.001) * 50;
|
| 1261 |
+
|
| 1262 |
+
// ๋๋ค ๋
ธ์ด์ฆ
|
| 1263 |
+
height += (Math.random() - 0.5) * 20;
|
| 1264 |
+
|
| 1265 |
+
// ๋งต ๊ฐ์ฅ์๋ฆฌ๋ก ๊ฐ์๋ก ๋์ด ๊ฐ์ (๊ฒฝ๊ณ ๋ถ๋๋ฝ๊ฒ)
|
| 1266 |
+
const distanceFromCenter = Math.sqrt(x * x + y * y);
|
| 1267 |
+
const maxDistance = GAME_CONSTANTS.MAP_SIZE * 0.4;
|
| 1268 |
+
if (distanceFromCenter > maxDistance) {
|
| 1269 |
+
const fade = 1 - (distanceFromCenter - maxDistance) / (GAME_CONSTANTS.MAP_SIZE * 0.1);
|
| 1270 |
+
height *= Math.max(0, fade);
|
| 1271 |
+
}
|
| 1272 |
+
|
| 1273 |
+
vertices[i + 2] = height;
|
| 1274 |
+
heightMap.push(height);
|
| 1275 |
}
|
| 1276 |
|
| 1277 |
terrainGeometry.computeVertexNormals();
|
| 1278 |
|
| 1279 |
+
// ์งํ ๋จธํฐ๋ฆฌ์ผ - ๋์ด์ ๋ฐ๋ฅธ ์์ ๋ณํ
|
| 1280 |
const terrainMaterial = new THREE.MeshLambertMaterial({
|
| 1281 |
+
vertexColors: true,
|
| 1282 |
+
flatShading: false
|
| 1283 |
});
|
| 1284 |
|
| 1285 |
+
// ๋์ด์ ๋ฐ๋ฅธ ์์ ์ค์
|
| 1286 |
+
const colors = [];
|
| 1287 |
+
for (let i = 0; i < heightMap.length; i++) {
|
| 1288 |
+
const height = heightMap[i];
|
| 1289 |
+
let color;
|
| 1290 |
+
|
| 1291 |
+
if (height < 50) {
|
| 1292 |
+
// ๋ฎ์ ์ง์ญ - ์งํ ์ด๋ก
|
| 1293 |
+
color = new THREE.Color(0x2d4a2b);
|
| 1294 |
+
} else if (height < 150) {
|
| 1295 |
+
// ์ค๊ฐ ์ง์ญ - ์ผ๋ฐ ์ด๋ก
|
| 1296 |
+
color = new THREE.Color(0x3a5f3a);
|
| 1297 |
+
} else if (height < 250) {
|
| 1298 |
+
// ๋์ ์ง์ญ - ๋ฐ์ ์ด๋ก/๊ฐ์
|
| 1299 |
+
color = new THREE.Color(0x4a6741);
|
| 1300 |
+
} else {
|
| 1301 |
+
// ๋งค์ฐ ๋์ ์ง์ญ - ๊ฐ์/ํ์
|
| 1302 |
+
color = new THREE.Color(0x5a5a4a);
|
| 1303 |
+
}
|
| 1304 |
+
|
| 1305 |
+
// ์ฝ๊ฐ์ ๋ณํ ์ถ๊ฐ
|
| 1306 |
+
const variation = 0.1;
|
| 1307 |
+
color.r += (Math.random() - 0.5) * variation;
|
| 1308 |
+
color.g += (Math.random() - 0.5) * variation;
|
| 1309 |
+
color.b += (Math.random() - 0.5) * variation;
|
| 1310 |
+
|
| 1311 |
+
colors.push(color.r, color.g, color.b);
|
| 1312 |
+
}
|
| 1313 |
+
|
| 1314 |
+
terrainGeometry.setAttribute('color', new THREE.Float32BufferAttribute(colors, 3));
|
| 1315 |
+
|
| 1316 |
const terrain = new THREE.Mesh(terrainGeometry, terrainMaterial);
|
| 1317 |
terrain.rotation.x = -Math.PI / 2;
|
| 1318 |
terrain.receiveShadow = true;
|
| 1319 |
+
|
| 1320 |
+
// ์งํ ์ ๋ณด ์ ์ฅ (์ด๋ชฉ ๋ฐฐ์น์ฉ)
|
| 1321 |
+
this.terrain = terrain;
|
| 1322 |
+
this.terrainGeometry = terrainGeometry;
|
| 1323 |
+
this.heightMap = heightMap;
|
| 1324 |
+
|
| 1325 |
this.scene.add(terrain);
|
| 1326 |
}
|
| 1327 |
|
| 1328 |
+
addVegetation() {
|
| 1329 |
+
if (!this.terrain || !this.heightMap) return;
|
| 1330 |
+
|
| 1331 |
+
// ๋๋ฌด ์ข
๋ฅ๋ณ ์ค์
|
| 1332 |
+
const treeTypes = [
|
| 1333 |
+
{
|
| 1334 |
+
name: 'pine',
|
| 1335 |
+
count: 500,
|
| 1336 |
+
minHeight: 50,
|
| 1337 |
+
maxHeight: 200,
|
| 1338 |
+
trunkColor: 0x4a3c28,
|
| 1339 |
+
leavesColor: 0x1a4d1a,
|
| 1340 |
+
trunkHeight: 20,
|
| 1341 |
+
trunkRadius: 1.5,
|
| 1342 |
+
leavesRadius: 8,
|
| 1343 |
+
leavesHeight: 25
|
| 1344 |
+
},
|
| 1345 |
+
{
|
| 1346 |
+
name: 'oak',
|
| 1347 |
+
count: 300,
|
| 1348 |
+
minHeight: 0,
|
| 1349 |
+
maxHeight: 150,
|
| 1350 |
+
trunkColor: 0x5c4033,
|
| 1351 |
+
leavesColor: 0x2d5016,
|
| 1352 |
+
trunkHeight: 15,
|
| 1353 |
+
trunkRadius: 2,
|
| 1354 |
+
leavesRadius: 12,
|
| 1355 |
+
leavesHeight: 20
|
| 1356 |
+
},
|
| 1357 |
+
{
|
| 1358 |
+
name: 'bush',
|
| 1359 |
+
count: 800,
|
| 1360 |
+
minHeight: 0,
|
| 1361 |
+
maxHeight: 100,
|
| 1362 |
+
trunkColor: 0x3a2f1a,
|
| 1363 |
+
leavesColor: 0x3a5f3a,
|
| 1364 |
+
trunkHeight: 3,
|
| 1365 |
+
trunkRadius: 0.5,
|
| 1366 |
+
leavesRadius: 5,
|
| 1367 |
+
leavesHeight: 6
|
| 1368 |
+
}
|
| 1369 |
+
];
|
| 1370 |
+
|
| 1371 |
+
// ๊ฐ ๋๋ฌด ํ์
๋ณ๋ก ๋ฐฐ์น
|
| 1372 |
+
treeTypes.forEach(treeType => {
|
| 1373 |
+
for (let i = 0; i < treeType.count; i++) {
|
| 1374 |
+
// ๋๋ค ์์น ์ ํ
|
| 1375 |
+
const x = (Math.random() - 0.5) * GAME_CONSTANTS.MAP_SIZE * 0.8;
|
| 1376 |
+
const z = (Math.random() - 0.5) * GAME_CONSTANTS.MAP_SIZE * 0.8;
|
| 1377 |
+
|
| 1378 |
+
// ํด๋น ์์น์ ์งํ ๋์ด ๊ณ์ฐ
|
| 1379 |
+
const terrainHeight = this.getTerrainHeightAt(x, z);
|
| 1380 |
+
|
| 1381 |
+
// ๋์ด ์ ํ ํ์ธ
|
| 1382 |
+
if (terrainHeight < treeType.minHeight || terrainHeight > treeType.maxHeight) {
|
| 1383 |
+
continue;
|
| 1384 |
+
}
|
| 1385 |
+
|
| 1386 |
+
// ๋๋ฌด ๊ทธ๋ฃน ์์ฑ
|
| 1387 |
+
const tree = new THREE.Group();
|
| 1388 |
+
|
| 1389 |
+
// ๋ชธํต
|
| 1390 |
+
const trunkGeometry = new THREE.CylinderGeometry(
|
| 1391 |
+
treeType.trunkRadius * 0.8,
|
| 1392 |
+
treeType.trunkRadius,
|
| 1393 |
+
treeType.trunkHeight,
|
| 1394 |
+
8
|
| 1395 |
+
);
|
| 1396 |
+
const trunkMaterial = new THREE.MeshLambertMaterial({
|
| 1397 |
+
color: treeType.trunkColor
|
| 1398 |
+
});
|
| 1399 |
+
const trunk = new THREE.Mesh(trunkGeometry, trunkMaterial);
|
| 1400 |
+
trunk.position.y = treeType.trunkHeight / 2;
|
| 1401 |
+
trunk.castShadow = true;
|
| 1402 |
+
trunk.receiveShadow = true;
|
| 1403 |
+
tree.add(trunk);
|
| 1404 |
+
|
| 1405 |
+
// ์ (๊ตฌํ ๋๋ ์๋ฟํ)
|
| 1406 |
+
let leavesGeometry;
|
| 1407 |
+
if (treeType.name === 'pine') {
|
| 1408 |
+
// ์๋๋ฌด๋ ์๋ฟํ
|
| 1409 |
+
leavesGeometry = new THREE.ConeGeometry(
|
| 1410 |
+
treeType.leavesRadius,
|
| 1411 |
+
treeType.leavesHeight,
|
| 1412 |
+
8
|
| 1413 |
+
);
|
| 1414 |
+
} else {
|
| 1415 |
+
// ๋ค๋ฅธ ๋๋ฌด๋ ๊ตฌํ
|
| 1416 |
+
leavesGeometry = new THREE.SphereGeometry(
|
| 1417 |
+
treeType.leavesRadius,
|
| 1418 |
+
8,
|
| 1419 |
+
6
|
| 1420 |
+
);
|
| 1421 |
+
}
|
| 1422 |
+
|
| 1423 |
+
const leavesMaterial = new THREE.MeshLambertMaterial({
|
| 1424 |
+
color: treeType.leavesColor
|
| 1425 |
+
});
|
| 1426 |
+
const leaves = new THREE.Mesh(leavesGeometry, leavesMaterial);
|
| 1427 |
+
leaves.position.y = treeType.trunkHeight + treeType.leavesHeight / 2;
|
| 1428 |
+
leaves.castShadow = true;
|
| 1429 |
+
leaves.receiveShadow = true;
|
| 1430 |
+
tree.add(leaves);
|
| 1431 |
+
|
| 1432 |
+
// ๋๋ฌด ์์น ๋ฐ ํฌ๊ธฐ ์ค์
|
| 1433 |
+
tree.position.set(x, terrainHeight, z);
|
| 1434 |
+
|
| 1435 |
+
// ํฌ๊ธฐ ๋ณํ
|
| 1436 |
+
const scale = 0.7 + Math.random() * 0.6;
|
| 1437 |
+
tree.scale.set(scale, scale, scale);
|
| 1438 |
+
|
| 1439 |
+
// ๋๋ค ํ์
|
| 1440 |
+
tree.rotation.y = Math.random() * Math.PI * 2;
|
| 1441 |
+
|
| 1442 |
+
this.scene.add(tree);
|
| 1443 |
+
}
|
| 1444 |
+
});
|
| 1445 |
+
|
| 1446 |
+
// ๋ฐ์ ์ถ๊ฐ
|
| 1447 |
+
this.addRocks();
|
| 1448 |
+
|
| 1449 |
+
// ํ ์ถ๊ฐ
|
| 1450 |
+
this.addGrass();
|
| 1451 |
+
}
|
| 1452 |
+
|
| 1453 |
+
addRocks() {
|
| 1454 |
+
const rockCount = 200;
|
| 1455 |
+
|
| 1456 |
+
for (let i = 0; i < rockCount; i++) {
|
| 1457 |
+
const x = (Math.random() - 0.5) * GAME_CONSTANTS.MAP_SIZE * 0.9;
|
| 1458 |
+
const z = (Math.random() - 0.5) * GAME_CONSTANTS.MAP_SIZE * 0.9;
|
| 1459 |
+
const terrainHeight = this.getTerrainHeightAt(x, z);
|
| 1460 |
+
|
| 1461 |
+
// ๋ฐ์ ์์ฑ
|
| 1462 |
+
const rockSize = 3 + Math.random() * 10;
|
| 1463 |
+
const rockGeometry = new THREE.DodecahedronGeometry(rockSize, 0);
|
| 1464 |
+
|
| 1465 |
+
// ๋ฐ์ ํํ ๋ณํ
|
| 1466 |
+
const vertices = rockGeometry.attributes.position.array;
|
| 1467 |
+
for (let j = 0; j < vertices.length; j += 3) {
|
| 1468 |
+
vertices[j] += (Math.random() - 0.5) * rockSize * 0.3;
|
| 1469 |
+
vertices[j + 1] += (Math.random() - 0.5) * rockSize * 0.3;
|
| 1470 |
+
vertices[j + 2] += (Math.random() - 0.5) * rockSize * 0.3;
|
| 1471 |
+
}
|
| 1472 |
+
rockGeometry.computeVertexNormals();
|
| 1473 |
+
|
| 1474 |
+
const rockMaterial = new THREE.MeshLambertMaterial({
|
| 1475 |
+
color: new THREE.Color(0.4 + Math.random() * 0.2, 0.4 + Math.random() * 0.2, 0.4 + Math.random() * 0.2),
|
| 1476 |
+
flatShading: true
|
| 1477 |
+
});
|
| 1478 |
+
|
| 1479 |
+
const rock = new THREE.Mesh(rockGeometry, rockMaterial);
|
| 1480 |
+
rock.position.set(x, terrainHeight + rockSize * 0.3, z);
|
| 1481 |
+
rock.rotation.set(
|
| 1482 |
+
Math.random() * Math.PI,
|
| 1483 |
+
Math.random() * Math.PI,
|
| 1484 |
+
Math.random() * Math.PI
|
| 1485 |
+
);
|
| 1486 |
+
rock.castShadow = true;
|
| 1487 |
+
rock.receiveShadow = true;
|
| 1488 |
+
|
| 1489 |
+
this.scene.add(rock);
|
| 1490 |
+
}
|
| 1491 |
+
}
|
| 1492 |
+
|
| 1493 |
+
addGrass() {
|
| 1494 |
+
// ํ ํจ์น ์์ฑ
|
| 1495 |
+
const grassPatchCount = 100;
|
| 1496 |
+
|
| 1497 |
+
for (let i = 0; i < grassPatchCount; i++) {
|
| 1498 |
+
const centerX = (Math.random() - 0.5) * GAME_CONSTANTS.MAP_SIZE * 0.8;
|
| 1499 |
+
const centerZ = (Math.random() - 0.5) * GAME_CONSTANTS.MAP_SIZE * 0.8;
|
| 1500 |
+
const terrainHeight = this.getTerrainHeightAt(centerX, centerZ);
|
| 1501 |
+
|
| 1502 |
+
// ๋ฎ์ ์ง์ญ์๋ง ํ ๋ฐฐ์น
|
| 1503 |
+
if (terrainHeight > 100) continue;
|
| 1504 |
+
|
| 1505 |
+
const grassGroup = new THREE.Group();
|
| 1506 |
+
const grassCountInPatch = 20 + Math.floor(Math.random() * 30);
|
| 1507 |
+
|
| 1508 |
+
for (let j = 0; j < grassCountInPatch; j++) {
|
| 1509 |
+
const offsetX = (Math.random() - 0.5) * 50;
|
| 1510 |
+
const offsetZ = (Math.random() - 0.5) * 50;
|
| 1511 |
+
|
| 1512 |
+
// ๊ฐ๋จํ ํ ๋ธ๋ ์ด๋
|
| 1513 |
+
const grassHeight = 2 + Math.random() * 3;
|
| 1514 |
+
const grassGeometry = new THREE.PlaneGeometry(0.5, grassHeight);
|
| 1515 |
+
grassGeometry.translate(0, grassHeight / 2, 0);
|
| 1516 |
+
|
| 1517 |
+
const grassMaterial = new THREE.MeshLambertMaterial({
|
| 1518 |
+
color: new THREE.Color(0.1 + Math.random() * 0.1, 0.4 + Math.random() * 0.2, 0.1),
|
| 1519 |
+
side: THREE.DoubleSide
|
| 1520 |
+
});
|
| 1521 |
+
|
| 1522 |
+
const grass = new THREE.Mesh(grassGeometry, grassMaterial);
|
| 1523 |
+
grass.position.set(offsetX, 0, offsetZ);
|
| 1524 |
+
grass.rotation.y = Math.random() * Math.PI;
|
| 1525 |
+
|
| 1526 |
+
grassGroup.add(grass);
|
| 1527 |
+
}
|
| 1528 |
+
|
| 1529 |
+
grassGroup.position.set(centerX, terrainHeight, centerZ);
|
| 1530 |
+
this.scene.add(grassGroup);
|
| 1531 |
+
}
|
| 1532 |
+
}
|
| 1533 |
+
|
| 1534 |
+
getTerrainHeightAt(x, z) {
|
| 1535 |
+
if (!this.terrainGeometry) return 0;
|
| 1536 |
+
|
| 1537 |
+
// ์งํ ์ขํ๋ฅผ ์ ๊ทํ
|
| 1538 |
+
const normalizedX = (x + GAME_CONSTANTS.MAP_SIZE / 2) / GAME_CONSTANTS.MAP_SIZE;
|
| 1539 |
+
const normalizedZ = (z + GAME_CONSTANTS.MAP_SIZE / 2) / GAME_CONSTANTS.MAP_SIZE;
|
| 1540 |
+
|
| 1541 |
+
// ๊ฐ์ฅ ๊ฐ๊น์ด ๋ฒํ
์ค ์ธ๋ฑ์ค ์ฐพ๊ธฐ
|
| 1542 |
+
const segments = Math.sqrt(this.terrainGeometry.attributes.position.count);
|
| 1543 |
+
const gridX = Math.floor(normalizedX * (segments - 1));
|
| 1544 |
+
const gridZ = Math.floor(normalizedZ * (segments - 1));
|
| 1545 |
+
|
| 1546 |
+
if (gridX < 0 || gridX >= segments - 1 || gridZ < 0 || gridZ >= segments - 1) {
|
| 1547 |
+
return 0;
|
| 1548 |
+
}
|
| 1549 |
+
|
| 1550 |
+
const index = gridZ * segments + gridX;
|
| 1551 |
+
return this.heightMap[index] || 0;
|
| 1552 |
+
}
|
| 1553 |
+
|
| 1554 |
addAtmosphericEffects() {
|
| 1555 |
// ๊ฐ์ ๋ ์ฐ ์งํ ์์คํ
|
| 1556 |
this.createMountainRanges();
|
|
|
|
|
|
|
|
|
|
| 1557 |
}
|
| 1558 |
|
| 1559 |
createMountainRanges() {
|
|
|
|
| 1671 |
// ์ฝ๊ฐ์ ๋๋ค ํ์
|
| 1672 |
mountainGroup.rotation.y = Math.random() * Math.PI;
|
| 1673 |
|
| 1674 |
+
// ๊ฑฐ๋ฆฌ๋ณ ์์: ๋ฉ๋ฆฌ ์๋ ์ฐ์ผ์๋ก ๋ ํธ๋ฅธ์์ผ๋ก ๋ณด์ด๋๋ก ์ค์
|
| 1675 |
mountainGroup.layers.set(0);
|
| 1676 |
|
| 1677 |
this.scene.add(mountainGroup);
|
|
|
|
| 1735 |
this.scene.add(silhouette);
|
| 1736 |
}
|
| 1737 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1738 |
|
| 1739 |
addClouds() {
|
| 1740 |
// ๋ค์ธต ๊ตฌ๋ฆ ์์คํ
|