Meta Description" name="description" />
<!DOCTYPE html>
<html lang="vi">
<head>
<meta charset="UTF-8">
<title>Minecraft Mobile: Rotation & Look</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<style>
body { margin: 0; overflow: hidden; user-select: none; touch-action: none; background: #87CEEB; }
canvas { display: block; }
/* UI Layer */
#ui-layer { position: absolute; bottom: 20px; width: 100%; display: flex; justify-content: space-between; padding: 0 20px; box-sizing: border-box; pointer-events: none; }
.btn { width: 65px; height: 65px; background: rgba(255,255,255,0.3); border-radius: 50%; display: flex; align-items: center; justify-content: center; color: white; font-weight: bold; font-family: sans-serif; pointer-events: auto; }
#break-btn { background: rgba(255, 0, 0, 0.4); margin-bottom: 15px; }
#joystick-container { width: 100px; height: 100px; background: rgba(255,255,255,0.2); border-radius: 50%; display: flex; align-items: center; justify-content: center; pointer-events: auto; }
#joystick-handle { width: 50px; height: 50px; background: rgba(255,255,255,0.5); border-radius: 50%; }
#right-controls { display: flex; flex-direction: column; align-items: center; }
</style>
</head>
<body>
<div id="ui-layer">
<div id="joystick-container"><div id="joystick-handle"></div></div>
<div id="right-controls">
<div id="break-btn" class="btn">PHÁ</div>
<div id="jump-btn" class="btn">NHẢY</div>
</div>
</div>
<script type="module">
import * as THREE from 'https://cdn.skypack.dev/three@0.132.2';
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x87CEEB);
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer({ antialias: false });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
const light = new THREE.AmbientLight(0xffffff, 0.8);
scene.add(light);
const dLight = new THREE.DirectionalLight(0xffffff, 0.4);
dLight.position.set(10, 20, 10);
scene.add(dLight);
// --- NHÂN VẬT & XOAY ---
const player = {
mesh: new THREE.Group(),
velocity: new THREE.Vector3(0, 0, 0),
isGrounded: false,
yaw: 0, // Xoay trái/phải
pitch: 0, // Xoay lên/xuống
lastInteracted: null
};
const pBody = new THREE.Mesh(new THREE.BoxGeometry(0.6, 1.8, 0.6), new THREE.MeshLambertMaterial({color: 0x0000ff}));
pBody.position.y = 0.9;
player.mesh.add(pBody);
player.mesh.position.set(0, 5, 0);
scene.add(player.mesh);
const blocks = [];
const blockGeo = new THREE.BoxGeometry(1, 1, 1);
const matGrass = new THREE.MeshLambertMaterial({ color: 0x44aa88 });
const matWood = new THREE.MeshLambertMaterial({ color: 0x5D4037 });
const matLeaves = new THREE.MeshLambertMaterial({ color: 0x2E7D32 });
function createBlock(x, y, z, type = 'grass') {
const mat = type === 'wood' ? matWood : (type === 'leaves' ? matLeaves : matGrass);
const cube = new THREE.Mesh(blockGeo, mat);
cube.position.set(x, y, z);
cube.userData.id = `${x},${y},${z}`;
scene.add(cube);
blocks.push(cube);
}
// --- CẢM ỨNG XOAY MÀN HÌNH ---
let touchStartX = 0, touchStartY = 0;
let isRotating = false;
window.addEventListener('touchstart', (e) => {
const touch = e.touches[e.touches.length - 1];
// Nếu chạm vào vùng trống (không phải joystick/nút)
if (!e.target.closest('.btn') && !e.target.closest('#joystick-container')) {
isRotating = true;
touchStartX = touch.clientX;
touchStartY = touch.clientY;
}
});
window.addEventListener('touchmove', (e) => {
if (!isRotating) return;
const touch = e.touches[e.touches.length - 1];
const dx = touch.clientX - touchStartX;
const dy = touch.clientY - touchStartY;
player.yaw -= dx * 0.005; // Tốc độ xoay ngang
player.pitch -= dy * 0.005; // Tốc độ xoay dọc
player.pitch = Math.max(-Math.PI/3, Math.min(Math.PI/3, player.pitch)); // Giới hạn ngước lên/xuống
touchStartX = touch.clientX;
touchStartY = touch.clientY;
});
window.addEventListener('touchend', () => isRotating = false);
// --- PHÁ & ĐẶT KHỐI ---
const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2(0, 0); // Tâm màn hình
document.getElementById('break-btn').addEventListener('touchstart', () => {
raycaster.setFromCamera(mouse, camera);
const intersects = raycaster.intersectObjects(blocks);
if (intersects.length > 0) {
const target = intersects[0].object;
scene.remove(target);
blocks.splice(blocks.indexOf(target), 1);
}
});
// Chạm nhanh để đặt khối
window.addEventListener('click', (e) => {
if (e.target.closest('.btn') || e.target.closest('#joystick-container')) return;
raycaster.setFromCamera(mouse, camera);
const intersects = raycaster.intersectObjects(blocks);
if (intersects.length > 0) {
const b = intersects[0];
const pos = b.object.position.clone().add(b.face.normal);
createBlock(pos.x, pos.y, pos.z);
}
});
// --- JOYSTICK & DI CHUYỂN ---
let moveData = { x: 0, z: 0 };
const joyContainer = document.getElementById('joystick-container');
const joyHandle = document.getElementById('joystick-handle');
joyContainer.addEventListener('touchmove', (e) => {
const rect = joyContainer.getBoundingClientRect();
const dx = e.touches[0].clientX - (rect.left + 50);
const dy = e.touches[0].clientY - (rect.top + 50);
const dist = Math.min(Math.sqrt(dx*dx + dy*dy), 50);
const angle = Math.atan2(dy, dx);
joyHandle.style.transform = `translate(${Math.cos(angle)*dist}px, ${Math.sin(angle)*dist}px)`;
moveData.x = (Math.cos(angle) * dist) / 50;
moveData.z = (Math.sin(angle) * dist) / 50;
});
joyContainer.addEventListener('touchend', () => {
joyHandle.style.transform = `translate(0,0)`;
moveData = { x: 0, z: 0 };
});
document.getElementById('jump-btn').addEventListener('touchstart', () => {
if (player.isGrounded) player.velocity.y = 0.15;
});
// --- VÒNG LẶP CHÍNH ---
function generateWorld() {
const px = Math.round(player.mesh.position.x);
const pz = Math.round(player.mesh.position.z);
for (let x = px-7; x < px+7; x++) {
for (let z = pz-7; z < pz+7; z++) {
const id = `${x},${z}_g`;
if (!blocks.find(b => b.userData.id === id)) {
const y = Math.floor(Math.sin(x/6)*1.2);
createBlock(x, y, z);
blocks[blocks.length-1].userData.id = id;
if(Math.random() > 0.99) { // Cây mọc hiếm hơn tí cho đỡ lag
for(let i=1; i<=3; i++) createBlock(x, y+i, z, 'wood');
}
}
}
}
}
function animate() {
requestAnimationFrame(animate);
// 1. Cập nhật vật lý rơi
player.velocity.y -= 0.008;
player.mesh.position.y += player.velocity.y;
// 2. Di chuyển dựa trên hướng Camera (Yaw)
const forward = new THREE.Vector3(Math.sin(player.yaw), 0, Math.cos(player.yaw));
const right = new THREE.Vector3().crossVectors(new THREE.Vector3(0,1,0), forward);
player.mesh.position.addScaledVector(forward, -moveData.z * 0.12);
player.mesh.position.addScaledVector(right, moveData.x * 0.12);
// 3. Xử lý va chạm sàn
player.isGrounded = false;
blocks.forEach(b => {
if (Math.abs(player.mesh.position.x - b.position.x) < 0.7 && Math.abs(player.mesh.position.z - b.position.z) < 0.7) {
const floorY = b.position.y + 1.4;
if (player.mesh.position.y < floorY && player.mesh.position.y > b.position.y - 0.5) {
player.mesh.position.y = floorY;
player.velocity.y = 0;
player.isGrounded = true;
}
}
});
// 4. Cập nhật Camera (Third Person Orbit)
const camDist = 6;
camera.position.x = player.mesh.position.x + Math.sin(player.yaw) * Math.cos(player.pitch) * camDist;
camera.position.y = player.mesh.position.y + Math.sin(player.pitch) * camDist + 2.5;
camera.position.z = player.mesh.position.z + Math.cos(player.yaw) * Math.cos(player.pitch) * camDist;
camera.lookAt(player.mesh.position.x, player.mesh.position.y + 1.5, player.mesh.position.z);
generateWorld();
renderer.render(scene, camera);
}
animate();
</script>
</body>
</html>3
2
201KB
1050KB
319.0ms
116.0ms
319.0ms