Meta Description" name="description" />
<!DOCTYPE html>
<html lang="tr">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no"/>
<title>Minecraft Mobil - 3D Voxel Dünya!</title>
<style>
body { margin:0; overflow:hidden; background:#000; font-family:Arial; }
canvas { display:block; }
#ui {
position: absolute; top: 20px; left: 20px; color: #fff; font-size: 28px; font-weight:bold;
text-shadow: 2px 2px 4px #000; pointer-events: none; z-index:10;
}
#crosshair {
position: absolute; top: 50%; left: 50%; width: 30px; height: 30px;
border: 3px solid #fff; border-radius: 50%; transform: translate(-50%, -50%);
pointer-events: none; z-index:10;
}
#startScreen, #gameOverScreen {
position: absolute; inset: 0; background: rgba(0,0,0,0.9); color: #fff;
display: flex; flex-direction: column; justify-content: center; align-items: center;
font-size: 32px; z-index:20;
}
#startScreen h1 { font-size:48px; margin:0; color:#4CAF50; text-shadow:0 0 20px #4CAF50; }
button { margin-top: 30px; padding: 20px 50px; font-size: 28px;
background: linear-gradient(45deg, #4CAF50, #45a049); border: none; color: #000;
border-radius: 50px; box-shadow: 0 10px 20px rgba(76,175,80,0.3); cursor:pointer; transition: all 0.2s;
}
button:hover, button:active { transform: scale(1.05); box-shadow: 0 15px 30px rgba(76,175,80,0.5); }
#instructions { font-size:20px; margin:20px 0; text-align:center; max-width:80%; line-height:1.4; }
#controls { position:absolute; bottom:30px; right:30px; z-index:100; display:none; }
#controls button { display:block; padding:20px 40px; margin:10px 0; font-size:24px; border-radius:20px;
border:none; color:white; cursor:pointer; min-width:150px; }
#breakBtn { background: linear-gradient(45deg, #f44336, #d32f2f); box-shadow: 0 5px 15px rgba(244,67,54,0.4); }
#placeBtn { background: linear-gradient(45deg, #4CAF50, #388E3C); box-shadow: 0 5px 15px rgba(76,175,80,0.4); }
#zone { position:absolute !important; left:30px !important; bottom:200px !important; width:160px !important; height:160px !important; z-index:90; }
#joystick { background:rgba(255,255,255,0.1); border-radius:50%; }
</style>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/nipplejs@0.10.2/dist/nipplejs.min.js"></script>
</head>
<body>
<div id="ui">Konum: <span id="pos">0,0,0</span></div>
<div id="crosshair"></div>
<div id="startScreen">
<h1>🟫 MİNE CRAFT 🟫</h1>
<div id="instructions">
• Sol joystick: Hareket et<br>
• Parmağını ekranda sürükle: Bakın etraf<br>
• KIR: Sol blok kır<br>
• YERLEŞTİR: Yeşil çim koy<br>
• Klavye WASD Space Shift de çalışır (PC)<br>
Sonsuz procedural dünya!
</div>
<button onclick="startGame()">BAŞLA ⛏️</button>
</div>
<div id="controls">
<button id="breakBtn">KIR ⛏️</button>
<button id="placeBtn">YERLEŞTİR 🌱</button>
</div>
<script>
let scene, camera, renderer, raycaster, highlightBox, joystick;
let chunks = {}, currentCx=0, currentCz=0, radius=6;
let velocity = new THREE.Vector3(), keys = {}, yaw=0, pitch=0, speed=20;
let chunkSize=16, chunkHeight=64;
let gameRunning = false, clock = new THREE.Clock();
let touchStart = {x:0, y:0};
const posEl = document.getElementById("pos");
const material = new THREE.MeshLambertMaterial({ vertexColors: true });
function init() {
const w = window.innerWidth, h = window.innerHeight;
scene = new THREE.Scene();
scene.fog = new THREE.Fog(0x87CEEB, 50, 300);
scene.background = new THREE.Color(0x87CEEB);
camera = new THREE.PerspectiveCamera(75, w/h, 0.1, 2000);
camera.position.set(0, 40, 0);
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(w, h);
renderer.shadowMap.enabled = true;
document.body.appendChild(renderer.domElement);
// Lights
const ambient = new THREE.AmbientLight(0x404040, 0.6);
scene.add(ambient);
const sun = new THREE.DirectionalLight(0xffffff, 0.8);
sun.position.set(50, 100, 50);
sun.castShadow = true;
scene.add(sun);
raycaster = new THREE.Raycaster();
// Highlight box
highlightBox = createWireframeBox();
scene.add(highlightBox);
// Events
window.addEventListener('resize', resize);
document.addEventListener('keydown', onKeyDown);
document.addEventListener('keyup', onKeyUp);
renderer.domElement.addEventListener('touchstart', onTouchStart, {passive:false});
renderer.domElement.addEventListener('touchmove', onTouchMove, {passive:false});
renderer.domElement.addEventListener('touchend', onTouchEnd, {passive:false});
renderer.domElement.addEventListener('contextmenu', e=>e.preventDefault());
setupJoystick();
setupButtons();
resize();
}
function createWireframeBox() {
const geo = new THREE.BufferGeometry();
const positions = [
// bottom
0,0,0, 1,0,0, 0,0,0, 0,0,1, 1,0,0, 1,0,1, 0,0,1, 1,0,1,
// top
0,1,0, 1,1,0, 0,1,0, 0,1,1, 1,1,0, 1,1,1, 0,1,1, 1,1,1,
// sides
0,0,0, 0,1,0, 1,0,0, 1,1,0, 0,0,1, 0,1,1, 1,0,1, 1,1,1
];
geo.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
const mat = new THREE.LineBasicMaterial({color: 0xffffff, transparent: true, opacity: 0.6});
return new THREE.LineSegments(geo, mat);
}
function setupJoystick() {
const zone = document.createElement('div');
zone.id = 'zone';
document.body.appendChild(zone);
joystick = nipplejs.create({
zone: document.getElementById('zone'),
mode: 'static',
position: { left: '50%', bottom: '50%' },
color: '#fff',
size: 160
});
joystick.on('move', (evt, data) => {
if (data.vector) {
velocity.x = data.vector.x * speed;
velocity.z = -data.vector.y * speed;
}
});
joystick.on('end', () => {
velocity.x = 0;
velocity.z = 0;
});
}
function setupButtons() {
document.getElementById('breakBtn').onclick = () => doRayAction(0);
document.getElementById('placeBtn').onclick = () => doRayAction(1);
}
function onKeyDown(e) { keys[e.code] = true; }
function onKeyUp(e) { keys[e.code] = false; }
function onTouchStart(e) {
e.preventDefault();
const touch = e.touches[0];
touchStart.x = touch.clientX;
touchStart.y = touch.clientY;
}
function onTouchMove(e) {
e.preventDefault();
const touch = e.touches[0];
let dx = touch.clientX - touchStart.x;
let dy = touch.clientY - touchStart.y;
yaw -= dx * 0.004;
pitch -= dy * 0.004;
pitch = Math.max(-Math.PI/2, Math.min(Math.PI/2, pitch));
touchStart.x = touch.clientX;
touchStart.y = touch.clientY;
}
function onTouchEnd(e) { e.preventDefault(); }
function resize() {
const w = window.innerWidth, h = window.innerHeight;
camera.aspect = w / h;
camera.updateProjectionMatrix();
renderer.setSize(w, h);
}
function fbm(wx, wz) {
let x = wx * 0.01, z = wz * 0.01;
let v = 0, a = 0.5;
for (let i = 0; i < 4; i++, a *= 0.5) {
v += a * noise2D(x, z);
x *= 2; z *= 2;
}
return v;
}
function noise2D(x, y) {
let n = x + y * 57;
n = (n << 13) ^ n;
return 1 - ((n * (n * n * 15731 + 789221) + 1376312589) & 0x7fffffff) / 1073741824;
}
function getIdx(x, y, z) {
return x + z * chunkSize + y * chunkSize * chunkSize;
}
function getVoxel(chunk, x, y, z) {
if (x < 0 || x >= chunkSize || y < 0 || y >= chunkHeight || z < 0 || z >= chunkSize) return 0;
return chunk.voxels[getIdx(x, y, z)];
}
function addFace(positions, normals, colors, indices, i, ax, ay, az, bx, by, bz, cx, cy, cz, dx, dy, dz, nx, ny, nz, color) {
const base = positions.length / 3;
positions.push(ax, ay, az, bx, by, bz, cx, cy, cz, dx, dy, dz);
for (let j = 0; j < 4; j++) {
normals.push(nx, ny, nz);
colors.push(color.r, color.g, color.b);
}
indices.push(base, base + 1, base + 2, base, base + 2, base + 3);
}
function buildMesh(chunk) {
let positions = [], normals = [], colors = [], indices = [];
let i = 0;
for (let x = 0; x < chunkSize; x++) {
for (let y = 0; y < chunkHeight; y++) {
for (let z = 0; z < chunkSize; z++) {
let type = getVoxel(chunk, x, y, z);
if (type === 0) continue;
let isGrass = type === 1;
let grassColor = new THREE.Color(0.3, 0.7, 0.2);
let dirtColor = new THREE.Color(0.5, 0.35, 0.15);
let stoneColor = new THREE.Color(0.4, 0.4, 0.45);
let blockColor = isGrass ? grassColor : (type === 3 ? stoneColor : dirtColor);
// Top
if (getVoxel(chunk, x, y + 1, z) === 0) {
addFace(positions, normals, colors, indices, i++,
x, y + 1, z, x + 1, y + 1, z, x + 1, y + 1, z + 1, x, y + 1, z + 1,
0, 1, 0, isGrass ? grassColor : blockColor);
}
// Bottom
if (getVoxel(chunk, x, y - 1, z) === 0) {
addFace(positions, normals, colors, indices, i++,
x, y, z, x + 1, y, z, x + 1, y, z + 1, x, y, z + 1,
0, -1, 0, dirtColor);
}
// Front (positive z)
if (getVoxel(chunk, x, y, z + 1) === 0) {
addFace(positions, normals, colors, indices, i++,
x, y + 1, z + 1, x + 1, y + 1, z + 1, x + 1, y, z + 1, x, y, z + 1,
0, 0, 1, blockColor);
}
// Back (negative z)
if (getVoxel(chunk, x, y, z - 1) === 0) {
addFace(positions, normals, colors, indices, i++,
x + 1, y + 1, z, x, y + 1, z, x, y, z, x + 1, y, z,
0, 0, -1, blockColor);
}
// Right (positive x)
if (getVoxel(chunk, x + 1, y, z) === 0) {
addFace(positions, normals, colors, indices, i++,
x + 1, y + 1, z, x + 1, y + 1, z + 1, x + 1, y, z + 1, x + 1, y, z,
1, 0, 0, blockColor);
}
// Left (negative x)
if (getVoxel(chunk, x - 1, y, z) === 0) {
addFace(positions, normals, colors, indices, i++,
x, y + 1, z + 1, x, y + 1, z, x, y, z, x, y, z + 1,
-1, 0, 0, blockColor);
}
}
}
}
const geo = new THREE.BufferGeometry();
geo.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
geo.setAttribute('normal', new THREE.Float32BufferAttribute(normals, 3));
geo.setAttribute('color', new THREE.Float32BufferAttribute(colors, 3));
geo.setIndex(indices);
geo.computeBoundingSphere();
const mesh = new THREE.Mesh(geo, material);
mesh.position.set(chunk.cx * chunkSize, 0, chunk.cz * chunkSize);
mesh.userData.chunkKey = `\( {chunk.cx}, \){chunk.cz}`;
return mesh;
}
function generateChunk(cx, cz) {
const voxels = new Uint8Array(chunkSize * chunkHeight * chunkSize);
for (let lx = 0; lx < chunkSize; lx++) {
for (let lz = 0; lz < chunkSize; lz++) {
let wx = cx * chunkSize + lx;
let wz = cz * chunkSize + lz;
let h = Math.floor(fbm(wx, wz) * 20 + 32);
h = Math.min(chunkHeight - 1, Math.max(0, h));
for (let ly = 0; ly <= h; ly++) {
let type = (ly === h) ? 1 : (ly > h - 4 ? 2 : 3);
voxels[getIdx(lx, ly, lz)] = type;
}
}
}
const chunk = {cx, cz, voxels};
chunk.mesh = buildMesh(chunk);
scene.add(chunk.mesh);
chunks[`\( {cx}, \){cz}`] = chunk;
}
function disposeChunk(key) {
const chunk = chunks[key];
if (chunk) {
scene.remove(chunk.mesh);
chunk.mesh.geometry.dispose();
delete chunks[key];
}
}
function updateChunks() {
const cx = Math.floor(camera.position.x / chunkSize);
const cz = Math.floor(camera.position.z / chunkSize);
if (cx !== currentCx || cz !== currentCz) {
currentCx = cx;
currentCz = cz;
// Unload far chunks
for (let key in chunks) {
const [ocx, ocz] = key.split(',').map(Number);
if (Math.abs(ocx - cx) > radius + 1 || Math.abs(ocz - cz) > radius + 1) {
disposeChunk(key);
}
}
// Generate near chunks
for (let dx = -radius; dx <= radius; dx++) {
for (let dz = -radius; dz <= radius; dz++) {
const ncx = cx + dx;
const ncz = cz + dz;
const nkey = `\( {ncx}, \){ncz}`;
if (!chunks[nkey]) {
generateChunk(ncx, ncz);
}
}
}
}
}
function doRayAction(action) {
raycaster.setFromCamera(new THREE.Vector2(0, 0), camera);
const inters = raycaster.intersectObjects(Object.values(chunks).map(c => c.mesh));
if (inters.length === 0) return;
const inter = inters[0];
const point = inter.point.clone();
const normal = inter.face.normal.clone();
if (action === 0) { // break
const pos = point.sub(normal.multiplyScalar(0.01)).floor();
const wx = Math.floor(pos.x), wy = Math.floor(pos.y), wz = Math.floor(pos.z);
const cx = Math.floor(wx / chunkSize);
const cz = Math.floor(wz / chunkSize);
removeVoxel(cx, cz, wx, wy, wz);
} else { // place
const pos = point.add(normal.multiplyScalar(0.51)).floor();
const wx = Math.floor(pos.x), wy = Math.floor(pos.y), wz = Math.floor(pos.z);
placeVoxel(wx, wy, wz, 1);
}
}
function removeVoxel(cx, cz, wx, wy, wz) {
const lx = wx - cx * chunkSize;
const lz = wz - cz * chunkSize;
const ly = wy;
if (lx < 0 || lx >= chunkSize || ly < 0 || ly >= chunkHeight || lz < 0 || lz >= chunkSize) return;
const key = `\( {cx}, \){cz}`;
const chunk = chunks[key];
if (!chunk) return;
const idx = getIdx(lx, ly, lz);
if (chunk.voxels[idx] === 0) return;
chunk.voxels[idx] = 0;
// Rebuild
const oldMesh = chunk.mesh;
chunk.mesh = buildMesh(chunk);
scene.remove(oldMesh);
oldMesh.geometry.dispose();
scene.add(chunk.mesh);
chunk.mesh.userData.chunkKey = key;
}
function placeVoxel(wx, wy, wz, type) {
const cx = Math.floor(wx / chunkSize);
const cz = Math.floor(wz / chunkSize);
const key = `\( {cx}, \){cz}`;
if (!chunks[key]) generateChunk(cx, cz);
const chunk = chunks[key];
let lx = wx - cx * chunkSize;
lx = ((lx % chunkSize) + chunkSize) % chunkSize;
let lz = wz - cz * chunkSize;
lz = ((lz % chunkSize) + chunkSize) % chunkSize;
const ly = wy;
if (ly < 0 || ly >= chunkHeight) return;
const idx = getIdx(lx, ly, lz);
if (chunk.voxels[idx] !== 0) return;
chunk.voxels[idx] = type;
// Rebuild
const oldMesh = chunk.mesh;
chunk.mesh = buildMesh(chunk);
scene.remove(oldMesh);
oldMesh.geometry.dispose();
scene.add(chunk.mesh);
chunk.mesh.userData.chunkKey = key;
}
function update() {
const dt = clock.getDelta();
// Input
velocity.set(0, 0, 0);
if (keys['KeyW'] || keys['ArrowUp']) velocity.z -= speed * dt;
if (keys['KeyS'] || keys['ArrowDown']) velocity.z += speed * dt;
if (keys['KeyA'] || keys['ArrowLeft']) velocity.x -= speed * dt;
if (keys['KeyD'] || keys['ArrowRight']) velocity.x += speed * dt;
if (keys['Space']) velocity.y += speed * dt * 2;
if (keys['ShiftLeft'] || keys['ShiftRight']) velocity.y -= speed * dt * 2;
// Move
const euler = new THREE.Euler(pitch, yaw, 0);
const forward = new THREE.Vector3(0, 0, -1).applyEuler(euler);
const right = new THREE.Vector3(1, 0, 0).applyEuler(euler);
camera.position.add(forward.multiplyScalar(velocity.z));
camera.position.add(right.multiplyScalar(velocity.x));
camera.position.y += velocity.y;
camera.rotation.set(pitch, yaw, 0);
// Raycast highlight
raycaster.setFromCamera(new THREE.Vector2(0, 0), camera);
const inters = raycaster.intersectObjects(Object.values(chunks).map(c => c.mesh));
if (inters.length > 0) {
const point = inters[0].point;
const pos = point.floor();
highlightBox.position.copy(pos).add(new THREE.Vector3(0.5, 0.5, 0.5));
highlightBox.visible = true;
} else {
highlightBox.visible = false;
}
// Update chunks
updateChunks();
// UI
posEl.textContent = `${Math.floor(camera.position.x)}, ${Math.floor(camera.position.y)}, ${Math.floor(camera.position.z)}`;
renderer.render(scene, camera);
requestAnimationFrame(update);
}
function startGame() {
document.getElementById("startScreen").style.display = "none";
document.getElementById("controls").style.display = "block";
document.getElementById("zone").style.display = "block";
gameRunning = true;
currentCx = 0; currentCz = 0;
for (let key in chunks) disposeChunk(key);
camera.position.set(0, 50, 0);
yaw = 0; pitch = 0;
update();
}
init();
</script>
</body>
</html>3
3
143KB
626KB
348.0ms
108.0ms
348.0ms