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 |
// ๋ค์ธต ๊ตฌ๋ฆ ์์คํ
|