Happy-Valley-Game / index.html
awacke1's picture
Create index.html
f7538ec verified
raw
history blame
13.1 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Shared 3D World Builder</title>
<style>
body { margin: 0; overflow: hidden; }
canvas { display: block; }
#info { position: absolute; top: 10px; left: 10px; color: white; background: rgba(0,0,0,0.7); padding: 10px; }
</style>
</head>
<body>
<div id="info">Use WASD to move, mouse to look, click to place object, right-click to delete.</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r134/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/examples/js/controls/PointerLockControls.js"></script>
<script>
let scene, camera, renderer, controls, websocket, selectedObjectType = 'None', worldObjects = {};
let moveForward = false, moveBackward = false, moveLeft = false, moveRight = false;
const username = window.USERNAME || 'Anonymous';
const websocketUrl = window.WEBSOCKET_URL || 'ws://localhost:8765';
const plotWidth = window.PLOT_WIDTH || 50.0;
const plotDepth = window.PLOT_DEPTH || 50.0;
function init() {
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
controls = new THREE.PointerLockControls(camera, document.body);
scene.add(controls.getObject());
camera.position.y = 1.6;
const groundGeometry = new THREE.PlaneGeometry(plotWidth, plotDepth);
const groundMaterial = new THREE.MeshBasicMaterial({ color: 0x888888 });
const ground = new THREE.Mesh(groundGeometry, groundMaterial);
ground.rotation.x = -Math.PI / 2;
scene.add(ground);
const gridHelper = new THREE.GridHelper(plotWidth, plotWidth / 2);
scene.add(gridHelper);
document.addEventListener('click', () => controls.lock());
document.addEventListener('keydown', onKeyDown);
document.addEventListener('keyup', onKeyUp);
document.addEventListener('mousedown', onMouseDown);
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
initWebSocket();
animate();
}
function initWebSocket() {
websocket = new WebSocket(websocketUrl);
websocket.onopen = () => console.log('WebSocket connected');
websocket.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.type === 'initial_state' || data.type === 'object_placed') {
const objects = data.type === 'initial_state' ? data.payload : { [data.payload.object_data.obj_id]: data.payload.object_data };
for (let obj_id in objects) {
if (!worldObjects[obj_id]) {
worldObjects[obj_id] = objects[obj_id];
addObjectToScene(objects[obj_id]);
}
}
} else if (data.type === 'object_deleted') {
if (worldObjects[data.payload.obj_id]) {
removeObjectFromScene(data.payload.obj_id);
delete worldObjects[data.payload.obj_id];
}
} else if (data.type === 'player_moved') {
// Handle player movement visualization if needed
} else if (data.type === 'user_join' || data.type === 'user_leave' || data.type === 'user_rename') {
console.log(`${data.payload.username} ${data.type === 'user_join' ? 'joined' : data.type === 'user_leave' ? 'left' : 'renamed to ' + data.payload.new_username}`);
}
};
websocket.onclose = () => console.log('WebSocket closed');
websocket.onerror = (error) => console.error('WebSocket error:', error);
}
function updateSelectedObjectType(newType) {
selectedObjectType = newType;
console.log(`Tool changed to: ${newType}`);
}
function addObjectToScene(objData) {
let geometry, material, mesh;
switch (objData.type) {
case 'Cube':
geometry = new THREE.BoxGeometry(1, 1, 1);
material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
break;
case 'Sphere':
geometry = new THREE.SphereGeometry(0.5, 32, 32);
material = new THREE.MeshBasicMaterial({ color: 0xff0000 });
break;
case 'Cylinder':
geometry = new THREE.CylinderGeometry(0.5, 0.5, 1, 32);
material = new THREE.MeshBasicMaterial({ color: 0x0000ff });
break;
case 'Cone':
geometry = new THREE.ConeGeometry(0.5, 1, 32);
material = new THREE.MeshBasicMaterial({ color: 0xffff00 });
break;
case 'Torus':
geometry = new THREE.TorusGeometry(0.4, 0.1, 16, 100);
material = new THREE.MeshBasicMaterial({ color: 0xff00ff });
break;
case 'Tree':
geometry = new THREE.ConeGeometry(0.5, 2, 8);
material = new THREE.MeshBasicMaterial({ color: 0x228B22 });
break;
case 'Rock':
geometry = new THREE.DodecahedronGeometry(0.5);
material = new THREE.MeshBasicMaterial({ color: 0x808080 });
break;
case 'Simple House':
geometry = new THREE.BoxGeometry(2, 1.5, 2);
material = new THREE.MeshBasicMaterial({ color: 0xA52A2A });
break;
case 'Pine Tree':
geometry = new THREE.ConeGeometry(0.3, 1.5, 8);
material = new THREE.MeshBasicMaterial({ color: 0x006400 });
break;
case 'Brick Wall':
geometry = new THREE.BoxGeometry(3, 1, 0.2);
material = new THREE.MeshBasicMaterial({ color: 0x8B4513 });
break;
case 'Mushroom':
geometry = new THREE.SphereGeometry(0.5, 32, 16, 0, Math.PI);
material = new THREE.MeshBasicMaterial({ color: 0xF5F5DC });
break;
case 'Cactus':
geometry = new THREE.CylinderGeometry(0.2, 0.2, 1.5, 8);
material = new THREE.MeshBasicMaterial({ color: 0x2E8B57 });
break;
case 'Campfire':
geometry = new THREE.DodecahedronGeometry(0.3);
material = new THREE.MeshBasicMaterial({ color: 0xFF4500 });
break;
case 'Star':
geometry = new THREE.SphereGeometry(0.3, 4, 2);
material = new THREE.MeshBasicMaterial({ color: 0xFFFF00 });
break;
case 'Gem':
geometry = new THREE.OctahedronGeometry(0.4);
material = new THREE.MeshBasicMaterial({ color: 0x00CED1 });
break;
case 'Tower':
geometry = new THREE.CylinderGeometry(0.3, 0.5, 2, 8);
material = new THREE.MeshBasicMaterial({ color: 0x708090 });
break;
case 'Barrier':
geometry = new THREE.BoxGeometry(2, 0.5, 0.2);
material = new THREE.MeshBasicMaterial({ color: 0x696969 });
break;
case 'Fountain':
geometry = new THREE.CylinderGeometry(0.5, 0.3, 1, 32);
material = new THREE.MeshBasicMaterial({ color: 0x4682B4 });
break;
case 'Lantern':
geometry = new THREE.BoxGeometry(0.3, 0.5, 0.3);
material = new THREE.MeshBasicMaterial({ color: 0xFFD700 });
break;
case 'Sign Post':
geometry = new THREE.BoxGeometry(0.2, 1, 0.2);
material = new THREE.MeshBasicMaterial({ color: 0x8B4513 });
break;
default:
return;
}
mesh = new THREE.Mesh(geometry, material);
mesh.position.set(objData.position.x, objData.position.y, objData.position.z);
mesh.rotation.set(objData.rotation.x, objData.rotation.y, objData.rotation.z);
mesh.name = objData.obj_id;
scene.add(mesh);
}
function removeObjectFromScene(obj_id) {
const object = scene.getObjectByName(obj_id);
if (object) {
scene.remove(object);
}
}
function onKeyDown(event) {
switch (event.code) {
case 'KeyW': moveForward = true; break;
case 'KeyS': moveBackward = true; break;
case 'KeyA': moveLeft = true; break;
case 'KeyD': moveRight = true; break;
}
}
function onKeyUp(event) {
switch (event.code) {
case 'KeyW': moveForward = false; break;
case 'KeyS': moveBackward = false; break;
case 'KeyA': moveLeft = false; break;
case 'KeyD': moveRight = false; break;
}
}
function onMouseDown(event) {
if (!controls.isLocked) return;
if (event.button === 0 && selectedObjectType !== 'None') {
const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2(0, 0);
raycaster.setFromCamera(mouse, camera);
const intersects = raycaster.intersectObjects(scene.children);
for (let intersect of intersects) {
if (intersect.object !== controls.getObject()) {
const position = intersect.point;
position.y += 0.5;
placeObject(position);
break;
}
}
} else if (event.button === 2) {
const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2(0, 0);
raycaster.setFromCamera(mouse, camera);
const intersects = raycaster.intersectObjects(scene.children);
for (let intersect of intersects) {
if (intersect.object !== controls.getObject() && intersect.object.name) {
websocket.send(JSON.stringify({
type: 'delete_object',
payload: { obj_id: intersect.object.name, username }
}));
break;
}
}
}
}
function placeObject(position) {
if (selectedObjectType === 'None') return;
const obj_id = `obj_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
const objectData = {
obj_id: obj_id,
type: selectedObjectType,
position: { x: position.x, y: position.y, z: position.z },
rotation: { x: 0, y: 0, z: 0 }
};
websocket.send(JSON.stringify({
type: 'place_object',
payload: { username, object_data: objectData }
}));
}
function animate() {
requestAnimationFrame(animate);
const delta = 0.1;
const velocity = new THREE.Vector3();
if (moveForward) velocity.z -= 0.1;
if (moveBackward) velocity.z += 0.1;
if (moveLeft) velocity.x -= 0.1;
if (moveRight) velocity.x += 0.1;
controls.getObject().position.add(velocity);
const pos = controls.getObject().position;
const rot = controls.getObject().rotation;
websocket.send(JSON.stringify({
type: 'player_position',
payload: {
username,
position: { x: pos.x, y: pos.y, z: pos.z },
rotation: { x: rot.x, y: rot.y, z: rot.z }
}
}));
renderer.render(scene, camera);
}
init();
</script>
</body>
</html>