Meta Description" name="description" />
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>ZOMBIE SURVIVOR 3D</title>
<style>
*{margin:0;padding:0;box-sizing:border-box;touch-action:none;user-select:none;-webkit-user-select:none}
body{background:#000;overflow:hidden;font-family:Arial, sans-serif}
canvas{display:block;width:100vw;height:100vh}
#hud{position:absolute;top:0;left:0;right:0;height:42px;background:linear-gradient(#000,#222);color:#fff;display:flex;align-items:center;justify-content:space-between;padding:0 10px;z-index:10;font-size:13px;border-bottom:2px solid #555}
#bars{position:absolute;top:46px;left:8px;right:80px;display:flex;gap:6px;z-index:10}
.bar{flex:1;height:10px;background:#333;border-radius:5px;overflow:hidden;border:1px solid #555}
.fill{height:100%;transition:width 0.2s}
#left{position:absolute;bottom:18px;left:18px;width:120px;height:120px;background:rgba(255,255,255,0.12);border:2px solid rgba(255,255,255,0.3);border-radius:50%;z-index:10}
#stick{position:absolute;width:54px;height:54px;background:rgba(255,255,255,0.9);border-radius:50%;left:33px;top:33px;box-shadow:0 2px 8px #000}
#right{position:absolute;bottom:18px;right:18px;width:140px;height:140px;background:rgba(255,255,255,0.06);border-radius:50%;z-index:10}
#btns{position:absolute;right:8px;top:65px;display:flex;flex-direction:column;gap:8px;z-index:10}
.b{width:62px;height:62px;border-radius:50%;border:2px solid #fff;display:flex;align-items:center;justify-content:center;color:#fff;font-weight:900;font-size:9px;text-align:center;box-shadow:0 3px 10px #000}
#bAtk{background:radial-gradient(#ff3333,#990000)} #bPis{background:radial-gradient(#4488ff,#0022aa)}
#bLoot{background:radial-gradient(#ffdd33,#aa7700);color:#000} #bBlock{background:radial-gradient(#55ffff,#008888);color:#000}
#bEat{background:radial-gradient(#55ff55,#007700)}
#msg{position:absolute;top:70px;left:50%;transform:translateX(-50%);background:rgba(0,0,0,0.85);color:#ff0;padding:6px 14px;border-radius:20px;z-index:10;font-size:12px;font-weight:bold}
#inv{position:absolute;bottom:150px;left:8px;background:rgba(0,0,0,0.8);color:#fff;padding:6px 8px;border-radius:8px;z-index:10;font-size:10px;line-height:13px;max-width:160px}
</style>
<script src="https://unpkg.com/three@0.160.0/build/three.min.js"></script>
</head>
<body>
<div id="hud"><span>β€οΈ<b id="hp">100</b> π«<b id="bl">12</b> π§<b id="zc">0</b></span><span id="day">DIA 1</span><span>πΎ<b id="saveBtn">SAVE</b></span></div>
<div id="bars"><div class="bar"><div id="hpBar" class="fill" style="width:100%;background:#f00"></div></div><div class="bar"><div id="hunBar" class="fill" style="width:100%;background:#ff8800"></div></div></div>
<div id="msg">3RA PERSONA - BUSCA CAJAS MARRONES</div>
<div id="left"><div id="stick"></div></div><div id="right"></div>
<div id="btns">
<div class="b" id="bAtk">MACHETE</div><div class="b" id="bPis">PISTOLA</div><div class="b" id="bLoot">LOOT</div><div class="b" id="bBlock">ESCUDO</div><div class="b" id="bEat">COMER</div>
</div>
<div id="inv">Cargando...</div>
<script>
// --- ESCENA ---
let scene=new THREE.Scene(); scene.background=new THREE.Color(0x87CEEB); scene.fog=new THREE.Fog(0x87CEEB,30,180);
let camera=new THREE.PerspectiveCamera(65, innerWidth/innerHeight, 0.1, 1000);
let renderer=new THREE.WebGLRenderer({antialias:true}); renderer.setSize(innerWidth,innerHeight); renderer.shadowMap.enabled=true; document.body.appendChild(renderer.domElement);
scene.add(new THREE.AmbientLight(0xffffff,0.9));
let sun=new THREE.DirectionalLight(0xffffff,1); sun.position.set(30,50,20); sun.castShadow=true; scene.add(sun);
let floor=new THREE.Mesh(new THREE.PlaneGeometry(600,600), new THREE.MeshStandardMaterial({color:0x5a5a5a})); floor.rotation.x=-Math.PI/2; floor.receiveShadow=true; scene.add(floor);
function mat(c){ return new THREE.MeshStandardMaterial({color:c}); }
function human(color, zombie){
let g=new THREE.Group();
let body=new THREE.Mesh(new THREE.BoxGeometry(0.7,0.9,0.4), mat(color)); body.castShadow=true;
let head=new THREE.Mesh(new THREE.SphereGeometry(0.35,10,10), mat(zombie?0x6baa6b:0xffdbac)); head.position.y=0.85; head.castShadow=true;
let leg1=new THREE.Mesh(new THREE.BoxGeometry(0.25,0.7,0.25), mat(0x222222)); leg1.position.set(-0.2,-0.8,0);
let leg2=leg1.clone(); leg2.position.x=0.2;
g.add(body,head,leg1,leg2); g.position.y=1.1; return g;
}
let you=human(0x0066ff,false); scene.add(you);
let buildings=[], crates=[], zombies=[];
for(let i=0;i<30;i++){
let x=(Math.random()-0.5)*160, z=(Math.random()-0.5)*160; if(Math.hypot(x,z)<10) continue;
let h=6+Math.random()*8;
let b=new THREE.Mesh(new THREE.BoxGeometry(8,h,8), mat(0x999999)); b.position.set(x,h/2,z); b.castShadow=true; b.receiveShadow=true; scene.add(b); buildings.push(b);
if(Math.random()<0.6){ let c=new THREE.Mesh(new THREE.BoxGeometry(1.2,0.9,1.2), mat(0x8B4513)); c.position.set(x+(Math.random()-0.5)*4,0.45,z+(Math.random()-0.5)*4); c.castShadow=true; scene.add(c); crates.push({mesh:c,taken:false}); }
}
function spawnZombie(n=1){ for(let k=0;k<n;k++){ let a=Math.random()*6.28,r=50+Math.random()*40; let m=human(0x2a7a2a,true); m.position.set(Math.cos(a)*r,0,Math.sin(a)*r); scene.add(m); zombies.push({mesh:m,hp:100}); } }
spawnZombie(10);
// --- JUGADOR ---
let player={pos:new THREE.Vector3(0,1.1,10),yaw:0,pitch:-0.15,mx:0,my:0,hp:100,hunger:100,bullets:12,inv:{madera:2,chapa:0,metal:0,lata:1,agua:1},sick:0,shield:0,blocking:false};
let day=1, dayT=0;
function ui(){
document.getElementById('hp').innerText=Math.floor(player.hp);
document.getElementById('bl').innerText=player.bullets;
document.getElementById('zc').innerText=zombies.length;
document.getElementById('hpBar').style.width=player.hp+'%';
document.getElementById('hunBar').style.width=player.hunger+'%';
let i=player.inv;
let t=`πͺ΅${i.madera} π©${i.chapa} βοΈ${i.metal} π₯«${i.lata} π§${i.agua}`;
if(player.sick>0) t+=`\nπ€’ ENVENENADO ${player.sick.toFixed(0)}s`;
if(player.shield>0) t+=`\nπ‘οΈ ESCUDO ${player.shield.toFixed(0)}`;
document.getElementById('inv').innerText=t;
if(player.hp<=0){ document.getElementById('msg').innerText='MORISTE - TOCA SAVE PARA REINICIAR'; }
}
// --- CONTROLES ---
let left=document.getElementById('left'), stick=document.getElementById('stick'), ld=false;
left.addEventListener('touchstart',()=>ld=true);
left.addEventListener('touchmove',e=>{
let rc=left.getBoundingClientRect(); let x=e.touches[0].clientX-(rc.left+60), y=e.touches[0].clientY-(rc.top+60);
let d=Math.hypot(x,y); if(d>36){x=x/d*36; y=y/d*36;}
stick.style.left=33+x+'px'; stick.style.top=33+y+'px'; player.mx=x/36; player.my=y/36;
});
left.addEventListener('touchend',()=>{ld=false; stick.style.left='33px'; stick.style.top='33px'; player.mx=0; player.my=0;});
let right=document.getElementById('right'), rd=false, lx=0, ly=0;
right.addEventListener('touchstart',e=>{rd=true; lx=e.touches[0].clientX; ly=e.touches[0].clientY;});
right.addEventListener('touchmove',e=>{
if(!rd) return; let dx=e.touches[0].clientX-lx, dy=e.touches[0].clientY-ly;
player.yaw-=dx*0.008; player.pitch-=dy*0.008; player.pitch=Math.max(-0.7,Math.min(0.5,player.pitch));
lx=e.touches[0].clientX; ly=e.touches[0].clientY;
});
right.addEventListener('touchend',()=>rd=false);
// --- ACCIONES ---
function tip(t){ document.getElementById('msg').innerText=t; setTimeout(()=>{ if(player.hp>0) document.getElementById('msg').innerText='DIA '+day; },1800); }
document.getElementById('bAtk').addEventListener('touchstart',e=>{
e.preventDefault(); let hit=false;
zombies.forEach((z,i)=>{
if(z.mesh.position.distanceTo(player.pos)<3.2){ z.hp-=55; hit=true; if(z.hp<=0){ scene.remove(z.mesh); zombies.splice(i,1); if(Math.random()<0.4){ player.inv.madera++; tip('MADERA +1'); } } }
});
if(!hit) tip('NADIE CERCA'); ui();
});
document.getElementById('bPis').addEventListener('touchstart',e=>{
e.preventDefault(); if(player.bullets<=0){ tip('SIN BALAS!'); return; } player.bullets--; let dir=new THREE.Vector3(0,0,-1); dir.applyAxisAngle(new THREE.Vector3(0,1,0), player.yaw); dir.y=player.pitch;
let ray=new THREE.Raycaster(player.pos, dir, 0, 70); let hits=ray.intersectObjects(zombies.map(z=>z.mesh),true);
if(hits.length>0){ let root=hits[0].object; while(root.parent && root.parent!==scene) root=root.parent; let f=zombies.findIndex(z=>z.mesh===root); if(f>-1){ scene.remove(zombies[f].mesh); zombies.splice(f,1); tip('HEADSHOT!'); player.inv.lata++; } } else tip('FALLASTE');
ui();
});
document.getElementById('bLoot').addEventListener('touchstart',e=>{
e.preventDefault(); let c=crates.find(c=>!c.taken && c.mesh.position.distanceTo(player.pos)<3.5);
if(c){ c.taken=true; scene.remove(c.mesh); let r=Math.random(); if(r<0.3){ player.inv.madera+=2; tip('MADERA x2'); } else if(r<0.6){ player.inv.agua++; tip('AGUA +1'); } else if(r<0.85){ player.inv.lata++; player.bullets+=3; tip('LATA + BALAS'); } else { player.inv.chapa++; player.inv.metal++; tip('MATERIALES RAROS!'); } ui(); }
else tip('NO HAY CAJA CERCA');
});
document.getElementById('bBlock').addEventListener('touchstart',()=>{ player.blocking=true; player.shield=Math.min(100,player.shield+0.5); tip('BLOQUEANDO'); });
document.getElementById('bBlock').addEventListener('touchend',()=>{ player.blocking=false; });
document.getElementById('bEat').addEventListener('touchstart',e=>{
e.preventDefault(); if(player.inv.lata>0){ player.inv.lata--; if(Math.random()<0.25){ player.sick=18; player.hp-=12; tip('PODRIDA! -12'); } else { player.hp=Math.min(100,player.hp+22); player.hunger=Math.min(100,player.hunger+30); tip('+22 VIDA +30 HAMBRE'); } }
else if(player.inv.agua>0){ player.inv.agua--; player.hp=Math.min(100,player.hp+8); player.hunger=Math.min(100,player.hunger+20); tip('AGUA +8'); }
else tip('NADA PARA COMER'); ui();
});
document.getElementById('saveBtn').addEventListener('touchstart',()=>{
if(player.hp<=0){ localStorage.removeItem('zombiePro'); location.reload(); return; }
localStorage.setItem('zombiePro', JSON.stringify({pos:player.pos,hp:player.hp,hunger:player.hunger,bullets:player.bullets,inv:player.inv,day})); tip('GUARDADO!');
});
// cargar
let sv=localStorage.getItem('zombiePro'); if(sv){ try{ let s=JSON.parse(sv); player.pos.set(s.pos.x,s.pos.y,s.pos.z); player.hp=s.hp; player.hunger=s.hunger; player.bullets=s.bullets; player.inv=s.inv; day=s.day; document.getElementById('day').innerText='DIA '+day; tip('PARTIDA CARGADA'); }catch(e){} }
function collide(p){ return buildings.some(b=>Math.abs(p.x-b.position.x)<4.5 && Math.abs(p.z-b.position.z)<4.5); }
function loop(){
requestAnimationFrame(loop);
if(player.hp>0){
// movimiento
let f=new THREE.Vector3(Math.sin(player.yaw),0,Math.cos(player.yaw));
let r=new THREE.Vector3(Math.cos(player.yaw),0,-Math.sin(player.yaw));
let mv=new THREE.Vector3(); mv.addScaledVector(f,-player.my*0.32); mv.addScaledVector(r,player.mx*0.32);
let np=player.pos.clone().add(mv); if(!collide(np)) player.pos.copy(np);
you.position.copy(player.pos); you.position.y=0; you.rotation.y=player.yaw;
// camara 3ra persona
let behind=new THREE.Vector3(0,3.2,7.5); behind.applyAxisAngle(new THREE.Vector3(0,1,0), player.yaw); behind.y+=player.pitch*3;
camera.position.copy(player.pos.clone().add(behind)); camera.lookAt(player.pos.x, player.pos.y+1, player.pos.z);
// zombies
zombies.forEach(z=>{
let d=player.pos.clone().sub(z.mesh.position); d.y=0; let dist=d.length();
if(dist>1.6){ d.normalize().multiplyScalar(0.07+day*0.005); if(!collide(z.mesh.position.clone().add(d))) z.mesh.position.add(d); }
else { if(player.blocking && player.shield>0){ player.shield-=0.6; } else { player.hp-=0.18; } }
z.mesh.lookAt(player.pos.x, z.mesh.position.y, player.pos.z);
});
// hambre y veneno
player.hunger-=0.008; if(player.hunger<=0){ player.hunger=0; player.hp-=0.02; }
if(player.sick>0){ player.sick-=0.016; player.hp-=0.025; }
if(player.shield>0 &&!player.blocking) player.shield-=0.05;
dayT++; if(dayT>900){ dayT=0; day++; document.getElementById('day').innerText='DIA '+day; spawnZombie(3+Math.floor(day/2)); tip('DIA '+day+' - VIENEN MAS!'); }
}
ui(); renderer.render(scene,camera);
}
loop();
ui();
window.addEventListener('resize',()=>{ camera.aspect=innerWidth/innerHeight; camera.updateProjectionMatrix(); renderer.setSize(innerWidth,innerHeight); });
</script>
</body>
</html>u2
2
176KB
667KB
1,796.0ms
116.0ms
1,796.0ms