Meta Description" name="description" />
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Roblox Web Obby - Fixed Physics</title>
<style>
body { margin: 0; overflow: hidden; background: #87CEEB; font-family: sans-serif; }
#ui { position: absolute; top: 20px; left: 20px; color: white; text-shadow: 2px 2px 4px #000; font-size: 24px; pointer-events: none; z-index: 10; }
#msg { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); color: white; background: rgba(0,0,0,0.8); padding: 30px; border-radius: 15px; text-align: center; cursor: pointer; z-index: 20; }
</style>
</head>
<body>
<div id="ui">Level: <span id="lvl-txt">1</span></div>
<div id="msg"><h1>ROBLOX OBBY</h1><p>CLICK TO START</p><p>WASD to Move | Space to Jump</p></div>
<script type="importmap">
{ "imports": { "three": "https://unpkg.com/three@0.160.0/build/three.module.js" } }
</script>
<script type="module">
import * as THREE from 'three';
let scene, camera, renderer, clock, player, charModel, camPivot, portal;
let platforms = [], currentLevel = 1;
const keys = { w: false, a: false, s: false, d: false, space: false };
let velocity = new THREE.Vector3(), canJump = false;
// Player height constants
const PLAYER_HEIGHT = 1.0;
init();
animate();
function init() {
scene = new THREE.Scene();
scene.background = new THREE.Color(0x87CEEB);
scene.fog = new THREE.Fog(0x87CEEB, 20, 200);
clock = new THREE.Clock();
// Lighting
scene.add(new THREE.HemisphereLight(0xffffff, 0x444444, 1.8));
const sun = new THREE.DirectionalLight(0xffffff, 1);
sun.position.set(10, 50, 10);
scene.add(sun);
// Player Group
player = new THREE.Group();
scene.add(player);
// Camera Setup (Third Person)
camPivot = new THREE.Group();
player.add(camPivot);
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 6, 15);
camera.lookAt(0, 2, 0);
camPivot.add(camera);
createNoob();
buildLevel(1);
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(window.devicePixelRatio);
document.body.appendChild(renderer.domElement);
// Input Listeners
document.addEventListener('keydown', (e) => {
let key = e.code.toLowerCase().replace('key','');
if(key === 'space') keys.space = true;
if(keys.hasOwnProperty(key)) keys[key] = true;
});
document.addEventListener('keyup', (e) => {
let key = e.code.toLowerCase().replace('key','');
if(key === 'space') keys.space = false;
if(keys.hasOwnProperty(key)) keys[key] = false;
});
document.body.addEventListener('click', () => {
document.body.requestPointerLock();
document.getElementById('msg').style.display='none';
});
document.addEventListener('mousemove', (e) => {
if(document.pointerLockElement) camPivot.rotation.y -= e.movementX * 0.005;
});
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
}
function createNoob() {
charModel = new THREE.Group();
const yellow = new THREE.MeshStandardMaterial({ color: 0xFFD700 });
const blue = new THREE.MeshStandardMaterial({ color: 0x0055FF });
const green = new THREE.MeshStandardMaterial({ color: 0x4CAF50 });
const torso = new THREE.Mesh(new THREE.BoxGeometry(1.2, 1.4, 0.7), blue);
torso.position.y = 1.7;
charModel.add(torso);
const head = new THREE.Mesh(new THREE.BoxGeometry(0.8, 0.8, 0.8), yellow);
head.position.y = 2.8;
charModel.add(head);
const armL = new THREE.Mesh(new THREE.BoxGeometry(0.5, 1.4, 0.5), yellow);
armL.position.set(-0.9, 1.7, 0);
charModel.add(armL);
const armR = armL.clone(); armR.position.x = 0.9;
charModel.add(armR);
const legL = new THREE.Mesh(new THREE.BoxGeometry(0.6, 1, 0.6), green);
legL.position.set(-0.35, 0.5, 0);
charModel.add(legL);
const legR = legL.clone(); legR.position.x = 0.35;
charModel.add(legR);
player.add(charModel);
}
function buildLevel(num) {
platforms.forEach(p => scene.remove(p));
platforms = [];
if(portal) scene.remove(portal);
if(num === 1) {
spawnPlatform(0, 0, 0, 15, 1, 15, 0xaaaaaa); // Spawn
spawnPlatform(0, 2, -20, 8, 1, 8, 0x9b59b6); // Purple
spawnPlatform(10, 4, -30, 8, 1, 8, 0x3498db); // Blue
createPortal(10, 5, -30);
} else {
// LEVEL 2 - RAINBOW OBBY
spawnPlatform(0, 0, 0, 15, 1, 15, 0x3498db);
const colors = [0xff0000, 0xffa500, 0xffff00, 0x008000, 0x0000ff, 0x4b0082];
for(let i=0; i<colors.length; i++) {
spawnPlatform(-10 + (i*4), i*2, -20 - (i*6), 6, 0.6, 6, colors[i]);
}
// Spheres
for(let i=0; i<4; i++) {
const sphere = new THREE.Mesh(new THREE.SphereGeometry(2.5, 32, 32), new THREE.MeshStandardMaterial({color: 0x2ecc71}));
sphere.position.set(10, 12, -60 - (i*10));
scene.add(sphere);
platforms.push(sphere);
}
// Rainbow Tube (Inside floor)
for(let i=0; i<25; i++) {
spawnPlatform(0, 15, -100 - i, 6, 0.2, 1, i%2===0 ? 0xff0000 : 0xffff00);
}
spawnPlatform(0, 15, -135, 20, 1, 20, 0xffd700); // Winner Gold
}
player.position.set(0, 10, 0); // Spawn from sky
velocity.set(0,0,0);
}
function spawnPlatform(x,y,z,w,h,d,color) {
const p = new THREE.Mesh(new THREE.BoxGeometry(w,h,d), new THREE.MeshStandardMaterial({color: color}));
p.position.set(x,y,z);
scene.add(p);
platforms.push(p);
}
function createPortal(x,y,z) {
portal = new THREE.Mesh(new THREE.TorusGeometry(2.5, 0.3, 16, 100), new THREE.MeshBasicMaterial({color: 0x00ffff, wireframe: true}));
portal.position.set(x,y+3,z);
scene.add(portal);
}
function animate() {
requestAnimationFrame(animate);
const delta = Math.min(clock.getDelta(), 0.1);
if (document.pointerLockElement) {
// Gravity
velocity.y -= 40 * delta;
player.position.y += velocity.y * delta;
// --- IMPROVED COLLISION ---
canJump = false;
const playerBox = new THREE.Box3().setFromCenterAndSize(
player.position,
new THREE.Vector3(1, 0.1, 1) // Small check at feet
);
platforms.forEach(p => {
const pBox = new THREE.Box3().setFromObject(p);
// Check if player is above platform and falling into it
if (player.position.x > pBox.min.x - 0.5 && player.position.x < pBox.max.x + 0.5 &&
player.position.z > pBox.min.z - 0.5 && player.position.z < pBox.max.z + 0.5) {
if (player.position.y <= pBox.max.y + 0.1 && player.position.y >= pBox.max.y - 1.0) {
if (velocity.y <= 0) {
velocity.y = 0;
player.position.y = pBox.max.y;
canJump = true;
}
}
}
});
// Movement
let mx = 0, mz = 0;
if(keys.w) mz -= 1; if(keys.s) mz += 1;
if(keys.a) mx -= 1; if(keys.d) mx += 1;
if(mx !== 0 || mz !== 0) {
const rot = camPivot.rotation.y;
const dx = (mx * Math.cos(rot) + mz * Math.sin(rot));
const dz = (mz * Math.cos(rot) - mx * Math.sin(rot));
player.position.x += dx * 15 * delta;
player.position.z += dz * 15 * delta;
charModel.rotation.y = Math.atan2(dx, dz);
}
if(keys.space && canJump) {
velocity.y = 18;
canJump = false;
}
// Portal Transition
if(portal && player.position.distanceTo(portal.position) < 4) {
currentLevel = 2;
document.getElementById('lvl-txt').innerText = "2";
buildLevel(2);
}
// Fall Off Respawn
if(player.position.y < -30) buildLevel(currentLevel);
}
if(portal) portal.rotation.z += 0.05;
renderer.render(scene, camera);
}
</script>
</body>
</html>
2
2
262KB
1253KB
338.0ms
116.0ms
338.0ms