🎮ArcadeLab

Escada da Montanha 3D

by GlitchViper25
206 lines8.2 KB🛠️ Three.js (3D graphics)
▶ Play
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1">
<title>Escada da Montanha 3D</title>
<style>
*{margin:0;padding:0;box-sizing:border-box;font-family:Arial,sans-serif;touch-action:none;}
body{overflow:hidden;background:#87CEEB;}
canvas{display:block;}
.interface{position:fixed;top:15px;left:50%;transform:translateX(-50%);color:white;font-size:22px;font-weight:bold;text-shadow:2px 2px 4px #000;z-index:10;}
.controles{position:fixed;bottom:20px;left:0;right:0;display:flex;justify-content:space-between;padding:0 20px;z-index:10;}
.joystick{width:120px;height:120px;background:rgba(255,255,255,0.2);border-radius:50%;position:relative;}
.bola{width:50px;height:50px;background:rgba(255,255,255,0.8);border-radius:50%;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);}
.btn-pular{width:80px;height:80px;background:#ff5722;color:white;border:none;border-radius:50%;font-size:18px;font-weight:bold;align-self:flex-end;}
.aviso{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);background:rgba(0,100,0,0.9);color:white;padding:30px;border-radius:12px;font-size:24px;text-align:center;display:none;z-index:20;}
</style>
</head>
<body>
<div class="interface">🗻 Suba a Montanha! 🚩</div>
<div class="controles">
  <div class="joystick" id="joystick"><div class="bola" id="alavanca"></div></div>
  <button class="btn-pular" id="btPular">Pular</button>
</div>
<div class="aviso" id="avisoGanhou">🎉 PARABÉNS! VOCÊ ALCANÇOU O CUME! 🎉<br>Clique para recomeçar</div>

<script src="https://cdn.jsdelivr.net/npm/three@0.158.0/build/three.min.js"></script>
<script>
const cena = new THREE.Scene();
cena.background = new THREE.Color(0x87CEEB);
const camera = new THREE.PerspectiveCamera(70, window.innerWidth/window.innerHeight, 0.1, 200);
const renderizador = new THREE.WebGLRenderer({antialias:true});
renderizador.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderizador.domElement);

const luz = new THREE.DirectionalLight(0xffffff, 1.2);
luz.position.set(5,10,5);
cena.add(luz); cena.add(new THREE.AmbientLight(0xffffff, 0.6));

// Jogador
const jogadorGeo = new THREE.CapsuleGeometry(0.4, 0.8, 4, 8);
const jogadorMat = new THREE.MeshStandardMaterial({color:0x2196F3});
const jogador = new THREE.Mesh(jogadorGeo, jogadorMat);
jogador.position.y = 1; cena.add(jogador);

// MONTANHA (invés de plataformas)
const montanhaGeo = new THREE.ConeGeometry(12, 25, 64); // Raio, Altura, Segmentos
const montanhaMat = new THREE.MeshStandardMaterial({color:0x795548});
const montanha = new THREE.Mesh(montanhaGeo, montanhaMat);
montanha.position.y = 12.5; // Ajusta a base no chão
cena.add(montanha);

// DETALHES DA MONTANHA (Pedras e Vegetação)
const pedrasGeo = new THREE.BoxGeometry(1,1,1);
const pedrasMat = new THREE.MeshStandardMaterial({color:0x5D4037});
for (let i = 0; i < 50; i++) {
  const pedra = new THREE.Mesh(pedrasGeo, pedrasMat);
  const angulo = Math.random() * Math.PI * 2;
  const distancia = Math.random() * 10;
  pedra.position.x = Math.cos(angulo) * distancia;
  pedra.position.z = Math.sin(angulo) * distancia;
  pedra.position.y = Math.random() * 20; // Altura aleatória na montanha
  pedra.rotation.set(Math.random()*Math.PI, Math.random()*Math.PI, Math.random()*Math.PI);
  cena.add(pedra);
}

// Grama no cume
const gramaGeo = new THREE.CircleGeometry(4, 32);
const gramaMat = new THREE.MeshStandardMaterial({color:0x2E7D32});
const grama = new THREE.Mesh(gramaGeo, gramaMat);
grama.position.y = 25; // Topo da montanha
grama.rotation.x = -Math.PI / 2; // Deixa a grama horizontal
cena.add(grama);

// Bandeira no cume
const bandeiraHaste = new THREE.Mesh(new THREE.CylinderGeometry(0.1,0.1,3), new THREE.MeshStandardMaterial({color:0x795548}));
bandeiraHaste.position.set(0,26.5,0); // No topo da grama
cena.add(bandeiraHaste);
const bandeiraTecido = new THREE.Mesh(new THREE.PlaneGeometry(2,1.2), new THREE.MeshStandardMaterial({color:0xff0000, side:THREE.DoubleSide}));
bandeiraTecido.position.set(1.1,27.5,0);
cena.add(bandeiraTecido);

// Sol (luz ambiente)
const solGeo = new THREE.SphereGeometry(2, 32, 32);
const solMat = new THREE.MeshStandardMaterial({color:0xFFFF00});
const sol = new THREE.Mesh(solGeo, solMat);
sol.position.set(30, 20, -20);
cena.add(sol);

// Movimento e física
let vel = {x:0,y:0,z:0};
let noChao = true;
const gravidade = -0.018;
const forcaPulo = 0.35;
const velMov = 0.12;
let teclas = {w:false,a:false,s:false,d:false,espaco:false};
let alavanca = {x:0,y:0};

// Teclado
document.addEventListener('keydown',e=>{
  if(e.key.toLowerCase()==='w') teclas.w=true;
  if(e.key.toLowerCase()==='a') teclas.a=true;
  if(e.key.toLowerCase()==='s') teclas.s=true;
  if(e.key.toLowerCase()==='d') teclas.d=true;
  if(e.key===' ') teclas.espaco=true;
});
document.addEventListener('keyup',e=>{
  if(e.key.toLowerCase()==='w') teclas.w=false;
  if(e.key.toLowerCase()==='a') teclas.a=false;
  if(e.key.toLowerCase()==='s') teclas.s=false;
  if(e.key.toLowerCase()==='d') teclas.d=false;
  if(e.key===' ') teclas.espaco=false;
});

// Joystick toque (AJUSTADO para direção correta)
const j = document.getElementById('joystick');
const b = document.getElementById('alavanca');
let tocando = false;
j.addEventListener('touchstart',e=>{tocando=true; e.preventDefault();});
document.addEventListener('touchmove',e=>{
  if(!tocando) return; e.preventDefault();
  const r = j.getBoundingClientRect();
  let dx = e.touches[0].clientX - (r.left+r.width/2);
  let dy = e.touches[0].clientY - (r.top+r.height/2);
  const dist = Math.hypot(dx,dy);
  if(dist>35){ dx = dx/dist*35; dy = dy/dist*35; }
  b.style.transform = `translate(calc(-50% + ${dx}px), calc(-50% + ${dy}px))`;
  alavanca.x = dx/35; 
  alavanca.y = dy/35 * (-1); // INVERTENDO o eixo Y para direção correta
});
document.addEventListener('touchend',()=>{
  tocando=false; b.style.transform='translate(-50%,-50%)'; alavanca.x=alavanca.y=0;
});
document.getElementById('btPular').addEventListener('touchstart',()=>teclas.espaco=true);
document.getElementById('btPular').addEventListener('touchend',()=>teclas.espaco=false);

// Colisão com a MONTANHA
function verificarChao(){
  noChao=false;

  // Verifica colisão com a montanha
  const distanciaCentro = Math.hypot(jogador.position.x, jogador.position.z);
  const alturaMaxima = 25 - (distanciaCentro / 12) * 25; // Calcula a altura máxima da montanha na posição do jogador

  if (jogador.position.y <= alturaMaxima + 0.9 && jogador.position.y >= alturaMaxima - 0.3 && vel.y <= 0) {
    jogador.position.y = alturaMaxima + 0.9;
    vel.y = 0;
    noChao = true;
  }

  // Verifica se o jogador caiu no chão
  if (jogador.position.y < 0.9) {
    jogador.position.y = 0.9;
    vel.y = 0;
    noChao = true;
  }
}

// Verificar vitória (chegou ao cume)
function checarVitoria(){
  if (jogador.position.y > 24) { // Se o jogador estiver quase no topo
    document.getElementById('avisoGanhou').style.display='block';
  }
}

// Loop
function animar(){
  requestAnimationFrame(animar);
  vel.y += gravidade;

  // Movimento
  let mx=0,mz=0;
  if(teclas.w) mz-=1; if(teclas.s) mz+=1;
  if(teclas.a) mx-=1; if(teclas.d) mx+=1;
  if(alavanca.y) mz = alavanca.y * (-1); // Joystick Y (cima/baixo) controla Z (frente/trás)
  if(alavanca.x) mx = alavanca.x; // Joystick X (esquerda/direita) controla X (esquerda/direita)
  if(mx||mz){ const n = Math.hypot(mx,mz); mx/=n; mz/=n; }
  jogador.position.x += mx*velMov;
  jogador.position.z += mz*velMov;

  if(teclas.espaco && noChao){ vel.y = forcaPulo; noChao=false; teclas.espaco=false; }
  jogador.position.y += vel.y;

  verificarChao();
  checarVitoria();

  // Câmera
  camera.position.set(jogador.position.x, jogador.position.y+4, jogador.position.z+8);
  camera.lookAt(jogador.position.x, jogador.position.y+1, jogador.position.z);

  renderizador.render(cena,camera);
}
animar();

window.addEventListener('resize',()=>{
  camera.aspect = window.innerWidth/window.innerHeight;
  camera.updateProjectionMatrix();
  renderizador.setSize(window.innerWidth, window.innerHeight);
});
document.getElementById('avisoGanhou').addEventListener('click',()=>{
  jogador.position.set(0,1,0); vel={x:0,y:0,z:0};
  document.getElementById('avisoGanhou').style.display='none';
});
</script>
</body>
</html>

Game Source: Escada da Montanha 3D

Creator: GlitchViper25

Libraries: three

Complexity: complex (206 lines, 8.2 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: escada-da-montanha-3d-glitchviper25" to link back to the original. Then publish at arcadelab.ai/publish.