adventure / static /js /three_map.js
oggata's picture
Update static/js/three_map.js
f6eab66 verified
// Three.jsを使用した3Dマップの実装
let scene, camera, renderer, buildings = {}, residents = {};
let controls;
function init() {
// シーンの作成
scene = new THREE.Scene();
scene.background = new THREE.Color(0x87CEEB); // 空色の背景
// カメラの設定
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(50, 50, 50);
camera.lookAt(0, 0, 0);
// レンダラーの設定
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true;
document.getElementById('three-container').appendChild(renderer.domElement);
// 光源の追加
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
directionalLight.position.set(50, 50, 50);
directionalLight.castShadow = true;
scene.add(directionalLight);
// 地面の作成
const groundGeometry = new THREE.PlaneGeometry(100, 100);
const groundMaterial = new THREE.MeshStandardMaterial({
color: 0x90EE90,
side: THREE.DoubleSide
});
const ground = new THREE.Mesh(groundGeometry, groundMaterial);
ground.rotation.x = -Math.PI / 2;
ground.receiveShadow = true;
scene.add(ground);
// カメラコントロールの追加
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
// アニメーションループの開始
animate();
// ウィンドウリサイズのハンドリング
window.addEventListener('resize', onWindowResize, false);
}
function createBuilding(name, position, type) {
let geometry, material, color;
// 建物の種類に応じて形状と色を設定
switch(type) {
case '住宅':
geometry = new THREE.BoxGeometry(8, 6, 8);
color = 0xFFB6C1; // ピンク系
break;
case '公園':
geometry = new THREE.CylinderGeometry(5, 5, 2, 32);
color = 0x228B22; // 森の緑
break;
case '図書館':
geometry = new THREE.BoxGeometry(12, 8, 12);
color = 0x8B4513; // 茶色
break;
case 'カフェ':
geometry = new THREE.BoxGeometry(10, 6, 10);
color = 0xDEB887; // ベージュ
break;
case '商店':
geometry = new THREE.BoxGeometry(10, 5, 10);
color = 0x4682B4; // スチールブルー
break;
default:
geometry = new THREE.BoxGeometry(10, 10, 10);
color = 0x808080;
}
material = new THREE.MeshStandardMaterial({
color: color,
roughness: 0.7,
metalness: 0.2
});
const building = new THREE.Mesh(geometry, material);
building.position.set(position.x, geometry.parameters.height / 2, position.z);
building.castShadow = true;
building.receiveShadow = true;
scene.add(building);
buildings[name] = building;
// 建物名のラベル
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
canvas.width = 256;
canvas.height = 64;
context.fillStyle = '#ffffff';
context.font = '24px Arial';
context.fillText(name, 10, 32);
const texture = new THREE.CanvasTexture(canvas);
const labelMaterial = new THREE.SpriteMaterial({ map: texture });
const label = new THREE.Sprite(labelMaterial);
label.position.set(position.x, geometry.parameters.height + 2, position.z);
scene.add(label);
}
function createResident(name, position, profession) {
let geometry, material, color;
// 職業に応じて色を設定
switch(profession) {
case '教師':
color = 0x0000FF; // 青
break;
case '看護師':
color = 0xFF0000; // 赤
break;
case 'エンジニア':
color = 0x00FF00; // 緑
break;
case 'デザイナー':
color = 0xFF00FF; // マゼンタ
break;
case '商店主':
color = 0xFFA500; // オレンジ
break;
default:
color = 0xFFFFFF; // 白
}
// 頭部
const headGeometry = new THREE.SphereGeometry(1, 32, 32);
const headMaterial = new THREE.MeshStandardMaterial({ color: 0xFFE4C4 });
const head = new THREE.Mesh(headGeometry, headMaterial);
head.position.y = 2;
head.castShadow = true;
// 胴体
const bodyGeometry = new THREE.CylinderGeometry(0.7, 0.7, 2, 32);
const bodyMaterial = new THREE.MeshStandardMaterial({ color: color });
const body = new THREE.Mesh(bodyGeometry, bodyMaterial);
body.position.y = 0.5;
body.castShadow = true;
// グループ化
const resident = new THREE.Group();
resident.add(head);
resident.add(body);
resident.position.set(position.x, 0, position.z);
scene.add(resident);
residents[name] = resident;
// 住民名のラベル
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
canvas.width = 256;
canvas.height = 64;
context.fillStyle = '#ffffff';
context.font = '24px Arial';
context.fillText(name, 10, 32);
const texture = new THREE.CanvasTexture(canvas);
const labelMaterial = new THREE.SpriteMaterial({ map: texture });
const label = new THREE.Sprite(labelMaterial);
label.position.set(position.x, 4, position.z);
scene.add(label);
}
function updatePositions(state) {
// 建物の位置を更新
state.locations.forEach(location => {
if (!buildings[location.name]) {
const type = location.name.includes('住宅') ? '住宅' :
location.name.includes('公園') ? '公園' :
location.name.includes('図書館') ? '図書館' :
location.name.includes('カフェ') ? 'カフェ' :
location.name.includes('商店') ? '商店' : 'その他';
createBuilding(location.name, location.position, type);
}
});
// 住民の位置を更新
state.residents.forEach(resident => {
if (!residents[resident.name]) {
createResident(resident.name, resident.position, resident.profession);
} else {
// 現在位置から目標位置への移動をアニメーション
const currentPos = residents[resident.name].position;
const targetPos = new THREE.Vector3(
resident.position.x,
0,
resident.position.z
);
// 移動速度
const speed = 0.1;
// 現在位置から目標位置への方向ベクトル
const direction = targetPos.sub(currentPos).normalize();
// 移動
currentPos.add(direction.multiplyScalar(speed));
// 住民の向きを移動方向に合わせる
if (direction.length() > 0) {
residents[resident.name].lookAt(targetPos);
}
}
});
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function animate() {
requestAnimationFrame(animate);
controls.update();
renderer.render(scene, camera);
}
// 初期化
init();