TROLL TOWER 3D
by MegaNinja83490 lines10.7 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">
<title>TROLL TOWER 3D</title>
<style>
*{box-sizing:border-box}
body{margin:0;overflow:hidden;background:#79caff;font-family:Arial,sans-serif}
#ui{position:absolute;top:15px;left:15px;color:white;z-index:5;
text-shadow:2px 2px 4px #333;pointer-events:none}
h1{margin:0;font-size:25px;color:#ffe45e}
#score{font-size:18px;margin-top:8px}
#help{font-size:13px;margin-top:8px;line-height:1.6}
#message{position:absolute;top:40%;width:100%;text-align:center;
color:white;font-size:35px;font-weight:bold;text-shadow:3px 3px #222;
z-index:8;pointer-events:none}
#overlay{position:absolute;inset:0;background:#0009;display:none;
align-items:center;justify-content:center;z-index:10}
#panel{background:#fff;border-radius:20px;padding:30px;
text-align:center;max-width:330px;width:90%;color:#333}
button{border:0;background:#ffb700;color:#222;font-weight:bold;
padding:13px 22px;border-radius:12px;font-size:17px;cursor:pointer}
#mobile{position:absolute;bottom:22px;left:0;right:0;
display:none;justify-content:space-between;padding:0 22px;z-index:6}
.controls{display:flex;gap:9px}
.controls button{width:57px;height:57px;padding:0;
background:#ffffffbb;font-size:22px;touch-action:none}
@media(max-width:800px){
#mobile{display:flex}
#help{font-size:11px}
h1{font-size:20px}
}
</style>
</head>
<body>
<div id="ui">
<h1>😈 TROLL TOWER</h1>
<div id="score">Stage: 0 / 10 | Checkpoint: 0</div>
<div id="help">
WASD / Arrow keys = Move<br>
SPACE = Jump<br>
Avoid the lava and troll platforms!
</div>
</div>
<div id="message">CLIMB THE TOWER!</div>
<div id="overlay">
<div id="panel">
<h1 id="resultTitle">YOU WIN!</h1>
<p id="resultText">You conquered Troll Tower!</p>
<button onclick="restartGame()">PLAY AGAIN</button>
</div>
</div>
<div id="mobile">
<div class="controls">
<button id="left">◀</button>
<button id="right">▶</button>
</div>
<div class="controls">
<button id="jump">⬆</button>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script>
if(typeof THREE==="undefined"){
document.getElementById("message").textContent=
"Internet required to load the 3D game!";
throw new Error("Three.js could not load");
}
// SCENE
const scene=new THREE.Scene();
scene.background=new THREE.Color(0x80d5ff);
scene.fog=new THREE.Fog(0x80d5ff,35,100);
const camera=new THREE.PerspectiveCamera(
70,window.innerWidth/window.innerHeight,0.1,200
);
const renderer=new THREE.WebGLRenderer({antialias:true});
renderer.setSize(window.innerWidth,window.innerHeight);
renderer.setPixelRatio(Math.min(window.devicePixelRatio,2));
renderer.shadowMap.enabled=true;
document.body.appendChild(renderer.domElement);
// LIGHTING
scene.add(new THREE.HemisphereLight(0xffffff,0x526080,2));
const sun=new THREE.DirectionalLight(0xffffff,2);
sun.position.set(-10,25,15);
sun.castShadow=true;
scene.add(sun);
// MATERIALS
function material(color,emissive=0x000000){
return new THREE.MeshStandardMaterial({
color:color,emissive:emissive,roughness:0.65
});
}
const playerMat=material(0xffcf47);
const lavaMat=new THREE.MeshStandardMaterial({
color:0xff2400,emissive:0xff3300,emissiveIntensity:1.4
});
// OBJECTS
let platforms=[];
let hazards=[];
let checkpoints=[];
let keys={};
let checkpoint=0;
let stage=0;
let coins=0;
let finished=false;
let grounded=false;
let velocityY=0;
let previousY=0;
let lastMessage=0;
const player=new THREE.Mesh(
new THREE.BoxGeometry(0.85,1.4,0.85),playerMat
);
player.castShadow=true;
scene.add(player);
// PLAYER FACE
const eyeMat=new THREE.MeshBasicMaterial({color:0x222222});
for(let side of [-1,1]){
const eye=new THREE.Mesh(
new THREE.BoxGeometry(0.12,0.15,0.04),eyeMat
);
eye.position.set(side*0.2,0.25,0.44);
player.add(eye);
}
// PLATFORM CREATOR
function makePlatform(name,x,y,z,color,width=6,depth=5){
const mesh=new THREE.Mesh(
new THREE.BoxGeometry(width,0.5,depth),
material(color)
);
mesh.position.set(x,y,z);
mesh.receiveShadow=true;
mesh.castShadow=true;
scene.add(mesh);
const p={mesh,name,x,y,z,width,depth,active:true,
troll:false,timer:0};
platforms.push(p);
return p;
}
// START
makePlatform("START",0,0,0,0x25d17c,9,8);
// TOWER STAGES
const stagePositions=[
[0,2.1,0],
[3,4.2,1.2],
[0,6.3,2.4],
[-3,8.4,1.2],
[0,10.5,0],
[3,12.6,-1.2],
[0,14.7,-2.4],
[-3,16.8,-1.2],
[0,18.9,0],
[3,21,1.2]
];
stagePositions.forEach((pos,i)=>{
let p=makePlatform(
"STAGE "+(i+1),
pos[0],pos[1],pos[2],
[i%2===0?0x3498ff:0xa56bff],
6,5
);
// Troll platforms
if(i===2||i===6){
p.troll=true;
p.mesh.material=material(0xff55bb);
}
});
// FINISH
makePlatform("FINISH",0,23.1,0,0xffd700,9,8);
// LAVA CREATOR
function addLava(p){
const lava=new THREE.Mesh(
new THREE.BoxGeometry(0.65,0.15,4.8),lavaMat
);
lava.position.set(p.x,p.y+0.34,p.z);
scene.add(lava);
hazards.push({mesh:lava,x:p.x,z:p.z,y:p.y});
}
// Add lava to selected stages
[1,3,5,7].forEach(i=>addLava(platforms[i+1]));
// DECORATIVE COINS
let coinObjects=[];
for(let i=0;i<10;i++){
const coin=new THREE.Mesh(
new THREE.TorusGeometry(0.4,0.12,8,16),
new THREE.MeshStandardMaterial({
color:0xffd700,emissive:0xaa6600
})
);
const p=platforms[i+1];
coin.position.set(p.x,p.y+1.1,p.z+1.3);
scene.add(coin);
coinObjects.push({mesh:coin,collected:false});
}
// CHECKPOINT FLAGS
[3,6,9].forEach(n=>{
const p=platforms[n];
const pole=new THREE.Mesh(
new THREE.CylinderGeometry(0.08,0.08,2,8),
material(0xffffff)
);
pole.position.set(p.x-2,p.y+1,p.z);
scene.add(pole);
const flag=new THREE.Mesh(
new THREE.BoxGeometry(0.9,0.55,0.08),
material(0x00ff77)
);
flag.position.set(p.x-1.55,p.y+1.65,p.z);
scene.add(flag);
checkpoints.push({stage:n,position:p});
});
// START PLAYER
function placePlayer(p){
player.position.set(p.x,p.y+1,p.z);
velocityY=0;
grounded=false;
}
placePlayer(platforms[0]);
// KEYBOARD
window.addEventListener("keydown",e=>{
keys[e.code]=true;
if(["Space","ArrowUp","ArrowDown","ArrowLeft","ArrowRight"]
.includes(e.code))e.preventDefault();
});
window.addEventListener("keyup",e=>{
keys[e.code]=false;
});
// MOBILE BUTTONS
function bindButton(id,key){
const b=document.getElementById(id);
b.addEventListener("pointerdown",e=>{
e.preventDefault();
keys[key]=true;
});
["pointerup","pointerleave","pointercancel"].forEach(event=>{
b.addEventListener(event,()=>keys[key]=false);
});
}
bindButton("left","ArrowLeft");
bindButton("right","ArrowRight");
bindButton("jump","Space");
// UI
const scoreUI=document.getElementById("score");
const message=document.getElementById("message");
const overlay=document.getElementById("overlay");
function showMessage(text){
message.textContent=text;
lastMessage=Date.now();
}
function updateUI(){
scoreUI.textContent=
"Stage: "+stage+" / 10 | Checkpoint: "+checkpoint;
}
// RESPAWN
function respawn(){
placePlayer(platforms[checkpoint]);
stage=checkpoint;
showMessage("OOPS! TRY AGAIN 😂");
updateUI();
}
// GAME OVER / WIN
function endGame(win){
finished=true;
overlay.style.display="flex";
document.getElementById("resultTitle").textContent=
win?"🏆 YOU WIN!":"GAME OVER";
document.getElementById("resultText").textContent=
win?"You conquered Troll Tower! Coins: "+coins:
"Don't give up! Try again!";
}
// RESTART
function restartGame(){
finished=false;
overlay.style.display="none";
checkpoint=0;
stage=0;
coins=0;
coinObjects.forEach(c=>{
c.collected=false;
c.mesh.visible=true;
});
platforms.forEach(p=>{
p.active=true;
p.mesh.visible=true;
p.timer=0;
});
placePlayer(platforms[0]);
updateUI();
showMessage("CLIMB THE TOWER!");
}
// PLATFORM COLLISION
function checkLanding(oldBottom,newBottom){
grounded=false;
for(let p of platforms){
if(!p.active||!p.mesh.visible)continue;
const top=p.y+0.25;
const dx=Math.abs(player.position.x-p.x);
const dz=Math.abs(player.position.z-p.z);
if(dx<p.width/2+0.35 && dz<p.depth/2+0.35 &&
oldBottom>=top && newBottom<=top && velocityY<=0){
player.position.y=top+0.7;
velocityY=0;
grounded=true;
if(p.name==="FINISH"){
endGame(true);
return;
}
if(p.troll&&!p.triggered){
p.triggered=true;
p.timer=1.1;
showMessage("HAHA! TROLL PLATFORM!");
}
if(p.name.startsWith("STAGE")){
let n=Number(p.name.split(" ")[1]);
stage=Math.max(stage,n);
if(n%3===0&&n>checkpoint){
checkpoint=n;
showMessage("CHECKPOINT SAVED!");
}
updateUI();
}
break;
}
}
}
// MAIN LOOP
const clock=new THREE.Clock();
function animate(){
requestAnimationFrame(animate);
const dt=Math.min(clock.getDelta(),0.04);
if(!finished){
// MOVEMENT
let moveX=0,moveZ=0;
if(keys["KeyA"]||keys["ArrowLeft"])moveX--;
if(keys["KeyD"]||keys["ArrowRight"])moveX++;
if(keys["KeyW"]||keys["ArrowUp"])moveZ--;
if(keys["KeyS"]||keys["ArrowDown"])moveZ++;
const len=Math.hypot(moveX,moveZ);
if(len>0){moveX/=len;moveZ/=len;}
player.position.x+=moveX*7*dt;
player.position.z+=moveZ*7*dt;
// JUMP
if(keys["Space"]&&grounded){
velocityY=10.5;
grounded=false;
}
previousY=player.position.y;
velocityY-=23*dt;
player.position.y+=velocityY*dt;
// LANDING
checkLanding(previousY-0.7,player.position.y-0.7);
// TROLL PLATFORM DISAPPEAR
platforms.forEach(p=>{
if(p.troll&&p.timer>0){
p.timer-=dt;
if(p.timer<=0){
p.active=false;
p.mesh.visible=false;
setTimeout(()=>{
p.active=true;
p.mesh.visible=true;
p.triggered=false;
},2500);
}
}
});
// LAVA COLLISION
for(let h of hazards){
if(Math.abs(player.position.x-h.x)<0.8&&
Math.abs(player.position.z-h.z)<2.5&&
Math.abs(player.position.y-(h.y+1))<1.2){
respawn();
break;
}
}
// COIN COLLECTION
coinObjects.forEach(c=>{
if(c.collected)return;
c.mesh.rotation.y+=dt*3;
if(player.position.distanceTo(c.mesh.position)<1.25){
c.collected=true;
c.mesh.visible=false;
coins++;
showMessage("+1 COIN! 🪙");
}
});
// FALL
if(player.position.y< -8){
respawn();
}
// LIMIT
player.position.x=THREE.MathUtils.clamp(player.position.x,-12,12);
player.position.z=THREE.MathUtils.clamp(player.position.z,-10,10);
}
// CAMERA
const target=new THREE.Vector3(
player.position.x+0.5,
player.position.y+5,
player.position.z+10
);
camera.position.lerp(target,0.07);
camera.lookAt(player.position.x,player.position.y,player.position.z);
renderer.render(scene,camera);
if(Date.now()-lastMessage>2200){
message.textContent="";
}
}
updateUI();
animate();
// RESIZE
window.addEventListener("resize",()=>{
camera.aspect=window.innerWidth/window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth,window.innerHeight);
});
</script>
</body>
</html>
Game Source: TROLL TOWER 3D
Creator: MegaNinja83
Libraries: three
Complexity: complex (490 lines, 10.7 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: troll-tower-3d-meganinja83" to link back to the original. Then publish at arcadelab.ai/publish.