MultiBlox Platform - Prototype v0.1
by DriftBear16603 lines18.1 KB🛠️ Three.js (3D graphics)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<title>MultiBlox Platform - Prototype v0.1</title>
<style>
:root {
--bg-dark: #0f111a;
--panel-bg: rgba(20, 24, 38, 0.85);
--accent-blue: #00d2ff;
--accent-purple: #7928ca;
--accent-gold: #ffb703;
--text-main: #f8fafc;
--text-muted: #94a3b8;
--border: rgba(255, 255, 255, 0.12);
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
user-select: none;
-webkit-user-select: none;
}
body, html {
width: 100%;
height: 100%;
overflow: hidden;
background-color: var(--bg-dark);
font-family: 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
color: var(--text-main);
}
#canvas-container {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
z-index: 1;
}
/* MultiBlox UI Overlay */
#ui-layer {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 10;
pointer-events: none;
display: flex;
flex-direction: column;
justify-content: space-between;
padding: 16px;
}
.interactive {
pointer-events: auto;
}
/* Top Platform Header */
.top-bar {
display: flex;
justify-content: space-between;
align-items: center;
background: var(--panel-bg);
backdrop-filter: blur(12px);
border: 1px solid var(--border);
border-radius: 12px;
padding: 10px 20px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
}
.brand-group {
display: flex;
align-items: center;
gap: 12px;
}
.brand-logo {
font-size: 20px;
font-weight: 900;
letter-spacing: 1px;
background: linear-gradient(135deg, var(--accent-blue), var(--accent-purple));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
text-transform: uppercase;
}
.stats-group {
display: flex;
align-items: center;
gap: 16px;
}
.stat-badge {
display: flex;
align-items: center;
gap: 8px;
font-size: 14px;
font-weight: 600;
background: rgba(255, 255, 255, 0.05);
padding: 6px 14px;
border-radius: 20px;
border: 1px solid var(--border);
}
.currency-icon {
color: var(--accent-gold);
font-weight: 900;
}
/* Health HUD */
.hud-bottom-left {
display: flex;
flex-direction: column;
gap: 8px;
width: 240px;
}
.health-bar-container {
width: 100%;
height: 14px;
background: rgba(0, 0, 0, 0.6);
border-radius: 7px;
overflow: hidden;
border: 1px solid var(--border);
}
.health-bar-fill {
width: 100%;
height: 100%;
background: linear-gradient(90deg, #ff4b4b, #ff8533);
transition: width 0.2s ease;
}
.hud-label {
font-size: 12px;
font-weight: 700;
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: 0.5px;
}
/* Mobile Controls Overlay */
.mobile-controls {
display: none;
position: absolute;
bottom: 24px;
width: calc(100% - 48px);
justify-content: space-between;
align-items: flex-end;
pointer-events: none;
}
.joystick-zone {
width: 120px;
height: 120px;
background: rgba(255, 255, 255, 0.1);
border: 2px dashed rgba(255, 255, 255, 0.3);
border-radius: 50%;
position: relative;
pointer-events: auto;
touch-action: none;
}
.joystick-knob {
width: 50px;
height: 50px;
background: var(--accent-blue);
border-radius: 50%;
position: absolute;
top: 35px;
left: 35px;
opacity: 0.8;
}
.action-btn {
width: 65px;
height: 65px;
border-radius: 50%;
background: var(--panel-bg);
border: 2px solid var(--accent-blue);
color: var(--text-main);
font-weight: bold;
font-size: 14px;
display: flex;
justify-content: center;
align-items: center;
pointer-events: auto;
touch-action: none;
}
.action-btn:active {
background: var(--accent-blue);
color: #000;
}
.crosshair {
position: absolute;
top: 50%;
left: 50%;
width: 6px;
height: 6px;
background: rgba(255, 255, 255, 0.4);
border-radius: 50%;
transform: translate(-50%, -50%);
pointer-events: none;
}
@media (max-width: 768px) {
.mobile-controls {
display: flex;
}
}
</style>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
</head>
<body>
<div id="canvas-container"></div>
<div id="ui-layer">
<!-- Top Platform Header -->
<div class="top-bar interactive">
<div class="brand-group">
<div class="brand-logo">MultiBlox</div>
</div>
<div class="stats-group">
<div class="stat-badge">
<span class="currency-icon">⛃</span>
<span id="multibux-val">0</span>
<span style="font-size: 10px; color: var(--text-muted);">(₹<span id="inr-val">0</span>)</span>
</div>
<div class="stat-badge">
<span>🏆 Score:</span>
<span id="score-val">0</span>
</div>
<div class="stat-badge">
<span>Lv. <span id="level-val">1</span></span>
</div>
</div>
</div>
<div class="crosshair"></div>
<!-- HUD Status -->
<div class="hud-bottom-left">
<div class="hud-label">Player Health</div>
<div class="health-bar-container">
<div id="health-fill" class="health-bar-fill"></div>
</div>
</div>
<!-- Mobile Virtual Controls -->
<div class="mobile-controls">
<div class="joystick-zone" id="joystick-zone">
<div class="joystick-knob" id="joystick-knob"></div>
</div>
<div class="action-btn" id="mobile-jump-btn">JUMP</div>
</div>
</div>
<script>
/* =========================================================================
1. STATE & CURRENCY (1 MultiBux = ₹5 INR)
========================================================================= */
const MULTIBUX_TO_INR_RATE = 5;
const PlayerState = {
name: "Player_1",
health: 100,
maxHealth: 100,
multiBux: 100,
score: 0,
level: 1,
speed: 12,
sprintMultiplier: 1.6,
jumpVelocity: 14,
isGrounded: false
};
function updateEconomyHUD() {
document.getElementById('multibux-val').innerText = PlayerState.multiBux;
document.getElementById('inr-val').innerText = PlayerState.multiBux * MULTIBUX_TO_INR_RATE;
document.getElementById('score-val').innerText = PlayerState.score;
document.getElementById('level-val').innerText = PlayerState.level;
}
updateEconomyHUD();
/* =========================================================================
2. 3D SCENE & LIGHTING SETUP
========================================================================= */
const container = document.getElementById('canvas-container');
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x0a0c14);
scene.fog = new THREE.FogExp2(0x0a0c14, 0.015);
const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
container.appendChild(renderer.domElement);
const hemiLight = new THREE.HemisphereLight(0xffffff, 0x334455, 0.6);
scene.add(hemiLight);
const dirLight = new THREE.DirectionalLight(0xfffaed, 0.9);
dirLight.position.set(40, 60, 30);
dirLight.castShadow = true;
dirLight.shadow.mapSize.width = 2048;
dirLight.shadow.mapSize.height = 2048;
dirLight.shadow.camera.near = 0.5;
dirLight.shadow.camera.far = 150;
const shadowD = 40;
dirLight.shadow.camera.left = -shadowD;
dirLight.shadow.camera.right = shadowD;
dirLight.shadow.camera.top = shadowD;
dirLight.shadow.camera.bottom = -shadowD;
scene.add(dirLight);
// Floor Base
const arenaSize = 120;
const floorGeo = new THREE.PlaneGeometry(arenaSize, arenaSize);
const floorMat = new THREE.MeshStandardMaterial({
color: 0x1a2035,
roughness: 0.8,
metalness: 0.2
});
const floorMesh = new THREE.Mesh(floorGeo, floorMat);
floorMesh.rotation.x = -Math.PI / 2;
floorMesh.receiveShadow = true;
scene.add(floorMesh);
const gridHelper = new THREE.GridHelper(arenaSize, 60, 0x00d2ff, 0x223344);
gridHelper.position.y = 0.01;
scene.add(gridHelper);
// Modular Obstacles
const obstacles = [];
function createObstacle(x, y, z, w, h, d, color = 0x3b4252) {
const geo = new THREE.BoxGeometry(w, h, d);
const mat = new THREE.MeshStandardMaterial({ color, roughness: 0.5 });
const mesh = new THREE.Mesh(geo, mat);
mesh.position.set(x, y + h / 2, z);
mesh.castShadow = true;
mesh.receiveShadow = true;
scene.add(mesh);
obstacles.push({
box: new THREE.Box3().setFromObject(mesh),
mesh: mesh
});
}
createObstacle(0, 0, -15, 8, 2, 8, 0x4c566a);
createObstacle(12, 0, -10, 6, 4, 6, 0x5e81ac);
createObstacle(-15, 0, 10, 10, 1.5, 10, 0x88c0d0);
createObstacle(20, 0, 15, 4, 6, 4, 0x81a1c1);
/* =========================================================================
3. PLAYER CHARACTER RIG
========================================================================= */
const playerGroup = new THREE.Group();
// Torso
const torsoGeo = new THREE.BoxGeometry(1.2, 1.4, 0.8);
const playerMat = new THREE.MeshStandardMaterial({ color: 0x00d2ff, roughness: 0.3 });
const torso = new THREE.Mesh(torsoGeo, playerMat);
torso.position.y = 1.6;
torso.castShadow = true;
playerGroup.add(torso);
// Head
const headGeo = new THREE.BoxGeometry(0.8, 0.8, 0.8);
const headMat = new THREE.MeshStandardMaterial({ color: 0xf8fafc, roughness: 0.4 });
const head = new THREE.Mesh(headGeo, headMat);
head.position.y = 2.7;
head.castShadow = true;
playerGroup.add(head);
// Limbs
const limbMat = new THREE.MeshStandardMaterial({ color: 0x1e293b, roughness: 0.5 });
const legGeo = new THREE.BoxGeometry(0.5, 1.0, 0.5);
const leftLeg = new THREE.Mesh(legGeo, limbMat);
leftLeg.position.set(-0.35, 0.5, 0);
leftLeg.castShadow = true;
playerGroup.add(leftLeg);
const rightLeg = new THREE.Mesh(legGeo, limbMat);
rightLeg.position.set(0.35, 0.5, 0);
rightLeg.castShadow = true;
playerGroup.add(rightLeg);
playerGroup.position.set(0, 0, 0);
scene.add(playerGroup);
/* =========================================================================
4. CONTROLS & CAMERA DYNAMICS
========================================================================= */
let cameraDistance = 8;
let cameraPitch = 0.3;
let cameraYaw = 0;
const playerVelocity = new THREE.Vector3();
const gravity = 32;
const keys = {};
window.addEventListener('keydown', (e) => { keys[e.code] = true; });
window.addEventListener('keyup', (e) => { keys[e.code] = false; });
let isPointerLocked = false;
document.addEventListener('pointerlockchange', () => {
isPointerLocked = document.pointerLockElement === renderer.domElement;
});
renderer.domElement.addEventListener('click', () => {
renderer.domElement.requestPointerLock();
});
window.addEventListener('mousemove', (e) => {
if (isPointerLocked) {
cameraYaw -= e.movementX * 0.003;
cameraPitch -= e.movementY * 0.003;
cameraPitch = Math.max(-0.2, Math.min(1.2, cameraPitch));
}
});
// Mobile Virtual Joystick
const joystick = { active: false, x: 0, y: 0 };
const joystickZone = document.getElementById('joystick-zone');
const joystickKnob = document.getElementById('joystick-knob');
let touchId = null;
joystickZone.addEventListener('touchstart', (e) => {
const touch = e.changedTouches[0];
touchId = touch.identifier;
joystick.active = true;
handleJoystickMove(touch);
});
window.addEventListener('touchmove', (e) => {
if (!joystick.active) return;
for (let i = 0; i < e.changedTouches.length; i++) {
if (e.changedTouches[i].identifier === touchId) {
handleJoystickMove(e.changedTouches[i]);
break;
}
}
});
const resetJoystick = () => {
joystick.active = false;
joystick.x = 0;
joystick.y = 0;
joystickKnob.style.transform = 'translate(0px, 0px)';
};
window.addEventListener('touchend', (e) => {
for (let i = 0; i < e.changedTouches.length; i++) {
if (e.changedTouches[i].identifier === touchId) {
resetJoystick();
break;
}
}
});
function handleJoystickMove(touch) {
const rect = joystickZone.getBoundingClientRect();
const centerX = rect.left + rect.width / 2;
const centerY = rect.top + rect.height / 2;
const dx = touch.clientX - centerX;
const dy = touch.clientY - centerY;
const dist = Math.min(40, Math.hypot(dx, dy));
const angle = Math.atan2(dy, dx);
const offsetX = Math.cos(angle) * dist;
const offsetY = Math.sin(angle) * dist;
joystickKnob.style.transform = `translate(${offsetX}px, ${offsetY}px)`;
joystick.x = offsetX / 40;
joystick.y = offsetY / 40;
}
document.getElementById('mobile-jump-btn').addEventListener('touchstart', (e) => {
e.preventDefault();
if (PlayerState.isGrounded) {
playerVelocity.y = PlayerState.jumpVelocity;
PlayerState.isGrounded = false;
}
});
/* =========================================================================
5. MAIN ENGINE LOOP
========================================================================= */
const clock = new THREE.Clock();
function update(dt) {
const forward = new THREE.Vector3(-Math.sin(cameraYaw), 0, -Math.cos(cameraYaw));
const right = new THREE.Vector3(Math.cos(cameraYaw), 0, -Math.sin(cameraYaw));
let moveX = 0;
let moveZ = 0;
if (keys['KeyW'] || keys['ArrowUp']) moveZ += 1;
if (keys['KeyS'] || keys['ArrowDown']) moveZ -= 1;
if (keys['KeyA'] || keys['ArrowLeft']) moveX -= 1;
if (keys['KeyD'] || keys['ArrowRight']) moveX += 1;
if (joystick.active) {
moveX += joystick.x;
moveZ -= joystick.y;
}
const moveDir = new THREE.Vector3()
.addScaledVector(forward, moveZ)
.addScaledVector(right, moveX);
const isSprinting = keys['ShiftLeft'] || keys['ShiftRight'];
const currentSpeed = PlayerState.speed * (isSprinting ? PlayerState.sprintMultiplier : 1.0);
if (moveDir.lengthSq() > 0.001) {
moveDir.normalize();
playerGroup.position.x += moveDir.x * currentSpeed * dt;
playerGroup.position.z += moveDir.z * currentSpeed * dt;
playerGroup.rotation.y = Math.atan2(moveDir.x, moveDir.z);
const walkCycle = Math.sin(clock.getElapsedTime() * (isSprinting ? 16 : 10));
leftLeg.rotation.x = walkCycle * 0.6;
rightLeg.rotation.x = -walkCycle * 0.6;
} else {
leftLeg.rotation.x = 0;
rightLeg.rotation.x = 0;
}
// Jump & Gravity
if ((keys['Space']) && PlayerState.isGrounded) {
playerVelocity.y = PlayerState.jumpVelocity;
PlayerState.isGrounded = false;
}
playerVelocity.y -= gravity * dt;
playerGroup.position.y += playerVelocity.y * dt;
if (playerGroup.position.y <= 0) {
playerGroup.position.y = 0;
playerVelocity.y = 0;
PlayerState.isGrounded = true;
}
// Obstacle Collision
const playerRadius = 0.8;
const playerHeight = 2.8;
const pPos = playerGroup.position;
for (let obs of obstacles) {
const min = obs.box.min;
const max = obs.box.max;
if (pPos.x > min.x - playerRadius && pPos.x < max.x + playerRadius &&
pPos.z > min.z - playerRadius && pPos.z < max.z + playerRadius) {
if (pPos.y + playerHeight > min.y && pPos.y < max.y) {
if (pPos.y >= max.y - 0.5 && playerVelocity.y <= 0) {
pPos.y = max.y;
playerVelocity.y = 0;
PlayerState.isGrounded = true;
}
}
}
}
// Camera Orbit
const targetPos = playerGroup.position.clone().add(new THREE.Vector3(0, 2, 0));
const camOffset = new THREE.Vector3(
Math.sin(cameraYaw) * Math.cos(cameraPitch) * cameraDistance,
Math.sin(cameraPitch) * cameraDistance,
Math.cos(cameraYaw) * Math.cos(cameraPitch) * cameraDistance
);
camera.position.copy(targetPos).add(camOffset);
camera.lookAt(targetPos);
}
function animate() {
requestAnimationFrame(animate);
const dt = Math.min(clock.getDelta(), 0.1);
update(dt);
renderer.render(scene, camera);
}
animate();
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
</script>
</body>
</html>Game Source: MultiBlox Platform - Prototype v0.1
Creator: DriftBear16
Libraries: three
Complexity: complex (603 lines, 18.1 KB)
The full source code is displayed above on this page.
Remix Instructions
To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: multiblox-platform-prototype-v0-1-driftbear16" to link back to the original. Then publish at arcadelab.ai/publish.