MultiBlox
by DriftBear161764 lines25.1 KB
<!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</title>
<style>
*{
box-sizing:border-box;
}
html,body{
margin:0;
width:100%;
height:100%;
overflow:hidden;
background:#87ceeb;
font-family:Arial,sans-serif;
}
canvas{
display:block;
}
#ui{
position:fixed;
top:10px;
left:10px;
z-index:20;
color:white;
background:rgba(0,0,0,.65);
border:1px solid rgba(255,255,255,.25);
border-radius:12px;
padding:12px;
min-width:190px;
pointer-events:none;
}
.logo{
font-size:23px;
font-weight:bold;
margin-bottom:5px;
}
.stat{
font-size:14px;
margin:3px 0;
}
#message{
color:#8cff8c;
margin-top:6px;
}
#crosshair{
position:fixed;
left:50%;
top:50%;
transform:translate(-50%,-50%);
z-index:15;
color:white;
font-size:28px;
font-weight:bold;
pointer-events:none;
text-shadow:0 0 4px black;
}
#hotbar{
position:fixed;
bottom:20px;
left:50%;
transform:translateX(-50%);
z-index:20;
display:flex;
gap:5px;
}
.slot{
width:58px;
height:58px;
border:3px solid #555;
background:rgba(0,0,0,.65);
color:white;
border-radius:7px;
display:flex;
align-items:center;
justify-content:center;
flex-direction:column;
font-size:12px;
}
.slot.selected{
border-color:#fff;
box-shadow:0 0 10px white;
}
.blockIcon{
width:25px;
height:25px;
margin-bottom:3px;
}
#help{
position:fixed;
right:10px;
top:10px;
z-index:20;
color:white;
background:rgba(0,0,0,.55);
padding:10px;
border-radius:10px;
font-size:13px;
}
#mobile{
display:none;
position:fixed;
bottom:90px;
left:15px;
z-index:30;
}
.mbtn{
width:55px;
height:55px;
margin:3px;
border:0;
border-radius:12px;
background:rgba(0,0,0,.55);
color:white;
font-size:22px;
}
@media(max-width:700px){
#help{
display:none;
}
#mobile{
display:block;
}
}
</style>
</head>
<body>
<div id="ui">
<div class="logo">🌍 MultiBlox</div>
<div class="stat">
💰 MultiBux:
<span id="bux">100</span>
</div>
<div class="stat">
🪙 Coins:
<span id="coins">0</span>
</div>
<div class="stat">
❤️ Health:
<span id="health">100</span>
</div>
<div class="stat">
🧱 Blocks:
<span id="blocks">0</span>
</div>
<div id="message">
World loading...
</div>
</div>
<div id="help">
<b>🎮 Controls</b><br>
WASD — Move<br>
Mouse — Look<br>
Space — Jump<br>
Shift — Sprint<br>
Left Click — Break<br>
Right Click — Place<br>
1–5 — Select Block<br>
P — Save
</div>
<div id="crosshair">+</div>
<div id="hotbar"></div>
<div id="mobile">
<button class="mbtn" id="up">▲</button><br>
<button class="mbtn" id="left">◀</button>
<button class="mbtn" id="down">▼</button>
<button class="mbtn" id="right">▶</button>
</div>
<script type="module">
import * as THREE from "three";
/* =====================================================
MULTIBLOX ENGINE
===================================================== */
const scene =
new THREE.Scene();
scene.background =
new THREE.Color(0x87ceeb);
/* =====================================================
CAMERA
===================================================== */
const camera =
new THREE.PerspectiveCamera(
75,
innerWidth / innerHeight,
0.1,
1000
);
camera.position.set(
0,
4,
8
);
/* =====================================================
RENDERER
===================================================== */
const renderer =
new THREE.WebGLRenderer({
antialias:true
});
renderer.setPixelRatio(
Math.min(devicePixelRatio,2)
);
renderer.setSize(
innerWidth,
innerHeight
);
document.body.appendChild(
renderer.domElement
);
/* =====================================================
LIGHTING
===================================================== */
const sun =
new THREE.DirectionalLight(
0xffffff,
2
);
sun.position.set(
40,
80,
30
);
scene.add(sun);
scene.add(
new THREE.HemisphereLight(
0x87ceeb,
0x446644,
0.8
)
);
/* =====================================================
BLOCK DEFINITIONS
===================================================== */
const BLOCKS = {
grass:{
color:0x55aa33,
name:"Grass"
},
dirt:{
color:0x8b5a2b,
name:"Dirt"
},
stone:{
color:0x777777,
name:"Stone"
},
wood:{
color:0x8b4513,
name:"Wood"
},
leaves:{
color:0x228b22,
name:"Leaves"
}
};
/* =====================================================
BLOCK MATERIALS
===================================================== */
const materials={};
for(const type in BLOCKS){
materials[type]=
new THREE.MeshStandardMaterial({
color:BLOCKS[type].color
});
}
/* =====================================================
WORLD
===================================================== */
const world =
new Map();
const blockGroup =
new THREE.Group();
scene.add(blockGroup);
const SIZE=32;
function key(x,y,z){
return x+","+y+","+z;
}
function addBlock(
x,
y,
z,
type
){
const k=
key(x,y,z);
if(world.has(k))
return;
const mesh=
new THREE.Mesh(
new THREE.BoxGeometry(
1,1,1
),
materials[type]
);
mesh.position.set(
x,
y,
z
);
mesh.userData.type=
type;
mesh.userData.block=
true;
blockGroup.add(mesh);
world.set(k,mesh);
}
function removeBlock(
x,
y,
z
){
const k=
key(x,y,z);
const mesh=
world.get(k);
if(!mesh)
return null;
blockGroup.remove(mesh);
world.delete(k);
mesh.geometry.dispose();
return mesh;
}
/* =====================================================
TERRAIN GENERATION
===================================================== */
function terrainHeight(x,z){
const a=
Math.sin(x*0.25)*1.4;
const b=
Math.cos(z*0.22)*1.2;
const c=
Math.sin((x+z)*0.12)*1.5;
return Math.max(
1,
Math.floor(
3+a+b+c
)
);
}
for(
let x=-SIZE;
x<=SIZE;
x++
){
for(
let z=-SIZE;
z<=SIZE;
z++
){
const h=
terrainHeight(x,z);
for(
let y=0;
y<h;
y++
){
let type="dirt";
if(y===h-1)
type="grass";
else if(y<1)
type="stone";
addBlock(
x,
y,
z,
type
);
}
}
}
/* =====================================================
TREES
===================================================== */
function createTree(x,z){
const h=
terrainHeight(x,z);
for(
let y=h;
y<h+4;
y++
){
addBlock(
x,
y,
z,
"wood"
);
}
for(
let dx=-2;
dx<=2;
dx++
){
for(
let dz=-2;
dz<=2;
dz++
){
for(
let dy=2;
dy<=4;
dy++
){
if(
Math.abs(dx)+
Math.abs(dz)+
Math.abs(dy-3)
<=4
){
addBlock(
x+dx,
h+dy,
z+dz,
"leaves"
);
}
}
}
}
}
for(
let i=0;
i<55;
i++
){
const x=
Math.floor(
Math.random()*
(SIZE*2-4)
)-SIZE+2;
const z=
Math.floor(
Math.random()*
(SIZE*2-4)
)-SIZE+2;
createTree(x,z);
}
/* =====================================================
WATER
===================================================== */
const waterMaterial=
new THREE.MeshStandardMaterial({
color:0x168cff,
transparent:true,
opacity:.55,
roughness:.1
});
const water=
new THREE.Mesh(
new THREE.PlaneGeometry(
SIZE*2,
SIZE*2
),
waterMaterial
);
water.rotation.x=
-Math.PI/2;
water.position.y=
1.25;
scene.add(water);
/* =====================================================
PLAYER
===================================================== */
const player=
new THREE.Object3D();
player.position.set(
0,
terrainHeight(0,0)+1.8,
5
);
scene.add(player);
const playerBody=
new THREE.Mesh(
new THREE.BoxGeometry(
.8,
1.7,
.8
),
new THREE.MeshStandardMaterial({
color:0xff4444
})
);
player.add(playerBody);
playerBody.position.y=
-0.8;
/* =====================================================
PLAYER DATA
===================================================== */
let health=100;
let bux=100;
let coins=0;
let selected=0;
let inventory={
grass:20,
dirt:20,
stone:10,
wood:10,
leaves:5
};
const blockTypes=
Object.keys(BLOCKS);
/* =====================================================
HOTBAR
===================================================== */
const hotbar=
document.getElementById(
"hotbar"
);
function updateHotbar(){
hotbar.innerHTML="";
blockTypes.forEach(
(type,i)=>{
const slot=
document.createElement(
"div"
);
slot.className=
"slot";
if(i===selected)
slot.classList.add(
"selected"
);
const icon=
document.createElement(
"div"
);
icon.className=
"blockIcon";
icon.style.background=
"#"+
BLOCKS[type].color
.toString(16)
.padStart(6,"0");
const text=
document.createElement(
"div"
);
text.textContent=
(i+1)+
" "+
inventory[type];
slot.appendChild(icon);
slot.appendChild(text);
hotbar.appendChild(slot);
}
);
}
updateHotbar();
/* =====================================================
UI
===================================================== */
function updateUI(){
document.getElementById(
"bux"
).textContent=bux;
document.getElementById(
"coins"
).textContent=coins;
document.getElementById(
"health"
).textContent=health;
let total=0;
for(const type in inventory)
total+=inventory[type];
document.getElementById(
"blocks"
).textContent=total;
updateHotbar();
}
function message(text){
document.getElementById(
"message"
).textContent=text;
}
/* =====================================================
KEYBOARD
===================================================== */
const keys={};
addEventListener(
"keydown",
e=>{
keys[
e.key.toLowerCase()
]=true;
const n=
Number(e.key);
if(
n>=1 &&
n<=5
){
selected=n-1;
updateUI();
}
if(
e.key.toLowerCase()==="p"
){
saveGame();
}
}
);
addEventListener(
"keyup",
e=>{
keys[
e.key.toLowerCase()
]=false;
}
);
/* =====================================================
MOUSE LOOK
===================================================== */
let yaw=0;
let pitch=0;
let mouseLocked=false;
renderer.addEventListener(
"click",
()=>{
if(
!mouseLocked
){
renderer
.requestPointerLock();
}
}
);
document.addEventListener(
"pointerlockchange",
()=>{
mouseLocked=
document.pointerLockElement===
renderer;
}
);
document.addEventListener(
"mousemove",
e=>{
if(!mouseLocked)
return;
yaw-=e.movementX*.002;
pitch-=e.movementY*.002;
pitch=
Math.max(
-1.4,
Math.min(
1.4,
pitch
)
);
}
);
/* =====================================================
RAYCASTER
===================================================== */
const raycaster=
new THREE.Raycaster();
const center=
new THREE.Vector2(0,0);
function targetedBlock(){
raycaster.setFromCamera(
center,
camera
);
const hits=
raycaster.intersectObjects(
blockGroup.children
);
if(
hits.length===0
)
return null;
return hits[0];
}
/* =====================================================
BREAK BLOCK
===================================================== */
function breakBlock(){
const hit=
targetedBlock();
if(!hit)
return;
const block=
hit.object;
const p=
block.position;
const type=
block.userData.type;
/*
Don't allow the player to
destroy the world below
the minimum layer.
*/
if(p.y<=0)
return;
removeBlock(
p.x,
p.y,
p.z
);
inventory[type] =
(inventory[type]||0)+1;
coins+=1;
bux+=1;
updateUI();
message(
"⛏️ Broke "+BLOCKS[type].name+
" +1 MultiBux"
);
}
/* =====================================================
PLACE BLOCK
===================================================== */
function placeBlock(){
const hit=
targetedBlock();
if(!hit)
return;
const type=
blockTypes[selected];
if(
inventory[type]<=0
){
message(
"❌ No "+BLOCKS[type].name+
" blocks!"
);
return;
}
const normal=
hit.face.normal;
const p=
hit.object.position;
const x=
Math.round(
p.x+normal.x
);
const y=
Math.round(
p.y+normal.y
);
const z=
Math.round(
p.z+normal.z
);
const k=
key(x,y,z);
if(world.has(k))
return;
/*
Prevent placing a block
directly inside the player.
*/
if(
Math.abs(
x-player.position.x
)<1 &&
Math.abs(
z-player.position.z
)<1 &&
Math.abs(
y-player.position.y
)<2
){
message(
"❌ Can't place block here"
);
return;
}
addBlock(
x,
y,
z,
type
);
inventory[type]--;
updateUI();
message(
"🧱 Placed "+
BLOCKS[type].name
);
}
/* =====================================================
MOUSE BUTTONS
===================================================== */
addEventListener(
"mousedown",
e=>{
if(e.button===0)
breakBlock();
if(e.button===2)
placeBlock();
}
);
addEventListener(
"contextmenu",
e=>{
e.preventDefault();
}
);
/* =====================================================
MOVEMENT
===================================================== */
let velocityY=0;
let onGround=false;
function updatePlayer(){
let speed=
keys.shift ? .22 : .12;
const forward=
new THREE.Vector3(
Math.sin(yaw),
0,
Math.cos(yaw)
);
const right=
new THREE.Vector3(
Math.cos(yaw),
0,
-Math.sin(yaw)
);
if(keys.w)
player.position.addScaledVector(
forward,
-speed
);
if(keys.s)
player.position.addScaledVector(
forward,
speed
);
if(keys.a)
player.position.addScaledVector(
right,
-speed
);
if(keys.d)
player.position.addScaledVector(
right,
speed
);
if(
keys[" "] &&
onGround
){
velocityY=.25;
onGround=false;
}
velocityY-=.012;
player.position.y+=
velocityY;
const ground=
terrainHeight(
Math.round(
player.position.x
),
Math.round(
player.position.z
)
)+1.8;
if(
player.position.y<
ground
){
player.position.y=
ground;
velocityY=0;
onGround=true;
}
player.rotation.y=
yaw;
}
/* =====================================================
CAMERA
===================================================== */
function updateCamera(){
const distance=7;
const height=3;
const target=
new THREE.Vector3(
player.position.x,
player.position.y+1,
player.position.z
);
camera.position.set(
target.x+
Math.sin(yaw)*
Math.cos(pitch)*
distance,
target.y+
Math.sin(pitch)*
distance+
height,
target.z+
Math.cos(yaw)*
Math.cos(pitch)*
distance
);
camera.lookAt(
target
);
}
/* =====================================================
COINS
===================================================== */
const coinGroup=
new THREE.Group();
scene.add(coinGroup);
function createCoin(x,y,z){
const coin=
new THREE.Mesh(
new THREE.CylinderGeometry(
.25,
.25,
.08,
16
),
new THREE.MeshStandardMaterial({
color:0xffd700,
metalness:.7,
roughness:.2
})
);
coin.rotation.x=
Math.PI/2;
coin.position.set(
x,y,z
);
coin.userData.coin=true;
coinGroup.add(
coin
);
}
for(
let i=0;
i<25;
i++
){
const x=
Math.floor(
Math.random()*50
)-25;
const z=
Math.floor(
Math.random()*50
)-25;
createCoin(
x,
terrainHeight(x,z)+1,
z
);
}
function updateCoins(){
for(
let i=coinGroup.children.length-1;
i>=0;
i--
){
const coin=
coinGroup.children[i];
coin.rotation.z+=.04;
const dx=
coin.position.x-
player.position.x;
const dz=
coin.position.z-
player.position.z;
if(
dx*dx+dz*dz<
1.5
){
coinGroup.remove(
coin
);
coins++;
bux+=2;
updateUI();
message(
"🪙 Coin collected! +2 MultiBux"
);
}
}
}
/* =====================================================
SAVE / LOAD
===================================================== */
function saveGame(){
const data={
bux:bux,
coins:coins,
health:health,
inventory:inventory,
position:{
x:player.position.x,
y:player.position.y,
z:player.position.z
}
};
localStorage.setItem(
"multiblox_save",
JSON.stringify(data)
);
message(
"💾 MultiBlox saved!"
);
}
function loadGame(){
try{
const raw=
localStorage.getItem(
"multiblox_save"
);
if(!raw)
return;
const data=
JSON.parse(raw);
bux=
data.bux??100;
coins=
data.coins??0;
health=
data.health??100;
inventory=
data.inventory||
inventory;
if(data.position){
player.position.set(
data.position.x,
data.position.y,
data.position.z
);
}
}catch(e){
console.log(
"Save could not be loaded."
);
}
}
loadGame();
updateUI();
/* =====================================================
DAY / NIGHT
===================================================== */
let worldTime=0;
function updateDayNight(){
worldTime+=.0007;
const sunlight=
Math.sin(worldTime)*
.5+.5;
sun.intensity=
.25+
sunlight*2;
const sky=
new THREE.Color();
sky.setHSL(
.55,
.65,
.45+
sunlight*.15
);
scene.background=
sky;
}
/* =====================================================
WATER ANIMATION
===================================================== */
function updateWater(){
water.position.y=
1.25+
Math.sin(
performance.now()*.001
)*.03;
}
/* =====================================================
MOBILE BUTTONS
===================================================== */
function mobileKey(
button,
key
){
const element=
document.getElementById(
button
);
element.addEventListener(
"touchstart",
e=>{
e.preventDefault();
keys[key]=true;
}
);
element.addEventListener(
"touchend",
e=>{
e.preventDefault();
keys[key]=false;
}
);
}
mobileKey(
"up",
"w"
);
mobileKey(
"down",
"s"
);
mobileKey(
"left",
"a"
);
mobileKey(
"right",
"d"
);
/* =====================================================
GAME LOOP
===================================================== */
function animate(){
requestAnimationFrame(
animate
);
updatePlayer();
updateCamera();
updateCoins();
updateDayNight();
updateWater();
renderer.render(
scene,
camera
);
}
animate();
/* =====================================================
RESIZE
===================================================== */
addEventListener(
"resize",
()=>{
camera.aspect=
innerWidth/
innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(
innerWidth,
innerHeight
);
}
);
message(
"🟢 World ready! Break blocks with left click."
);
</script>
</body>
</html>Game Source: MultiBlox
Creator: DriftBear16
Libraries: none
Complexity: complex (1764 lines, 25.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-driftbear16" to link back to the original. Then publish at arcadelab.ai/publish.