Meta Description" name="description" />
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Subway Surf Mini 3D HP</title>
<style>
body { margin: 0; overflow: hidden; touch-action: none; }
canvas { display: block; }
.score { position: absolute; top: 10px; left: 10px; font-size: 24px; color: #fff; text-shadow: 1px 1px 2px #000; }
.btn{ position: absolute; width: 60px; height: 60px; background: rgba(0,0,0,0.6); color: white; border-radius: 30px; text-align:center; line-height:60px; font-size:24px; user-select:none; }
#left{left:20px; bottom:80px;} #right{left:100px; bottom:80px;} #jump{right:60px; bottom:80px;}
</style>
</head>
<body>
<div class="score" id="score">Score: 0</div>
<div class="btn" id="left">◀</div>
<div class="btn" id="right">▶</div>
<div class="btn" id="jump">⭑</div>
<!-- Three.js -->
<script src="https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.min.js"></script>
<!-- OrbitControls untuk visual debug (tidak dipakai kontrolnya) -->
<script src="https://cdn.jsdelivr.net/npm/three@0.160.0/examples/js/controls/OrbitControls.js"></script>
<script>
// scene & camera
const scene = new THREE.Scene();
scene.fog = new THREE.Fog(0xa0a0a0, 10, 50);
const camera = new THREE.PerspectiveCamera(60, window.innerWidth/window.innerHeight, 0.1, 1000);
camera.position.set(0,5,10);
// renderer
const renderer = new THREE.WebGLRenderer({antialias:true});
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true;
document.body.appendChild(renderer.domElement);
// light
const dirLight = new THREE.DirectionalLight(0xffffff, 1);
dirLight.position.set(5,10,7);
dirLight.castShadow = true;
scene.add(dirLight);
const ambLight = new THREE.AmbientLight(0x404040);
scene.add(ambLight);
// ground track
let tracks = [];
function addTrack(z){
const geo = new THREE.BoxGeometry(6,1,10);
const mat = new THREE.MeshStandardMaterial({color:0x555555});
const track = new THREE.Mesh(geo, mat);
track.position.set(0,-0.5,z);
track.receiveShadow = true;
scene.add(track);
tracks.push(track);
}
for(let i=0;i<15;i++) addTrack(i*10);
// player
const playerGeo = new THREE.SphereGeometry(0.5,32,32);
const playerMat = new THREE.MeshStandardMaterial({color:0xff4444});
const player = new THREE.Mesh(playerGeo, playerMat);
player.position.set(0,0.5,0);
player.castShadow = true;
scene.add(player);
// coins
let coins = [];
function addCoin(x,z){
const geo = new THREE.TorusGeometry(0.3,0.1,16,100);
const mat = new THREE.MeshStandardMaterial({color:0xffdd00, emissive:0xffdd00});
const coin = new THREE.Mesh(geo, mat);
coin.position.set(x,0.4,z);
coin.rotation.x = Math.PI/2;
coin.castShadow = true;
scene.add(coin);
coins.push(coin);
}
for(let i=1;i<30;i++){
addCoin([-2,0,2][Math.floor(Math.random()*3)], i*5);
}
// obstacles
let obstacles = [];
function addObstacle(x,z){
const geo = new THREE.BoxGeometry(1,1,1);
const mat = new THREE.MeshStandardMaterial({color:0x2222ff});
const obs = new THREE.Mesh(geo, mat);
obs.position.set(x,0.5,z);
obs.castShadow = true;
scene.add(obs);
obstacles.push(obs);
}
for(let i=1;i<30;i++){
if(Math.random()<0.4){
addObstacle([-2,0,2][Math.floor(Math.random()*3)], i*7);
}
}
// controls
let move = {left:false, right:false, jump:false};
let velocityY = 0;
const gravity = -0.5;
let lane = 0;
let score = 0;
const scoreEl = document.getElementById("score");
["left","right","jump"].forEach(id=>{
const btn = document.getElementById(id);
btn.addEventListener("touchstart", e=>{move[id]=true; e.preventDefault();});
btn.addEventListener("touchend", e=>{move[id]=false; e.preventDefault();});
});
// animate
function animate(){
requestAnimationFrame(animate);
// auto forward
player.position.z += 0.25;
// lane change
if(move.left && lane > -1){ lane--; move.left=false; }
if(move.right && lane < 1){ lane++; move.right=false; }
player.position.x = THREE.MathUtils.lerp(player.position.x, lane*2, 0.2);
// jump & gravity
if(move.jump && player.position.y <= 0.5){ velocityY = 0.6; }
velocityY += gravity*0.02;
player.position.y += velocityY;
if(player.position.y < 0.5){player.position.y = 0.5; velocityY=0;}
// coin pickup
for(let i=coins.length-1;i>=0;i--){
if(player.position.distanceTo(coins[i].position) < 0.7){
scene.remove(coins[i]);
coins.splice(i,1);
score++;
scoreEl.textContent = "Score: "+score;
}
}
// obstacles
for(let obs of obstacles){
if(player.position.distanceTo(obs.position) < 0.7){
alert("Game Over! Score: "+score);
window.location.reload();
}
}
// endless track
tracks.forEach(t=>{
if(t.position.z + 5 < player.position.z){
t.position.z += tracks.length*10;
}
});
// render
renderer.render(scene,camera);
}
animate();
// handle resize
window.addEventListener("resize", ()=>{
camera.aspect = window.innerWidth/window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
</script>
</body>
</html>3
2
154KB
660KB
811.0ms
120.0ms
811.0ms