import * as THREE from 'three'; import { PointerLockControls } from 'PointerLockControls'; import { World, Body, Box, Sphere, Vec3 } from 'cannon-es'; const scene = new THREE.Scene(); const camera = new THREE.PerspectiveCamera(75, window.innerWidth/window.innerHeight, 0.1, 1000); const renderer = new THREE.WebGLRenderer(); renderer.setSize(window.innerWidth, window.innerHeight); document.body.appendChild(renderer.domElement); // ライト scene.add(new THREE.HemisphereLight(0xffffff, 0x444444)); // 地面 const floor = new THREE.Mesh(new THREE.BoxGeometry(100, 1, 100), new THREE.MeshStandardMaterial({ color: 0x888888 })); floor.position.y = -0.5; scene.add(floor); // 物理ワールド const world = new World(); world.gravity.set(0, -9.82, 0); // 地面の物理 const groundBody = new Body({ mass: 0, shape: new Box(new Vec3(50, 0.5, 50)), position: new Vec3(0, -0.5, 0) }); world.addBody(groundBody); // 壁 const wall_1 = new THREE.Mesh( new THREE.BoxGeometry(5, 5, 1), // 幅, 高さ, 奥行き new THREE.MeshStandardMaterial({ color: 0x4444ff }) ); wall_1.position.set(0, 0.5, -10); // 地面からの高さを考慮してY=2.5 scene.add(wall_1); const wall_1Body = new Body({ mass: 0, // 静的 shape: new Box(new Vec3(2.5, 2.5, 0.5)), // 半分のサイズを指定 position: new Vec3(0, 0.5, -10) }); world.addBody(wall_1Body); // プレイヤーの物理ボディ const playerRadius = 1; const playerBody = new Body({ mass: 5, shape: new Sphere(playerRadius), position: new Vec3(0, 5, 0), linearDamping: 0.9 }); world.addBody(playerBody); // FPSコントロール const controls = new PointerLockControls(camera, document.body); document.addEventListener('click', () => { controls.lock(); }); const velocity = new Vec3(); const direction = new THREE.Vector3(); const keyState = {}; document.addEventListener('keydown', (e) => { keyState[e.code] = true; }); document.addEventListener('keyup', (e) => { keyState[e.code] = false; }); let canJump = false; playerBody.addEventListener('collide', (e) => { const contact = e.contact; const contactNormal = new Vec3(); if (contact.bi.id === playerBody.id) { contact.ni.negate(contactNormal); } else { contactNormal.copy(contact.ni); } if (contactNormal.y > 0.5) { canJump = true; } }); let lastTime; function animate(time) { requestAnimationFrame(animate); if (!lastTime) lastTime = time; const deltaTime = (time - lastTime) / 1000; // 秒に変換 lastTime = time; // プレイヤー操作 const moveSpeed = 8; const forward = new THREE.Vector3(); controls.getDirection(forward); forward.y = 0; forward.normalize(); const right = new THREE.Vector3(); right.crossVectors(forward, camera.up); // 入力方向をベクトルとして合成 direction.set(0, 0, 0); if (keyState['KeyW']) direction.add(forward); if (keyState['KeyS']) direction.sub(forward); if (keyState['KeyA']) direction.sub(right); if (keyState['KeyD']) direction.add(right); direction.normalize(); velocity.set(direction.x * moveSpeed, playerBody.velocity.y, direction.z * moveSpeed); playerBody.velocity.copy(velocity); if (keyState['Space'] && canJump) { playerBody.velocity.y = 8; canJump = false; } world.step(1/60, deltaTime); // プレイヤーの物理ボディにカメラを追従 camera.position.copy(playerBody.position); renderer.render(scene, camera); } requestAnimationFrame(animate);