🎮ArcadeLab

Generic 3D Game

by ThunderOwl74
45 lines8.4 KB🛠️ Three.js (3D graphics)
▶ Play
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Generic 3D Game</title>
<style>
html,body{margin:0;height:100%;overflow:hidden;background:#020617;color:#e5eefc;font-family:Inter,system-ui,Segoe UI,sans-serif}#game{position:fixed;inset:0}.screen{position:fixed;inset:0;display:flex;align-items:center;justify-content:center;background:rgba(3,7,18,.75);backdrop-filter:blur(8px)}.panel{width:min(560px,calc(100vw - 32px));border:1px solid rgba(148,163,184,.25);border-radius:18px;background:rgba(15,23,42,.92);padding:24px;box-shadow:0 24px 80px rgba(0,0,0,.45)}button,select{border:0;border-radius:12px;padding:12px 16px;font-weight:700}button{background:#4f46e5;color:white;cursor:pointer}select{background:#020617;color:white;border:1px solid #334155}.row{display:flex;gap:10px;flex-wrap:wrap}.hud{position:fixed;top:12px;left:12px;right:12px;display:flex;justify-content:space-between;pointer-events:none}.pill{background:rgba(15,23,42,.76);border:1px solid rgba(148,163,184,.22);border-radius:999px;padding:8px 12px;font-size:13px}.error{color:#fecaca}
</style>
</head>
<body>
<div id="game"></div>
<div class="hud"><div class="pill" id="hud-left">Score 0</div><div class="pill" id="hud-right">Wave 1</div></div>
<div class="screen" id="menu"><div class="panel"><h1>Generic 3D Game</h1><p id="menu-copy">Adapt this shell to the requested game. It already has scene setup, player, actors, camera, raycasting hooks, states, settings, and pause flow.</p><div class="row"><button id="play">Play</button><select id="difficulty"><option value="1">Normal</option><option value="1.35">Hard</option><option value=".75">Relaxed</option></select></div></div></div>
<script src="https://unpkg.com/three@0.160.0/build/three.min.js"></script>
<script>
window.onload=function(){
var mount=document.getElementById('game'),menu=document.getElementById('menu'),copy=document.getElementById('menu-copy'),hudLeft=document.getElementById('hud-left'),hudRight=document.getElementById('hud-right');
if(!window.THREE){copy.className='error';copy.textContent='Three.js failed to load. Check your connection and reload.';return}
var renderer,scene,camera,clock,raycaster;
try{renderer=new THREE.WebGLRenderer({antialias:true,alpha:false});renderer.setPixelRatio(Math.min(2,window.devicePixelRatio||1));renderer.shadowMap.enabled=true;mount.appendChild(renderer.domElement)}catch(e){copy.className='error';copy.textContent='WebGL is not available in this browser.';return}
scene=new THREE.Scene();scene.background=new THREE.Color(0x07111f);scene.fog=new THREE.Fog(0x07111f,35,110);camera=new THREE.PerspectiveCamera(60,1,.1,200);clock=new THREE.Clock();raycaster=new THREE.Raycaster();
var Game={state:'start',score:0,wave:1,lives:3,settings:{difficulty:1},keys:{},pointer:{x:0,y:0,down:false},player:null,actors:[],particles:[],worldSize:58,follow:new THREE.Vector3(),tmp:new THREE.Vector3()};
scene.add(new THREE.HemisphereLight(0xdbeafe,0x111827,1.3));var sun=new THREE.DirectionalLight(0xffffff,1.4);sun.position.set(12,20,8);sun.castShadow=true;scene.add(sun);
var floorGeo=new THREE.PlaneGeometry(Game.worldSize,Game.worldSize,32,32),floorMat=new THREE.MeshStandardMaterial({color:0x0f172a,roughness:.9,metalness:.05});var floor=new THREE.Mesh(floorGeo,floorMat);floor.rotation.x=-Math.PI/2;floor.receiveShadow=true;scene.add(floor);var grid=new THREE.GridHelper(Game.worldSize,29,0x334155,0x1e293b);scene.add(grid);
function makeActor(kind,x,z,color,scale){var group=new THREE.Group();group.position.set(x,0,z);var body=new THREE.Mesh(new THREE.BoxGeometry(1,1,1),new THREE.MeshStandardMaterial({color:color||0x38bdf8,roughness:.55,metalness:.05}));body.position.y=.5;body.scale.set(scale||1,scale||1,scale||1);body.castShadow=true;group.add(body);group.userData={kind:kind,vel:new THREE.Vector3(),speed:8,radius:.75,life:1,value:1,cooldown:0,body:body};scene.add(group);Game.actors.push(group);return group}
function burst(pos,color,count){for(var i=0;i<count;i++){var m=new THREE.Mesh(new THREE.SphereGeometry(.09,8,8),new THREE.MeshBasicMaterial({color:color||0xffffff}));m.position.copy(pos);m.userData={vel:new THREE.Vector3((Math.random()-.5)*8,Math.random()*5,(Math.random()-.5)*8),age:0,ttl:.6+Math.random()*.5};scene.add(m);Game.particles.push(m)}}
function reset(){Game.state='playing';Game.score=0;Game.wave=1;Game.lives=3;Game.actors.forEach(function(a){scene.remove(a)});Game.particles.forEach(function(p){scene.remove(p)});Game.actors=[];Game.particles=[];Game.player=makeActor('player',0,0,0x22d3ee,1.15);Game.player.userData.speed=10;spawnWave();menu.style.display='none'}
function spawnWave(){var n=5+Game.wave*2;for(var i=0;i<n;i++){var angle=Math.random()*Math.PI*2,rad=14+Math.random()*15;var enemy=makeActor('enemy',Math.cos(angle)*rad,Math.sin(angle)*rad,0xf97316,.9);enemy.userData.speed=(2.5+Math.random()*2)*Game.settings.difficulty}for(var j=0;j<4;j++){makeActor('pickup',(Math.random()-.5)*42,(Math.random()-.5)*42,0x84cc16,.65).userData.kind='pickup'}}
function overlay(title,body,button){Game.state=title==='Paused'?'paused':Game.state;menu.style.display='flex';menu.querySelector('h1').textContent=title;copy.textContent=body;copy.className='';document.getElementById('play').textContent=button||'Play'}
function update(dt){var p=Game.player;if(!p)return;var ux=(Game.keys.d||Game.keys.ArrowRight?1:0)-(Game.keys.a||Game.keys.ArrowLeft?1:0),uz=(Game.keys.s||Game.keys.ArrowDown?1:0)-(Game.keys.w||Game.keys.ArrowUp?1:0),len=Math.hypot(ux,uz)||1;p.position.x+=ux/len*p.userData.speed*dt;p.position.z+=uz/len*p.userData.speed*dt;p.position.x=Math.max(-27,Math.min(27,p.position.x));p.position.z=Math.max(-27,Math.min(27,p.position.z));p.rotation.y=Math.atan2(ux,uz);Game.actors.forEach(function(a){var d=a.userData;if(a===p)return;if(d.kind==='enemy'){Game.tmp.copy(p.position).sub(a.position);var l=Game.tmp.length()||1;a.position.addScaledVector(Game.tmp,l?d.speed*dt/l:0);a.lookAt(p.position.x,.5,p.position.z);if(a.position.distanceTo(p.position)<1.35){Game.lives--;burst(p.position,0xef4444,18);a.position.set((Math.random()-.5)*48,0,(Math.random()-.5)*48);if(Game.lives<=0)overlay('Game Over','You were caught. Restart and change your route.','Restart')}}else if(d.kind==='pickup'&&a.position.distanceTo(p.position)<1.25){Game.score+=d.value;burst(a.position,0x84cc16,12);scene.remove(a);a.userData.dead=true;if(Game.score>=Game.wave*6){Game.wave++;spawnWave()}}});Game.actors=Game.actors.filter(function(a){return!a.userData.dead});Game.particles.forEach(function(o){o.userData.age+=dt;o.position.addScaledVector(o.userData.vel,dt);o.userData.vel.y-=7*dt;o.material.opacity=1-o.userData.age/o.userData.ttl;o.material.transparent=true});Game.particles=Game.particles.filter(function(o){if(o.userData.age<o.userData.ttl)return true;scene.remove(o);return false});Game.follow.lerp(new THREE.Vector3(p.position.x+10,12,p.position.z+14),.08);camera.position.copy(Game.follow);camera.lookAt(p.position.x,0,p.position.z);hudLeft.textContent='Score '+Game.score+'  Lives '+Game.lives;hudRight.textContent='Wave '+Game.wave}
function loop(){var dt=Math.min(.033,clock.getDelta());if(Game.state==='playing')update(dt);renderer.render(scene,camera);requestAnimationFrame(loop)}
function resize(){var w=innerWidth,h=innerHeight;renderer.setSize(w,h,false);camera.aspect=w/h;camera.updateProjectionMatrix()}
window.addEventListener('resize',resize);
window.addEventListener('keydown',function(e){if(['ArrowUp','ArrowDown','ArrowLeft','ArrowRight',' '].indexOf(e.key)>=0)e.preventDefault();Game.keys[e.key]=true;Game.keys[e.key.toLowerCase()]=true;if(e.key==='Escape'){if(Game.state==='playing')overlay('Paused','Adjust settings or resume when ready.','Resume');else if(Game.state==='paused'){Game.state='playing';menu.style.display='none'}}});
window.addEventListener('keyup',function(e){Game.keys[e.key]=false;Game.keys[e.key.toLowerCase()]=false});
renderer.domElement.addEventListener('pointermove',function(e){Game.pointer.x=e.clientX/innerWidth*2-1;Game.pointer.y=-(e.clientY/innerHeight)*2+1;raycaster.setFromCamera(Game.pointer,camera)});
renderer.domElement.addEventListener('pointerdown',function(){Game.pointer.down=true});renderer.domElement.addEventListener('pointerup',function(){Game.pointer.down=false});
document.getElementById('difficulty').onchange=function(){Game.settings.difficulty=parseFloat(this.value)||1};
document.getElementById('play').onclick=function(){if(Game.state==='paused'){Game.state='playing';menu.style.display='none'}else reset()};
resize();loop();
};
</script>
</body>
</html>

Game Source: Generic 3D Game

Creator: ThunderOwl74

Libraries: three

Complexity: simple (45 lines, 8.4 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: generic-3d-game-thunderowl74" to link back to the original. Then publish at arcadelab.ai/publish.