Meta Description" name="description" />
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>Mobile Creative Sandbox</title>
<style>
body {
margin: 0;
overflow: hidden;
background-color: #87CEEB;
font-family: sans-serif;
user-select: none;
-webkit-user-select: none;
touch-action: none;
}
#ui-container {
position: absolute;
top: 15px;
left: 15px;
color: white;
text-shadow: 1px 1px 3px rgba(0,0,0,0.8);
pointer-events: none;
font-size: 14px;
}
#crosshair {
position: absolute;
top: 50%;
left: 50%;
width: 10px;
height: 10px;
transform: translate(-50%, -50%);
color: white;
font-size: 24px;
pointer-events: none;
}
/* Virtual Joystick */
#joystick-boundary {
position: absolute;
bottom: 30px;
left: 30px;
width: 100px;
height: 100px;
background: rgba(255, 255, 255, 0.2);
border: 2px solid rgba(255, 255, 255, 0.4);
border-radius: 50%;
touch-action: none;
}
#joystick-nub {
position: absolute;
top: 30px;
left: 30px;
width: 40px;
height: 40px;
background: rgba(255, 255, 255, 0.6);
border-radius: 50%;
}
/* Build Button */
#build-btn {
position: absolute;
bottom: 40px;
right: 30px;
width: 70px;
height: 70px;
background: #b0413e;
color: white;
border: 3px solid white;
border-radius: 50%;
font-weight: bold;
font-size: 16px;
box-shadow: 0px 4px 10px rgba(0,0,0,0.3);
}
</style>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
</head>
<body>
<div id="ui-container">
<b>Earth Sandbox (Mobile)</b><br>
Drag right side to look | Use joystick to move
</div>
<div id="crosshair">+</div>
<div id="joystick-boundary">
<div id="joystick-nub"></div>
</div>
<button id="build-btn">BUILD</button>
<script>
// --- 1. ENGINE SETUP ---
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: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// Lights
scene.add(new THREE.AmbientLight(0xffffff, 0.7));
const sun = new THREE.DirectionalLight(0xffffff, 0.6);
sun.position.set(10, 20, 10);
scene.add(sun);
// --- 2. GROUND GRID ---
const blockSize = 2;
const blocks = [];
const grassGeo = new THREE.BoxGeometry(blockSize, blockSize, blockSize);
const grassMat = new THREE.MeshStandardMaterial({ color: 0x557a2b, roughness: 0.8 });
for (let x = -10; x < 10; x++) {
for (let z = -10; z < 10; z++) {
const block = new THREE.Mesh(grassGeo, grassMat);
block.position.set(x * blockSize, -blockSize/2, z * blockSize);
scene.add(block);
blocks.push(block);
}
}
camera.position.set(0, 4, 8);
// --- 3. MOBILE TOUCH LOOK CONTROLS ---
let touchStartLook = { x: 0, y: 0 };
let cameraRotation = { y: 0, x: 0 };
window.addEventListener('touchstart', (e) => {
for(let i=0; i<e.touches.length; i++) {
// If touch is on the right half of screen, look around
if (e.touches[i].clientX > window.innerWidth / 2) {
touchStartLook.x = e.touches[i].clientX;
touchStartLook.y = e.touches[i].clientY;
}
}
});
window.addEventListener('touchmove', (e) => {
for(let i=0; i<e.touches.length; i++) {
if (e.touches[i].clientX > window.innerWidth / 2) {
const deltaX = e.touches[i].clientX - touchStartLook.x;
const deltaY = e.touches[i].clientY - touchStartLook.y;
cameraRotation.y -= deltaX * 0.005;
cameraRotation.x -= deltaY * 0.005;
cameraRotation.x = Math.max(-Math.PI/3, Math.min(Math.PI/3, cameraRotation.x));
camera.rotation.order = "YXZ";
camera.rotation.set(cameraRotation.x, cameraRotation.y, 0);
touchStartLook.x = e.touches[i].clientX;
touchStartLook.y = e.touches[i].clientY;
}
}
});
// --- 4. VIRTUAL JOYSTICK MOVEMENT ---
const boundary = document.getElementById('joystick-boundary');
const nub = document.getElementById('joystick-nub');
let moveVector = { x: 0, y: 0 };
let joystickActive = false;
boundary.addEventListener('touchstart', (e) => {
joystickActive = true;
});
window.addEventListener('touchmove', (e) => {
if (!joystickActive) return;
const touch = e.touches[0];
const bRect = boundary.getBoundingClientRect();
const centerX = bRect.left + bRect.width / 2;
const centerY = bRect.top + bRect.height / 2;
let dx = touch.clientX - centerX;
let dy = touch.clientY - centerY;
const dist = Math.sqrt(dx*dx + dy*dy);
const maxDist = 30;
if (dist > maxDist) {
dx = (dx / dist) * maxDist;
dy = (dy / dist) * maxDist;
}
nub.style.transform = `translate(${dx}px, ${dy}px)`;
// Normalize values between -1 and 1
moveVector.x = dx / maxDist;
moveVector.y = dy / maxDist;
});
window.addEventListener('touchend', () => {
joystickActive = false;
nub.style.transform = 'translate(0px, 0px)';
moveVector = { x: 0, y: 0 };
});
// --- 5. TOUCH BUILDING ---
const raycaster = new THREE.Raycaster();
const centerMouse = new THREE.Vector2(0, 0);
const brickMat = new THREE.MeshStandardMaterial({ color: 0xb0413e });
document.getElementById('build-btn').addEventListener('touchstart', (e) => {
e.preventDefault(); // Stop screen zoom
raycaster.setFromCamera(centerMouse, camera);
const intersects = raycaster.intersectObjects(blocks);
if (intersects.length > 0) {
const intersect = intersects[0];
const newBlock = new THREE.Mesh(grassGeo, brickMat);
newBlock.position.copy(intersect.object.position).add(intersect.face.normal.multiplyScalar(blockSize));
scene.add(newBlock);
blocks.push(newBlock);
}
});
// --- 6. GAME LOOP ---
function animate() {
requestAnimationFrame(animate);
// Forward/Backward & Strafe based on camera direction
const speed = 0.1;
const forward = new THREE.Vector3(0, 0, -1).applyQuaternion(camera.quaternion);
const side = new THREE.Vector3(1, 0, 0).applyQuaternion(camera.quaternion);
forward.y = 0; // Don't fly into sky
side.y = 0;
forward.normalize();
side.normalize();
camera.position.addScaledVector(forward, -moveVector.y * speed);
camera.position.addScaledVector(side, moveVector.x * speed);
renderer.render(scene, camera);
}
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
animate();
</script>
</body>
</html>2
2
128KB
598KB
3,406.0ms
108.0ms
3,406.0ms