import * as THREE from "https://unpkg.com/three@0.126.1/build/three.module.js";
import { PointerLockControls } from "https://unpkg.com/three@0.126.1/examples/jsm/controls/PointerLockControls.js";
import { GLTFLoader } from "https://unpkg.com/three@0.126.1/examples/jsm/loaders/GLTFLoader.js";
import { sendGhostPosition, sendUserName } from "./client.js";
let camera, scene, renderer, controls, mixer; // カメラ、シーン、レンダラー、コントロール、ミキサーの追加
let moveForward = false,
moveBackward = false,
moveLeft = false,
moveRight = false;
let ghostLoaded = false,
TalkRoom,
WorldStick,
ghost; // ghostモデル本体
let object; // メインの3Dオブジェクト参照用(通常、ghostと同じ 感情モーフ等)
let lastSentPosition = { x: null, y: null, z: null }; //ghostの場所
let connections = 3;
let headPartLogged = false;
// ユーザー名を取得してサーバーに送信
let userName = prompt("あなたの名前を入力してください:");
alert(`こんにちは、${userName}さん!`);
sendUserName(userName);
// アニメーション再生関数
function playAnimation(animationName) {
if (!mixer || !ghost || !ghost.animations) return;
const clip = THREE.AnimationClip.findByName(ghost.animations, animationName);
if (clip) {
const action = mixer.clipAction(clip);
action.reset().play();
}
}
function init() {
scene = new THREE.Scene();
scene.background = new THREE.Color(0xffffff);
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(1, 5, 5);
camera.lookAt(new THREE.Vector3(0, 0, 0));
const isMobile = window.innerWidth < 768;
if (isMobile) {
camera.position.set(50, 100, 100);
} else {
camera.position.set(1, 5, 5);
}
const light = new THREE.AmbientLight(0xffffff, 0.8);
light.position.set(100, 500, 500);
scene.add(light);
light.castShadow = true;
const loader = new GLTFLoader();
loader.load("talkroom.glb", (gltf) => {
TalkRoom = gltf.scene;
scene.add(TalkRoom);
});
loader.load("guidestick.glb", (gltf) => {
WorldStick = gltf.scene;
scene.add(WorldStick);
});
for (let i = 0; i < connections; i++) {
loader.load("ghost.glb", (gltf) => {
ghost = gltf.scene;
object = ghost;
scene.add(ghost);
ghostLoaded = true;
// 座標をランダムで決定
ghost.position.x = Math.round(Math.random() * 80) - 30;
ghost.position.y = Math.random() * 0 - 0; // 高さ指定
ghost.position.z = Math.round(Math.random() * 50);
const pos = ghost.position;
console.log(pos.x, pos.y, pos.z);
const headPart = ghost.getObjectByName("head-part");
if (headPart && !headPartLogged) {
console.log("head-partが見つかりました:", headPart);
headPartLogged = true; // これで次からは表示されなくなる
} else if (!headPart && !headPartLogged) {
console.warn("head-partは見つかりませんでした。");
headPartLogged = true;
}
mixer = new THREE.AnimationMixer(ghost);
if (gltf.animations && gltf.animations.length > 0) {
gltf.animations.forEach((clip) => {
mixer.clipAction(clip).play();
});
}
ghost.animations = gltf.animations;
});
}
controls = new PointerLockControls(camera, document.body);
scene.add(controls.getObject());
document.getElementById("WebGL-output").addEventListener("click", () => controls.lock());
document.getElementById("overlay").addEventListener("click", (e) => {
e.preventDefault();
e.stopPropagation();
});
document.addEventListener("keydown", handleKeyDown);
document.addEventListener("keyup", handleKeyUp);
renderer = new THREE.WebGLRenderer({ alpha: true, antialias: true });
renderer.setSize(window.innerWidth * 0.8, window.innerHeight * 0.8);
document.getElementById("WebGL-output").appendChild(renderer.domElement);
}
// キーボード操作処理
function handleKeyDown(event) {
switch (event.key) {
// 空間操作
case "w":
moveForward = true;
break;
case "s":
moveBackward = true;
break;
case "a":
moveLeft = true;
break;
case "d":
moveRight = true;
break;
// アバター操作
case "g":
case "ArrowUp":
if (ghostLoaded) ghost.position.z -= 0.5;
break;
case "b":
case "ArrowDown":
if (ghostLoaded) ghost.position.z += 0.5;
break;
case "h":
case "ArrowRight":
if (ghostLoaded) ghost.position.x += 0.5;
break;
case "f":
case "ArrowLeft":
if (ghostLoaded) ghost.position.x -= 0.5;
break;
case "r":
if (ghostLoaded) ghost.rotation.y += Math.PI / 16;
break;
}
}
function handleKeyUp(event) {
switch (event.key) {
case "w":
case "ArrowUp":
moveForward = false;
break;
case "s":
case "ArrowDown":
moveBackward = false;
break;
case "a":
case "ArrowLeft":
moveLeft = false;
break;
case "d":
case "ArrowRight":
moveRight = false;
break;
}
}
// 移動ボタン
const moveforBtn = document.getElementById('forward');
moveforBtn.addEventListener('click', function () {
if (ghostLoaded) ghost.position.z -= 2;
});
const movebackBtn = document.getElementById('back');
movebackBtn.addEventListener('click', function () {
if (ghostLoaded) ghost.position.z += 2;
});
const moverightBtn = document.getElementById('right');
moverightBtn.addEventListener('click', function () {
if (ghostLoaded) ghost.position.x += 2;
});
const moveleftBtn = document.getElementById('left');
moveleftBtn.addEventListener('click', function () {
if (ghostLoaded) ghost.position.x -= 2;
});
const moveturn = document.getElementById('turn');
moveturn.addEventListener('click', function () {
if (ghostLoaded) ghost.rotation.y += Math.PI / 12;
});
// カメラ位置更新
function updateCameraPosition() {
const speed = 10;
const direction = new THREE.Vector3();
direction.x = Number(moveRight) - Number(moveLeft);
direction.z = Number(moveBackward) - Number(moveForward);
direction.normalize();
const delta = speed * 0.1;
controls.getObject().translateX(direction.x * delta);
controls.getObject().translateZ(direction.z * delta);
}
// アニメーションループ
function animate() {
requestAnimationFrame(animate);
updateCameraPosition();
if (mixer) mixer.update(0.01);
renderer.render(scene, camera);
}
function logGhostPosition() {
if (ghostLoaded && ghost) {
const pos = new THREE.Vector3();
ghost.getWorldPosition(pos);
console.log("Ghost位置:", pos.x.toFixed(2), pos.y.toFixed(2), pos.z.toFixed(2));
}
}
setInterval(logGhostPosition, 150); // 0.15秒ごとに出力
// クリック時の処理
document.addEventListener("dblclick", function (event) {
event.preventDefault();
const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();
const rect = renderer.domElement.getBoundingClientRect();
mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
raycaster.setFromCamera(mouse, camera);
const intersects = raycaster.intersectObjects(scene.children, true);
if (intersects.length > 0) {
if (intersects[0].object.name == "円柱001_1") {
window.open("choose.html");
}
console.log(intersects[0]);
}
});
// 感情名とmorphTarget名のマッピング
const emotionMorphMap = {
smile: "smile",
sad: "sad",
surprised: "surprised"
};
function handleEmotionButtonClick(emotion) {
console.log(`${emotion}ボタンがクリックされました!`);
playAnimation(emotion);
const morphName = emotionMorphMap[emotion];
if (!morphName) return;
if (!object) {
console.warn("objectが未ロードです。");
return;
};
const headPartGroup = object.getObjectByName("head-part");
if (!headPartGroup) {
console.warn("head-partグループが見つかりませんでした。");
return;
}
headPartGroup.traverse((child) => {
if (child.isMesh && child.morphTargetDictionary && child.morphTargetInfluences) {
const morphIndex = child.morphTargetDictionary[morphName];
if (morphIndex !== undefined) {
let current = child.morphTargetInfluences[morphIndex];
let target = (current > 0.5) ? 0.0 : 1.0;
let speed = 0.05;
function updateMorph() {
if (Math.abs(current - target) > 0.01) {
current += (target - current) * speed;
child.morphTargetInfluences[morphIndex] = current;
requestAnimationFrame(updateMorph);
} else {
child.morphTargetInfluences[morphIndex] = target;
}
}
updateMorph();
}
}
});
}
// 感情ボタンイベント登録
document.querySelectorAll('.emotion-button').forEach(button => {
button.addEventListener('click', function () {
const emotion = this.dataset.emotion;
handleEmotionButtonClick(emotion);
});
});
// 初期化
init();
animate();
// 位置が変わったかどうか判定
function hasPositionChanged(current, last) {
return current.x !== last.x || current.y !== last.y || current.z !== last.z;
}
// 位置が変わったときだけ送信
setInterval(() => {
if (ghostLoaded && ghost) {
const current = {
x: ghost.position.x,
y: ghost.position.y,
z: ghost.position.z
};
if (hasPositionChanged(current, lastSentPosition)) {
sendGhostPosition(current.x, current.y, current.z);
lastSentPosition = { ...current };
}
// 別モデルの位置情報にしたがって動かせるようにしたい
ghost.position.set(current.x, current.y, current.z);
}
}, 300);