mirror of https://github.com/kortix-ai/suna.git
495 lines
18 KiB
HTML
495 lines
18 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Minecraft Clone</title>
|
|
<style>
|
|
/* Previous CSS styles remain the same */
|
|
body { margin: 0; overflow: hidden; }
|
|
canvas { display: block; image-rendering: pixelated; }
|
|
.crosshair {
|
|
position: fixed;
|
|
top: 50%;
|
|
left: 50%;
|
|
transform: translate(-50%, -50%);
|
|
color: white;
|
|
font-size: 24px;
|
|
pointer-events: none;
|
|
text-shadow: 2px 2px #000;
|
|
}
|
|
.inventory {
|
|
position: fixed;
|
|
bottom: 20px;
|
|
left: 50%;
|
|
transform: translateX(-50%);
|
|
display: flex;
|
|
gap: 5px;
|
|
background: rgba(0, 0, 0, 0.5);
|
|
padding: 10px;
|
|
border-radius: 5px;
|
|
}
|
|
.inventory-slot {
|
|
width: 50px;
|
|
height: 50px;
|
|
background: rgba(255, 255, 255, 0.2);
|
|
border: 2px solid #fff;
|
|
cursor: pointer;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
color: white;
|
|
image-rendering: pixelated;
|
|
}
|
|
.inventory-slot.selected {
|
|
background: rgba(255, 255, 255, 0.4);
|
|
border-color: #ffff00;
|
|
}
|
|
.hud {
|
|
position: fixed;
|
|
top: 20px;
|
|
left: 20px;
|
|
color: white;
|
|
font-family: 'Minecraft', monospace;
|
|
text-shadow: 2px 2px #000;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="crosshair">+</div>
|
|
<div class="hud">
|
|
<div id="coordinates"></div>
|
|
<div id="selected-block"></div>
|
|
</div>
|
|
<div class="inventory" id="inventory"></div>
|
|
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
|
|
<script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/controls/PointerLockControls.js"></script>
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/simplex-noise/2.4.0/simplex-noise.min.js"></script>
|
|
<script>
|
|
// Initialize Three.js with Minecraft-style settings
|
|
const scene = new THREE.Scene();
|
|
scene.fog = new THREE.Fog(0x8CB4DB, 30, 100); // Minecraft-style fog distance
|
|
scene.background = new THREE.Color(0x8CB4DB); // Minecraft sky blue
|
|
|
|
const camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.1, 1000);
|
|
const renderer = new THREE.WebGLRenderer({
|
|
antialias: false,
|
|
logarithmicDepthBuffer: true,
|
|
physicallyCorrectLights: true
|
|
});
|
|
const clock = new THREE.Clock(); // Disable antialiasing for pixelated look
|
|
renderer.setSize(window.innerWidth, window.innerHeight);
|
|
renderer.setPixelRatio(1); // Force pixel ratio to 1 for sharp pixels
|
|
renderer.shadowMap.enabled = true;
|
|
document.body.appendChild(renderer.domElement);
|
|
|
|
// Minecraft-style textures
|
|
const textureLoader = new THREE.TextureLoader();
|
|
const texturePaths = {
|
|
grass_top: 'https://raw.githubusercontent.com/mrdoob/three.js/dev/examples/textures/minecraft/grass_top.png',
|
|
grass_side: 'https://raw.githubusercontent.com/mrdoob/three.js/dev/examples/textures/minecraft/grass_side.png',
|
|
dirt: 'https://raw.githubusercontent.com/mrdoob/three.js/dev/examples/textures/minecraft/dirt.png',
|
|
stone: 'https://raw.githubusercontent.com/mrdoob/three.js/dev/examples/textures/minecraft/stone.png',
|
|
wood: 'https://raw.githubusercontent.com/mrdoob/three.js/dev/examples/textures/minecraft/wood.png',
|
|
wood_top: 'https://raw.githubusercontent.com/mrdoob/three.js/dev/examples/textures/minecraft/wood.png',
|
|
leaves: 'https://raw.githubusercontent.com/mrdoob/three.js/dev/examples/textures/minecraft/leaves.png',
|
|
bedrock: 'https://raw.githubusercontent.com/mrdoob/three.js/dev/examples/textures/minecraft/stone.png'
|
|
};
|
|
|
|
// Configure texture settings for pixelated look
|
|
function loadMinecraftTexture(path) {
|
|
const texture = textureLoader.load(path);
|
|
texture.magFilter = THREE.NearestFilter;
|
|
texture.minFilter = THREE.NearestFilter;
|
|
texture.wrapS = THREE.RepeatWrapping;
|
|
texture.wrapT = THREE.RepeatWrapping;
|
|
return texture;
|
|
}
|
|
|
|
// Load all textures with Minecraft settings
|
|
const blockTextures = {};
|
|
for (const [key, path] of Object.entries(texturePaths)) {
|
|
blockTextures[key] = loadMinecraftTexture(path);
|
|
}
|
|
|
|
// Create materials with Minecraft-style shading
|
|
const materialSettings = {
|
|
transparent: false,
|
|
side: THREE.FrontSide,
|
|
shadowSide: THREE.FrontSide,
|
|
depthWrite: true,
|
|
depthTest: true
|
|
};
|
|
|
|
const blockMaterials = {
|
|
grass: [
|
|
new THREE.MeshBasicMaterial({ map: blockTextures.grass_side }),
|
|
new THREE.MeshBasicMaterial({ map: blockTextures.grass_side }),
|
|
new THREE.MeshBasicMaterial({ map: blockTextures.grass_top }),
|
|
new THREE.MeshBasicMaterial({ map: blockTextures.dirt }),
|
|
new THREE.MeshBasicMaterial({ map: blockTextures.grass_side }),
|
|
new THREE.MeshBasicMaterial({ map: blockTextures.grass_side })
|
|
],
|
|
dirt: Array(6).fill(new THREE.MeshBasicMaterial({ map: blockTextures.dirt })),
|
|
stone: Array(6).fill(new THREE.MeshBasicMaterial({ map: blockTextures.stone })),
|
|
wood: [
|
|
new THREE.MeshBasicMaterial({ map: blockTextures.wood }),
|
|
new THREE.MeshBasicMaterial({ map: blockTextures.wood }),
|
|
new THREE.MeshBasicMaterial({ map: blockTextures.wood_top }),
|
|
new THREE.MeshBasicMaterial({ map: blockTextures.wood_top }),
|
|
new THREE.MeshBasicMaterial({ map: blockTextures.wood }),
|
|
new THREE.MeshBasicMaterial({ map: blockTextures.wood })
|
|
],
|
|
leaves: Array(6).fill(new THREE.MeshBasicMaterial({
|
|
map: blockTextures.leaves,
|
|
transparent: true,
|
|
alphaTest: 0.5
|
|
})),
|
|
bedrock: Array(6).fill(new THREE.MeshBasicMaterial({ map: blockTextures.bedrock }))
|
|
};
|
|
|
|
// Simple lighting for Minecraft-style rendering
|
|
const ambientLight = new THREE.AmbientLight(0xffffff, 1.0);
|
|
scene.add(ambientLight);
|
|
|
|
// Enhanced block creation with Minecraft materials
|
|
function createBlock(x, y, z, type = 'grass') {
|
|
const geometry = new THREE.BoxGeometry(1, 1, 1);
|
|
const materials = blockMaterials[type];
|
|
const cube = new THREE.Mesh(geometry, materials);
|
|
cube.position.set(x, y, z);
|
|
cube.castShadow = true;
|
|
cube.receiveShadow = true;
|
|
cube.userData.type = type;
|
|
scene.add(cube);
|
|
blocks.set(`${x},${y},${z}`, cube);
|
|
return cube;
|
|
}
|
|
|
|
// Improved terrain generation with more Minecraft-like features
|
|
const noise = new SimplexNoise();
|
|
const worldSize = 32;
|
|
const blocks = new Map();
|
|
|
|
function generateTerrain() {
|
|
// Generate bedrock layer
|
|
for (let x = -worldSize/2; x < worldSize/2; x++) {
|
|
for (let z = -worldSize/2; z < worldSize/2; z++) {
|
|
createBlock(x, 0, z, 'bedrock');
|
|
}
|
|
}
|
|
|
|
// Generate terrain
|
|
for (let x = -worldSize/2; x < worldSize/2; x++) {
|
|
for (let z = -worldSize/2; z < worldSize/2; z++) {
|
|
const height = Math.floor(
|
|
(noise.noise2D(x * 0.1, z * 0.1) + 1) * 5 + 5
|
|
);
|
|
|
|
// Generate layers
|
|
for (let y = 1; y < height; y++) {
|
|
const blockType = y === height - 1 ? 'grass' :
|
|
y > height - 4 ? 'dirt' : 'stone';
|
|
createBlock(x, y, z, blockType);
|
|
}
|
|
|
|
// Generate trees with better distribution
|
|
if (Math.random() < 0.02 && height > 3) {
|
|
const treeHeight = 4 + Math.floor(Math.random() * 3);
|
|
|
|
// Tree trunk
|
|
for (let y = height; y < height + treeHeight; y++) {
|
|
createBlock(x, y, z, 'wood');
|
|
}
|
|
|
|
// Tree leaves
|
|
for (let lx = -2; lx <= 2; lx++) {
|
|
for (let ly = -2; ly <= 2; ly++) {
|
|
for (let lz = -2; lz <= 2; lz++) {
|
|
if (Math.abs(lx) + Math.abs(ly) + Math.abs(lz) <= 4 && Math.random() < 0.7) {
|
|
createBlock(
|
|
x + lx,
|
|
height + treeHeight + ly,
|
|
z + lz,
|
|
'leaves'
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Player Controls and Physics
|
|
const controls = new THREE.PointerLockControls(camera, document.body);
|
|
let canJump = false;
|
|
let velocity = new THREE.Vector3();
|
|
let moveForward = false;
|
|
let moveBackward = false;
|
|
let moveLeft = false;
|
|
let moveRight = false;
|
|
let playerHeight = 1.6;
|
|
camera.position.set(0, playerHeight + 20, 20); // Move camera back for better initial view
|
|
|
|
// Click to start
|
|
document.addEventListener('click', () => {
|
|
controls.lock();
|
|
});
|
|
|
|
controls.addEventListener('lock', () => {
|
|
document.getElementById('inventory').style.display = 'flex';
|
|
});
|
|
|
|
controls.addEventListener('unlock', () => {
|
|
document.getElementById('inventory').style.display = 'none';
|
|
});
|
|
|
|
// Keyboard controls
|
|
const onKeyDown = function(event) {
|
|
switch (event.code) {
|
|
case 'ArrowUp':
|
|
case 'KeyW':
|
|
moveForward = true;
|
|
break;
|
|
case 'ArrowDown':
|
|
case 'KeyS':
|
|
moveBackward = true;
|
|
break;
|
|
case 'ArrowLeft':
|
|
case 'KeyA':
|
|
moveLeft = true;
|
|
break;
|
|
case 'ArrowRight':
|
|
case 'KeyD':
|
|
moveRight = true;
|
|
break;
|
|
case 'Space':
|
|
if (canJump) {
|
|
velocity.y = 8;
|
|
canJump = false;
|
|
}
|
|
break;
|
|
}
|
|
};
|
|
|
|
const onKeyUp = function(event) {
|
|
switch (event.code) {
|
|
case 'ArrowUp':
|
|
case 'KeyW':
|
|
moveForward = false;
|
|
break;
|
|
case 'ArrowDown':
|
|
case 'KeyS':
|
|
moveBackward = false;
|
|
break;
|
|
case 'ArrowLeft':
|
|
case 'KeyA':
|
|
moveLeft = false;
|
|
break;
|
|
case 'ArrowRight':
|
|
case 'KeyD':
|
|
moveRight = false;
|
|
break;
|
|
}
|
|
};
|
|
|
|
document.addEventListener('keydown', onKeyDown);
|
|
document.addEventListener('keyup', onKeyUp);
|
|
|
|
// Inventory system
|
|
const inventory = {
|
|
selectedSlot: 0,
|
|
slots: [
|
|
{ type: 'grass', count: Infinity },
|
|
{ type: 'dirt', count: Infinity },
|
|
{ type: 'stone', count: Infinity },
|
|
{ type: 'wood', count: Infinity },
|
|
{ type: 'leaves', count: Infinity },
|
|
{ type: 'bedrock', count: Infinity }
|
|
]
|
|
};
|
|
|
|
function updateInventoryUI() {
|
|
const inventoryEl = document.getElementById('inventory');
|
|
inventoryEl.innerHTML = '';
|
|
inventory.slots.forEach((slot, index) => {
|
|
const slotEl = document.createElement('div');
|
|
slotEl.className = `inventory-slot ${index === inventory.selectedSlot ? 'selected' : ''}`;
|
|
slotEl.textContent = slot.type;
|
|
slotEl.onclick = () => {
|
|
inventory.selectedSlot = index;
|
|
updateInventoryUI();
|
|
};
|
|
inventoryEl.appendChild(slotEl);
|
|
});
|
|
}
|
|
|
|
// Mouse wheel to change selected slot
|
|
document.addEventListener('wheel', (event) => {
|
|
if (controls.isLocked) {
|
|
inventory.selectedSlot = (inventory.selectedSlot + (event.deltaY > 0 ? 1 : -1) + inventory.slots.length) % inventory.slots.length;
|
|
updateInventoryUI();
|
|
}
|
|
});
|
|
|
|
// Block interaction
|
|
const raycaster = new THREE.Raycaster();
|
|
const mouse = new THREE.Vector2();
|
|
|
|
// Place block
|
|
document.addEventListener('contextmenu', (event) => {
|
|
event.preventDefault();
|
|
if (controls.isLocked) {
|
|
raycaster.setFromCamera(mouse, camera);
|
|
const intersects = raycaster.intersectObjects(scene.children);
|
|
|
|
if (intersects.length > 0) {
|
|
const intersect = intersects[0];
|
|
const position = intersect.point.add(intersect.face.normal);
|
|
const roundedPos = position.round();
|
|
|
|
// Check if block placement is not too close to player
|
|
const playerPos = camera.position.clone();
|
|
if (playerPos.distanceTo(roundedPos) > 2) {
|
|
const selectedType = inventory.slots[inventory.selectedSlot].type;
|
|
createBlock(
|
|
roundedPos.x,
|
|
roundedPos.y,
|
|
roundedPos.z,
|
|
selectedType
|
|
);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// Break block
|
|
document.addEventListener('click', (event) => {
|
|
if (controls.isLocked) {
|
|
raycaster.setFromCamera(mouse, camera);
|
|
const intersects = raycaster.intersectObjects(scene.children);
|
|
|
|
if (intersects.length > 0) {
|
|
const intersect = intersects[0];
|
|
const block = intersect.object;
|
|
|
|
// Prevent breaking bedrock
|
|
if (block.userData.type !== 'bedrock') {
|
|
scene.remove(block);
|
|
blocks.delete(`${block.position.x},${block.position.y},${block.position.z}`);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// Player physics and movement
|
|
function movePlayer(delta) {
|
|
if (controls.isLocked) {
|
|
// Gravity
|
|
velocity.y -= 20 * delta; // Minecraft-like gravity
|
|
|
|
// Forward/backward movement
|
|
const direction = new THREE.Vector3();
|
|
direction.z = Number(moveForward) - Number(moveBackward);
|
|
direction.x = Number(moveRight) - Number(moveLeft);
|
|
direction.normalize();
|
|
|
|
// Apply movement based on camera direction
|
|
if (moveForward || moveBackward) {
|
|
velocity.z -= direction.z * 20 * delta;
|
|
}
|
|
if (moveLeft || moveRight) {
|
|
velocity.x -= direction.x * 20 * delta;
|
|
}
|
|
|
|
// Apply friction
|
|
velocity.x *= 0.9;
|
|
velocity.z *= 0.9;
|
|
|
|
// Collision detection
|
|
const futurePosition = camera.position.clone();
|
|
futurePosition.x += velocity.x * delta;
|
|
futurePosition.y += velocity.y * delta;
|
|
futurePosition.z += velocity.z * delta;
|
|
|
|
// Check for collisions with blocks
|
|
const playerRadius = 0.3;
|
|
const checkPositions = [
|
|
[0, 0, 0], // Center
|
|
[playerRadius, 0, playerRadius], // Corners
|
|
[-playerRadius, 0, playerRadius],
|
|
[playerRadius, 0, -playerRadius],
|
|
[-playerRadius, 0, -playerRadius]
|
|
];
|
|
|
|
let collision = false;
|
|
for (const [offsetX, offsetY, offsetZ] of checkPositions) {
|
|
const checkPos = futurePosition.clone().add(new THREE.Vector3(offsetX, offsetY, offsetZ));
|
|
const blockPos = checkPos.clone().floor();
|
|
if (blocks.has(`${blockPos.x},${blockPos.y},${blockPos.z}`)) {
|
|
collision = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Update position if no collision
|
|
if (!collision) {
|
|
controls.moveRight(-velocity.x * delta);
|
|
controls.moveForward(-velocity.z * delta);
|
|
camera.position.y += velocity.y * delta;
|
|
} else {
|
|
velocity.setX(0);
|
|
velocity.setZ(0);
|
|
if (velocity.y < 0) {
|
|
velocity.setY(0);
|
|
canJump = true;
|
|
}
|
|
}
|
|
|
|
// Prevent falling through ground
|
|
if (camera.position.y < playerHeight) {
|
|
camera.position.y = playerHeight;
|
|
velocity.y = 0;
|
|
canJump = true;
|
|
}
|
|
|
|
// Update HUD
|
|
document.getElementById('coordinates').textContent =
|
|
`Position: ${Math.round(camera.position.x)}, ${Math.round(camera.position.y)}, ${Math.round(camera.position.z)}`;
|
|
document.getElementById('selected-block').textContent =
|
|
`Selected: ${inventory.slots[inventory.selectedSlot].type}`;
|
|
}
|
|
}
|
|
// ... (previous code for controls, movement, and interaction remains unchanged)
|
|
|
|
// Update animation loop with fog
|
|
function animate() {
|
|
requestAnimationFrame(animate);
|
|
const delta = Math.min(0.1, clock.getDelta());
|
|
movePlayer(delta);
|
|
|
|
// Update fog based on time of day (simplified)
|
|
const time = Date.now() * 0.001;
|
|
const fogColor = new THREE.Color(0x8CB4DB);
|
|
scene.fog.color = fogColor;
|
|
scene.background = fogColor;
|
|
|
|
renderer.render(scene, camera);
|
|
}
|
|
|
|
// Initialize the game
|
|
generateTerrain();
|
|
animate();
|
|
|
|
// Handle window resize
|
|
window.addEventListener('resize', () => {
|
|
camera.aspect = window.innerWidth / window.innerHeight;
|
|
camera.updateProjectionMatrix();
|
|
renderer.setSize(window.innerWidth, window.innerHeight);
|
|
});
|
|
</script>
|
|
</body>
|
|
</html> |