🎮ArcadeLab

Bihar Open World — Chhath Ghat Edition

by PlasmaRanger55
152 lines17.6 KB🛠️ Three.js (3D graphics)
▶ Play
<!doctype html>
<html lang="hi">
<head>
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
<title>Bihar Open World — Chhath Ghat Edition</title>
<style>
*{box-sizing:border-box}html,body{margin:0;height:100%;overflow:hidden;background:#8bc8e9;font-family:system-ui,-apple-system,Arial;touch-action:none}
#hud{position:fixed;z-index:5;left:10px;top:10px;max-width:min(360px,calc(100vw - 20px));background:#10202ddd;color:white;padding:11px 13px;border-radius:13px;line-height:1.35;box-shadow:0 5px 22px #14233440}
#hud strong{letter-spacing:.5px}#status{color:#ffe4a0;font-weight:750}#help{font-size:11px;opacity:.9;margin-top:4px}
#buttons{position:fixed;z-index:5;left:12px;right:12px;bottom:max(12px,env(safe-area-inset-bottom));display:flex;justify-content:space-between;align-items:end;pointer-events:none}
.pad{display:grid;grid-template-columns:47px 47px 47px;grid-template-rows:47px 47px 47px;gap:4px;pointer-events:auto}
button{border:1px solid #ffffff99;background:#142b42d9;color:#fff;border-radius:12px;font-weight:800;touch-action:none;box-shadow:0 3px 9px #0003}
#up{grid-column:2}#left{grid-column:1;grid-row:2}#down{grid-column:2;grid-row:2}#right{grid-column:3;grid-row:2}
#actions{display:grid;grid-template-columns:repeat(2,minmax(66px,80px));gap:6px;pointer-events:auto}
#actions button{height:43px;font-size:11px}
#toast{position:fixed;z-index:8;bottom:160px;left:50%;transform:translateX(-50%);padding:9px 13px;border-radius:11px;background:#101820ed;color:white;max-width:90vw;text-align:center;display:none}
#view{position:fixed;z-index:5;right:12px;top:12px;background:#142b42dc;padding:9px 12px;font-size:12px}
#crosshair{position:fixed;z-index:2;left:50%;top:50%;width:6px;height:6px;border-radius:50%;background:#ffffffaa;transform:translate(-50%,-50%);pointer-events:none}
</style></head><body>
<div id="hud"><strong>BIHAR OPEN WORLD</strong><br><span id="status">छठ घाट की ओर चलें</span><div id="help">चलना: WASD / स्क्रीन बटन · E: गाड़ी या घर · V: 360° कैमरा · माउस/उंगली ड्रैग: कैमरा घुमाएँ</div></div>
<button id="view">CAMERA 360°</button><div id="crosshair"></div><div id="toast"></div>
<div id="buttons"><div class="pad"><button id="up">▲</button><button id="left">◀</button><button id="down">▼</button><button id="right">▶</button></div><div id="actions"><button id="use">E · ENTER</button><button id="cameraBtn">V · VIEW</button><button id="rotateBtn">360° SPIN</button><button id="exitBtn">EXIT CAR</button></div></div>
<script type="module">
import * as THREE from 'https://cdn.jsdelivr.net/npm/three@0.166.1/build/three.module.js';
const scene=new THREE.Scene();scene.background=new THREE.Color(0x9bcce8);scene.fog=new THREE.Fog(0x9bcce8,65,235);
const camera=new THREE.PerspectiveCamera(67,innerWidth/innerHeight,.1,500);
const renderer=new THREE.WebGLRenderer({antialias:true,powerPreference:'high-performance'});
renderer.setSize(innerWidth,innerHeight);renderer.setPixelRatio(Math.min(devicePixelRatio,1.65));renderer.shadowMap.enabled=true;renderer.shadowMap.type=THREE.PCFSoftShadowMap;renderer.toneMapping=THREE.ACESFilmicToneMapping;renderer.toneMappingExposure=1.1;document.body.appendChild(renderer.domElement);
scene.add(new THREE.HemisphereLight(0xdff4ff,0x6e543b,2.15));const sun=new THREE.DirectionalLight(0xffdfaa,3.0);sun.position.set(-40,60,30);sun.castShadow=true;sun.shadow.mapSize.set(1536,1536);sun.shadow.camera.left=-90;sun.shadow.camera.right=90;sun.shadow.camera.top=90;sun.shadow.camera.bottom=-90;scene.add(sun);
const M=(c,r=.82,metal=0)=>new THREE.MeshStandardMaterial({color:c,roughness:r,metalness:metal});
const mat={grass:M(0x65894c),road:M(0x363b40),pave:M(0xbdb5a4),sand:M(0xc6ac7b),water:M(0x398fb0,.22,.12),skin:M(0xb87950),hair:M(0x252429),shirt:M(0x2878a3),pants:M(0x30343b),glass:M(0x91cce1,.22,.25),rubber:M(0x202226),rim:M(0xbac3c9,.25,.65),lamp:M(0xffe9ae,.25),redlamp:M(0xff4638,.3),gold:M(0xe6b94f,.35,.45),cloth:M(0xff9d35),white:M(0xf3e9d5)};
function mesh(parent,geo,material,x=0,y=0,z=0){const o=new THREE.Mesh(geo,material);o.position.set(x,y,z);o.castShadow=true;o.receiveShadow=true;parent.add(o);return o}
function box(parent,x,y,z,sx,sy,sz,material,cast=true){const o=mesh(parent,new THREE.BoxGeometry(sx,sy,sz),material,x,y,z);o.castShadow=cast;return o}
function cyl(parent,x,y,z,rt,rb,h,material,seg=12){return mesh(parent,new THREE.CylinderGeometry(rt,rb,h,seg),material,x,y,z)}
function sphere(parent,x,y,z,r,material,seg=14){return mesh(parent,new THREE.SphereGeometry(r,seg,10),material,x,y,z)}
// World base and crossroad
box(scene,0,-.45,0,300,.7,300,mat.grass,false);
box(scene,0,-.08,0,13,.16,230,mat.road,false);box(scene,0,-.07,0,230,.16,13,mat.road,false);
for(let i=-105;i<=105;i+=12){box(scene,i,.015,0,.13,.02,5,mat.gold,false);box(scene,0,.015,i,5,.02,.13,mat.gold,false)}
box(scene,7.7,.02,0,2,.2,230,mat.pave,false);box(scene,-7.7,.02,0,2,.2,230,mat.pave,false);box(scene,0,.02,7.7,230,.2,2,mat.pave,false);box(scene,0,.02,-7.7,230,.2,2,mat.pave,false);
// River/ghat: water strip east side and descending steps
box(scene,72,-.1,0,70,.18,300,mat.water,false);
box(scene,33,-.04,0,12,.18,260,mat.sand,false);
for(let j=0;j<7;j++)box(scene,39+j*3,-.01-j*.025,0,3,.15,250,M(j%2?0xb8a27c:0xd1bc91),false);
// homes with door and interior room
const houses=[];
function house(x,z,w=8,d=8,h=5,color=0xe5d3ae){
 const g=new THREE.Group();g.position.set(x,0,z);scene.add(g);
 box(g,0,h/2,0,w,h,d,M(color));box(g,0,h+.13,0,w+.3,.25,d+.3,M(0x984a38));
 box(g,-w*.23,.92,d/2+.045,.8,1.84,.1,M(0x292827));box(g,w*.22,1.5,d/2+.05,.8,.65,.1,M(0x8bc5dc,.3));
 box(g,0,h+.4,d/2+.12,w*.76,.45,.13,mat.gold);
 const interior=new THREE.Group();interior.position.set(x,0,z);scene.add(interior);interior.visible=false;
 box(interior,0,.03,0,w*.78,.08,d*.78,M(0xb9a07a),false);box(interior,0,.62,-d*.18,w*.38,1.24,.28,M(0x946744),false);
 box(interior,-w*.2,.36,d*.12,.8,.7,.8,M(0x537c5a),false);box(interior,w*.2,.5,-d*.1,.7,1,.7,M(0x3c454e),false);
 houses.push({x,z,w,d,g,interior,inside:false});
}
const walls=[0xe6d5b5,0xb96e4d,0xd5e1d8,0x8eb1bf,0xd7bf88,0xb6c7a1];
for(let z=-84;z<=84;z+=20){house(-18,z,8,8,4.7,walls[Math.abs(z/20)%6|0]);house(18,z,8,8,5,walls[(Math.abs(z/20)+2)%6|0])}
for(let x=-84;x<=84;x+=20){if(Math.abs(x)<30)continue;house(x,-18,8,8,4.5,walls[Math.abs(x/20)%6|0]);house(x,18,8,8,5,walls[(Math.abs(x/20)+3)%6|0])}
function tree(x,z){cyl(scene,x,1.1,z,.2,.3,2.2,M(0x65472f));let crown=mesh(scene,new THREE.IcosahedronGeometry(1.3,1),M(0x34764a),x,2.7,z);crown.castShadow=true}
for(let z=-100;z<=100;z+=14){tree(-11,z);tree(11,z)}for(let x=-100;x<=100;x+=14){tree(x,-11);tree(x,11)}
// Human model with visible head, eyes, nose, ears, torso, separate arms/hands/legs/shoes; rotate freely.
function makeHuman(parent,opts={}){
 const g=new THREE.Group();parent.add(g);const skin=M(opts.skin||0xb87950),hair=M(opts.hair||0x282326),shirt=M(opts.shirt||0x2878a3),pants=M(opts.pants||0x30343b),shoe=M(0x26272b);
 // torso, neck, head
 const torso=mesh(g,new THREE.CapsuleGeometry(.29,.55,4,10),shirt,0,1.15,0);
 box(g,0,1.52,0,.38,.18,.29,shirt);
 cyl(g,0,1.53,0,.105,.105,.2,skin,10);
 sphere(g,0,1.82,0,.225,skin,18); // full face
 // hair cap and fringe
 const cap=mesh(g,new THREE.SphereGeometry(.232,16,10,0,Math.PI*2,0,Math.PI*.57),hair,0,1.91,0);
 // eyes on front (+Z)
 sphere(g,-.078,1.835,.194,.025,M(0xf8f4e8),8);sphere(g,.078,1.835,.194,.025,M(0xf8f4e8),8);
 sphere(g,-.078,1.833,.215,.012,M(0x211b18),8);sphere(g,.078,1.833,.215,.012,M(0x211b18),8);
 sphere(g,0,1.79,.225,.025,skin,8);
 // ears
 sphere(g,-.23,1.81,0,.045,skin,8);sphere(g,.23,1.81,0,.045,skin,8);
 // smile
 const smile=mesh(g,new THREE.TorusGeometry(.045,.009,5,12,Math.PI),M(0x5a2820),0,1.755,.207);smile.rotation.z=Math.PI;
 const limbs=[];
 for(const s of [-1,1]){
   const arm=new THREE.Group();arm.position.set(s*.34,1.38,0);g.add(arm);
   mesh(arm,new THREE.CapsuleGeometry(.09,.36,4,8),shirt,0,-.19,0);
   mesh(arm,new THREE.CapsuleGeometry(.075,.22,4,8),skin,0,-.48,.01);
   sphere(arm,0,-.6,.02,.085,skin,8);limbs.push({part:arm,kind:'arm',side:s});
   const leg=new THREE.Group();leg.position.set(s*.15,.67,0);g.add(leg);
   mesh(leg,new THREE.CapsuleGeometry(.105,.38,4,8),pants,0,-.2,0);
   mesh(leg,new THREE.CapsuleGeometry(.1,.12,4,8),shoe,0,-.44,.04);
   box(leg,0,-.48,.11,.19,.09,.25,shoe);limbs.push({part:leg,kind:'leg',side:s});
 }
 return {group:g,limbs,torso};
}
const player=new THREE.Group();scene.add(player);player.position.set(0,0,13);const playerModel=makeHuman(player,{shirt:0x246f9d,pants:0x292f39});
const npcs=[];
for(let i=0;i<18;i++){const p=new THREE.Group();scene.add(p);const h=makeHuman(p,{shirt:[0xc85d48,0x3d79a8,0x4b8055,0xd3aa4a,0x8b68a5,0xe6e0cf][i%6],pants:i%2?0x33353b:0x51463a});const x=(Math.random()>.5?1:-1)*(9+Math.random()*2),z=-95+Math.random()*190;p.position.set(x,0,z);npcs.push({p,h,x,z,phase:Math.random()*6});}
// Ghat decorations, diyas, puja baskets, flags, people and DJ speakers
for(let i=0;i<16;i++){const z=-45+i*6;box(scene,44,.25,z,1.1,.45,1.1,M(0xb88a55));cyl(scene,44,.55,z,.28,.34,.18,M(0x9d6136));for(let j=0;j<3;j++)sphere(scene,43.8+j*.2,.7,z,.12,M([0xe1a33a,0xe6c75c,0xb95d30][j]),8)}
for(let i=0;i<22;i++){const x=40+Math.random()*4,z=-55+Math.random()*110;const flame=mesh(scene,new THREE.ConeGeometry(.09,.32,7),M(0xffb32e,.3),x,.5,z);flame.userData.flame=true}
for(let z=-48;z<=48;z+=12){cyl(scene,47,1.8,z,.06,.07,3.6,M(0x6e4c2f));const flag=box(scene,47.6,2.65,z,.95,.55,.04,M(z%2?0xff8b32:0x2e9b64));flag.rotation.y=.1}
// DJ platform and speaker stacks (decorative)
box(scene,52,.45,-5,7,.8,5,M(0x393c42));box(scene,52,1,-5,6,.25,4,M(0x6c4c35));
for(const x of [49.5,54.5]){box(scene,x,2,-5,1.45,2.4,.9,M(0x17191d));for(let y of [1.35,2.15,2.65]){cyl(scene,x,y,-4.51,.32,.32,.08,M(0x5d6770),16);cyl(scene,x,y,-4.45,.19,.19,.04,M(0x141619),16)}}
const djSign=box(scene,52,3.5,-5,4,.55,.1,M(0xf2ae34)); // sign is intentionally plain 3D decoration
// Vehicles: stylized original models, not exact manufacturer replicas.
const vehicles=[];
function makeCar(name,x,z,kind,color){
 const root=new THREE.Group();root.position.set(x,0,z);scene.add(root);const w=kind==='sports'?1.9:2.1,l=kind==='sports'?4.2:4.5,paint=M(color,.3,.25),black=M(0x202329,.5),chrome=M(0xc4cbd0,.25,.7);
 box(root,0,.62,0,w,.48,l,paint);
 if(kind==='sports'){box(root,0,1.02,-.18,w*.72,.45,l*.45,paint);box(root,0,1.08,-.2,w*.65,.32,l*.4,mat.glass);box(root,0,.55,l*.48,w*.84,.12,.18,black)}
 else{box(root,0,1.18,-.15,w*.78,.9,l*.49,paint);box(root,0,1.23,-.18,w*.72,.66,l*.43,mat.glass);box(root,0,1.05,l*.31,w*.76,.42,.07,paint);if(kind==='offroad'){box(root,0,1.75,-.18,w*.8,.12,l*.52,black);box(root,0,.72,l*.55,.48,.48,.13,black)}if(kind==='suv')box(root,0,1.68,-.15,w*.82,.13,l*.52,black)}
 box(root,0,.57,l/2+.04,.75,.23,.09,black);for(const s of [-1,1]){box(root,s*w*.36,.72,l/2+.06,.3,.16,.08,mat.lamp);box(root,s*w*.36,.58,-l/2-.04,.26,.14,.07,mat.redlamp)}
 box(root,0,.34,l/2+.07,w*.92,.13,.16,chrome);const wheels=[];
 for(const xx of [-w/2-.03,w/2+.03])for(const zz of [-l*.31,l*.31]){const wh=mesh(root,new THREE.CylinderGeometry(kind==='offroad'?.43:.36,kind==='offroad'?.43:.36,.22,18),mat.rubber,xx,.38,zz);wh.rotation.z=Math.PI/2;wheels.push(wh);const hub=mesh(root,new THREE.CylinderGeometry(.19,.19,.235,12),chrome,xx,.38,zz);hub.rotation.z=Math.PI/2}
 // two front seats, dashboard and steering wheel
 for(let zz of [-.55,.35])for(let xx of [-.48,.48])box(root,xx,.78,zz,.38,.28,.38,M(0x3d4249));
 box(root,0,.98,l*.08,w*.65,.12,.35,M(0x272b31));const wheel=mesh(root,new THREE.TorusGeometry(.18,.035,8,18),black,-.48,1.04,l*.14);wheel.rotation.x=.9;
 vehicles.push({name,root,wheels,kind,speed:0,occupied:false});}
makeCar('Sports Coupe',-3,-3,'sports',0xd42d32);makeCar('Off-road 4x4',4,-3,'offroad',0x4e8051);makeCar('Luxury SUV',-3,4,'suv',0x242a31);makeCar('City SUV',4,4,'suv',0xc9cbd0);
let occupied=null,yaw=0,pitch=.12,view360=false;const keys={};let pointer=null;const status=document.getElementById('status'),toast=document.getElementById('toast');
function msg(t){toast.textContent=t;toast.style.display='block';clearTimeout(msg.timer);msg.timer=setTimeout(()=>toast.style.display='none',2200)}
function action(){
 if(occupied){occupied.occupied=false;player.visible=true;player.position.set(occupied.root.position.x-2,0,occupied.root.position.z);occupied.speed=0;occupied=null;msg('आप गाड़ी से बाहर आ गए।');return}
 let vnear=null,vd=5;for(const v of vehicles){let d=v.root.position.distanceTo(player.position);if(d<vd){vd=d;vnear=v}}
 if(vnear){occupied=vnear;vnear.occupied=true;player.visible=false;vnear.speed=0;msg(vnear.name+' में बैठ गए! WASD से चलाएँ।');return}
 const b=houses.find(b=>Math.hypot(player.position.x-b.x,player.position.z-b.z)<6);
 if(b){b.inside=!b.inside;b.interior.visible=b.inside;if(b.inside){player.position.set(b.x+1,0,b.z);msg('घर के अंदर — फर्नीचर वाला प्रोटोटाइप रूम।')}else msg('घर से बाहर आ गए।');return}
 msg('गाड़ी या घर के पास जाएँ, फिर ENTER दबाएँ।');
}
function toggleView(){view360=!view360;msg(view360?'360° कैमरा मोड चालू':'सामान्य कैमरा मोड');}
addEventListener('keydown',e=>{keys[e.key.toLowerCase()]=true;if(e.key.toLowerCase()==='e')action();if(e.key.toLowerCase()==='v')toggleView()});addEventListener('keyup',e=>keys[e.key.toLowerCase()]=false);
renderer.domElement.addEventListener('pointerdown',e=>{pointer={x:e.clientX,y:e.clientY,id:e.pointerId};renderer.domElement.setPointerCapture(e.pointerId)});
renderer.domElement.addEventListener('pointermove',e=>{if(pointer&&pointer.id===e.pointerId){yaw-=(e.clientX-pointer.x)*.006;pitch=THREE.MathUtils.clamp(pitch+(e.clientY-pointer.y)*.003,-.15,.7);pointer.x=e.clientX;pointer.y=e.clientY}});
renderer.domElement.addEventListener('pointerup',()=>pointer=null);
function bind(id,key){const b=document.getElementById(id);b.addEventListener('pointerdown',e=>{e.preventDefault();keys[key]=true});['pointerup','pointercancel','pointerleave'].forEach(ev=>b.addEventListener(ev,()=>keys[key]=false))}
bind('up','w');bind('down','s');bind('left','a');bind('right','d');
document.getElementById('use').onclick=action;document.getElementById('exitBtn').onclick=()=>{if(occupied)action();else msg('पहले गाड़ी में बैठें।')};
document.getElementById('cameraBtn').onclick=toggleView;document.getElementById('view').onclick=toggleView;document.getElementById('rotateBtn').onclick=()=>{yaw+=Math.PI*2;msg('360° कैमरा घुमाया');};
const clock=new THREE.Clock();
function animate(){requestAnimationFrame(animate);const dt=Math.min(clock.getDelta(),.035),now=performance.now();
 if(occupied){const v=occupied,th=(keys.w||keys.arrowup?1:0)-(keys.s||keys.arrowdown?1:0),st=(keys.d||keys.arrowright?1:0)-(keys.a||keys.arrowleft?1:0);v.speed+=th*dt*9;v.speed*=Math.pow(.985,dt*60);v.speed=THREE.MathUtils.clamp(v.speed,-6,17);v.root.rotation.y+=st*dt*(.55+Math.min(Math.abs(v.speed)/7,1.5))*Math.sign(v.speed||1);v.root.position.x+=Math.sin(v.root.rotation.y)*v.speed*dt;v.root.position.z+=Math.cos(v.root.rotation.y)*v.speed*dt;v.root.position.x=THREE.MathUtils.clamp(v.root.position.x,-115,115);v.root.position.z=THREE.MathUtils.clamp(v.root.position.z,-115,115);v.wheels.forEach(w=>w.rotation.x+=v.speed*dt*1.6);player.position.copy(v.root.position);if(view360)yaw+=dt*.25;}
 else{const f=(keys.w||keys.arrowup?1:0)-(keys.s||keys.arrowdown?1:0),s=(keys.d||keys.arrowright?1:0)-(keys.a||keys.arrowleft?1:0);if(Math.abs(f)+Math.abs(s)>0){player.position.x+=Math.sin(yaw)*f*dt*5+Math.cos(yaw)*s*dt*5;player.position.z+=Math.cos(yaw)*f*dt*5-Math.sin(yaw)*s*dt*5;player.rotation.y=yaw;playerModel.limbs.forEach(l=>{const swing=Math.sin(now*.012)*(Math.abs(f)+Math.abs(s))*.42;l.part.rotation.x=l.kind==='leg'?swing*l.side:-swing*l.side});}else playerModel.limbs.forEach(l=>l.part.rotation.x*=.8);player.position.x=THREE.MathUtils.clamp(player.position.x,-120,120);player.position.z=THREE.MathUtils.clamp(player.position.z,-120,120);if(view360)yaw+=dt*.35;}
 npcs.forEach(n=>{n.p.position.x=n.x+Math.sin(now*.00035+n.phase)*.5;n.p.position.z=n.z+Math.cos(now*.0003+n.phase)*.4;n.h.limbs.forEach(l=>l.part.rotation.x=Math.sin(now*.004+n.phase)*(l.kind==='leg'?.12:.08)*l.side)});
 const focus=occupied?occupied.root.position:player.position;let off;if(view360){off=new THREE.Vector3(Math.sin(now*.0006)*9,4+pitch*3,Math.cos(now*.0006)*9)}else off=new THREE.Vector3(Math.sin(yaw)*8,3.25+pitch*4,Math.cos(yaw)*8);
 camera.position.lerp(new THREE.Vector3(focus.x+off.x,focus.y+off.y,focus.z+off.z),.12);camera.lookAt(focus.x,focus.y+1,focus.z);
 if(occupied)status.textContent='ड्राइविंग: '+occupied.name+' · गति '+Math.round(Math.abs(occupied.speed)*8)+' (गेम यूनिट)';
 else if(vehicles.some(v=>v.root.position.distanceTo(player.position)<5))status.textContent='E दबाएँ: गाड़ी में बैठें';
 else if(houses.some(b=>Math.hypot(player.position.x-b.x,player.position.z-b.z)<6))status.textContent='E दबाएँ: घर के अंदर जाएँ';
 else if(Math.hypot(player.position.x-46,player.position.z)<20)status.textContent='छठ घाट · दीये · पूजा सजावट · DJ मंच';
 else status.textContent='गाँव और शहर में घूमिए';
 renderer.render(scene,camera)}
animate();addEventListener('resize',()=>{camera.aspect=innerWidth/innerHeight;camera.updateProjectionMatrix();renderer.setSize(innerWidth,innerHeight);});
</script></body></html>

Game Source: Bihar Open World — Chhath Ghat Edition

Creator: PlasmaRanger55

Libraries: three

Complexity: moderate (152 lines, 17.6 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: bihar-open-world-chhath-ghat-edition-plasmaranger55" to link back to the original. Then publish at arcadelab.ai/publish.