started building the game.
This commit is contained in:
+289
@@ -0,0 +1,289 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Scrap-Garden Prototype: Phase 1</title>
|
||||
<style>
|
||||
body { margin: 0; overflow: hidden; background-color: #000; font-family: monospace; }
|
||||
#instructions {
|
||||
position: absolute;
|
||||
top: 50%; width: 100%;
|
||||
text-align: center;
|
||||
color: white;
|
||||
pointer-events: none;
|
||||
font-size: 24px;
|
||||
transform: translateY(-50%);
|
||||
background: rgba(0,0,0,0.5);
|
||||
padding: 20px 0;
|
||||
}
|
||||
#reticle {
|
||||
position: absolute;
|
||||
top: 50%; left: 50%;
|
||||
width: 10px; height: 10px;
|
||||
background: yellow;
|
||||
border-radius: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
pointer-events: none;
|
||||
opacity: 0.8;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="reticle"></div>
|
||||
<div id="instructions">CLICK TO START</div>
|
||||
|
||||
<script type="module">
|
||||
import * as THREE from 'https://unpkg.com/three@0.150.0/build/three.module.js';
|
||||
|
||||
// --- GLOBAL VARIABLES ---
|
||||
let camera, scene, renderer;
|
||||
let player, playerVelocity, playerDirection;
|
||||
let moveForward = false, moveBackward = false, moveLeft = false, moveRight = false, canJump = false;
|
||||
let bullets = [];
|
||||
let prevTime = performance.now();
|
||||
|
||||
// Configuration
|
||||
const PLAYER_SPEED = 20.0;
|
||||
const JUMP_FORCE = 15.0;
|
||||
const GRAVITY = 40.0;
|
||||
const BULLET_SPEED = 50.0;
|
||||
|
||||
init();
|
||||
animate();
|
||||
|
||||
function init() {
|
||||
// 1. SETUP SCENE
|
||||
scene = new THREE.Scene();
|
||||
scene.background = new THREE.Color(0x87CEEB); // Sky Blue (Aether Sky)
|
||||
scene.fog = new THREE.Fog(0x87CEEB, 10, 80); // Fog for that MML/Silo vibe
|
||||
|
||||
// 2. SETUP CAMERA (Third Person)
|
||||
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
|
||||
|
||||
// 3. RENDERER
|
||||
renderer = new THREE.WebGLRenderer({ antialias: true });
|
||||
renderer.setPixelRatio(window.devicePixelRatio);
|
||||
renderer.setSize(window.innerWidth, window.innerHeight);
|
||||
renderer.shadowMap.enabled = true;
|
||||
document.body.appendChild(renderer.domElement);
|
||||
|
||||
// 4. LIGHTING
|
||||
const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
|
||||
scene.add(ambientLight);
|
||||
|
||||
const dirLight = new THREE.DirectionalLight(0xffffff, 0.8);
|
||||
dirLight.position.set(50, 100, 50);
|
||||
dirLight.castShadow = true;
|
||||
scene.add(dirLight);
|
||||
|
||||
// 5. THE WORLD (Ground & Obstacles)
|
||||
// Ground
|
||||
const floorGeo = new THREE.PlaneGeometry(200, 200);
|
||||
const floorMat = new THREE.MeshStandardMaterial({ color: 0x555555 }); // Grey Concrete
|
||||
const floor = new THREE.Mesh(floorGeo, floorMat);
|
||||
floor.rotation.x = -Math.PI / 2;
|
||||
floor.receiveShadow = true;
|
||||
scene.add(floor);
|
||||
|
||||
// Obstacles (Random Blue Cubes)
|
||||
const boxGeo = new THREE.BoxGeometry(4, 4, 4);
|
||||
const boxMat = new THREE.MeshStandardMaterial({ color: 0x0044aa });
|
||||
|
||||
for (let i = 0; i < 50; i++) {
|
||||
const box = new THREE.Mesh(boxGeo, boxMat);
|
||||
// Random position spread out, avoiding center
|
||||
const x = (Math.random() - 0.5) * 150;
|
||||
const z = (Math.random() - 0.5) * 150;
|
||||
// Keep center clear for spawn
|
||||
if(Math.abs(x) < 10 && Math.abs(z) < 10) continue;
|
||||
|
||||
box.position.set(x, 2, z); // y=2 is half height
|
||||
box.castShadow = true;
|
||||
box.receiveShadow = true;
|
||||
scene.add(box);
|
||||
}
|
||||
|
||||
// 6. THE PLAYER
|
||||
// Group holds the camera and mesh handles visual
|
||||
player = new THREE.Group();
|
||||
scene.add(player);
|
||||
|
||||
// Visual Mesh (Green Capsule)
|
||||
const playerGeo = new THREE.CapsuleGeometry(1, 2, 4, 8);
|
||||
const playerMat = new THREE.MeshStandardMaterial({ color: 0x33ff33 });
|
||||
const playerMesh = new THREE.Mesh(playerGeo, playerMat);
|
||||
playerMesh.position.y = 2; // Lift half height + radius so it stands on floor
|
||||
playerMesh.castShadow = true;
|
||||
player.add(playerMesh);
|
||||
|
||||
// Attach Camera to Player (The MML "Tight Follow")
|
||||
// Positioned behind and slightly up
|
||||
camera.position.set(0, 5, 8);
|
||||
camera.lookAt(0, 2, 0); // Look at player's back/head
|
||||
player.add(camera);
|
||||
|
||||
playerVelocity = new THREE.Vector3();
|
||||
playerDirection = new THREE.Vector3();
|
||||
|
||||
// 7. EVENTS & CONTROLS
|
||||
setupControls();
|
||||
|
||||
window.addEventListener('resize', onWindowResize);
|
||||
}
|
||||
|
||||
function setupControls() {
|
||||
// Pointer Lock (Click to capture mouse)
|
||||
const instructions = document.getElementById('instructions');
|
||||
|
||||
document.addEventListener('click', () => {
|
||||
document.body.requestPointerLock();
|
||||
});
|
||||
|
||||
document.addEventListener('pointerlockchange', () => {
|
||||
if (document.pointerlockElement === document.body) {
|
||||
instructions.style.display = 'none';
|
||||
} else {
|
||||
instructions.style.display = 'block';
|
||||
}
|
||||
});
|
||||
|
||||
// Mouse Movement (Rotates Player Group)
|
||||
document.addEventListener('mousemove', (event) => {
|
||||
if (document.pointerlockElement === document.body) {
|
||||
// Rotate player on Y axis (Yaw)
|
||||
// Negative movementX because we want to turn left when moving mouse left
|
||||
player.rotation.y -= event.movementX * 0.002;
|
||||
|
||||
// Simple vertical camera tilt (Optional constraint)
|
||||
// (Skipping vertical look for pure MML feel, but usually desired)
|
||||
}
|
||||
});
|
||||
|
||||
// Keyboard
|
||||
const onKeyDown = (event) => {
|
||||
switch (event.code) {
|
||||
case 'KeyW': moveForward = true; break;
|
||||
case 'KeyA': moveLeft = true; break;
|
||||
case 'KeyS': moveBackward = true; break;
|
||||
case 'KeyD': moveRight = true; break;
|
||||
case 'Space':
|
||||
if (canJump === true) {
|
||||
playerVelocity.y += JUMP_FORCE;
|
||||
canJump = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
const onKeyUp = (event) => {
|
||||
switch (event.code) {
|
||||
case 'KeyW': moveForward = false; break;
|
||||
case 'KeyA': moveLeft = false; break;
|
||||
case 'KeyS': moveBackward = false; break;
|
||||
case 'KeyD': moveRight = false; break;
|
||||
}
|
||||
};
|
||||
|
||||
// Shooting
|
||||
const onMouseDown = (event) => {
|
||||
if (document.pointerlockElement === document.body && event.button === 0) {
|
||||
shootBullet();
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('keydown', onKeyDown);
|
||||
document.addEventListener('keyup', onKeyUp);
|
||||
document.addEventListener('mousedown', onMouseDown);
|
||||
}
|
||||
|
||||
function shootBullet() {
|
||||
// Create Bullet Geometry
|
||||
const bulletGeo = new THREE.SphereGeometry(0.3, 8, 8);
|
||||
const bulletMat = new THREE.MeshBasicMaterial({ color: 0xffff00 });
|
||||
const bullet = new THREE.Mesh(bulletGeo, bulletMat);
|
||||
|
||||
// Start bullet at player position (slightly in front and up)
|
||||
// We need to get the player's world position and rotation
|
||||
const spawnPos = new THREE.Vector3(0, 3, -2).applyMatrix4(player.matrixWorld);
|
||||
bullet.position.copy(spawnPos);
|
||||
|
||||
// Calculate Velocity based on player rotation
|
||||
const velocity = new THREE.Vector3(0, 0, -1);
|
||||
velocity.applyQuaternion(player.quaternion);
|
||||
velocity.multiplyScalar(BULLET_SPEED);
|
||||
|
||||
scene.add(bullet);
|
||||
|
||||
// Add to array
|
||||
bullets.push({ mesh: bullet, velocity: velocity, creationTime: performance.now() });
|
||||
}
|
||||
|
||||
function onWindowResize() {
|
||||
camera.aspect = window.innerWidth / window.innerHeight;
|
||||
camera.updateProjectionMatrix();
|
||||
renderer.setSize(window.innerWidth, window.innerHeight);
|
||||
}
|
||||
|
||||
function animate() {
|
||||
requestAnimationFrame(animate);
|
||||
|
||||
const time = performance.now();
|
||||
const delta = (time - prevTime) / 1000;
|
||||
|
||||
// --- PLAYER MOVEMENT PHYSICS ---
|
||||
|
||||
// Friction/Damping
|
||||
playerVelocity.x -= playerVelocity.x * 10.0 * delta;
|
||||
playerVelocity.z -= playerVelocity.z * 10.0 * delta;
|
||||
playerVelocity.y -= 9.8 * 2.0 * delta; // Gravity
|
||||
|
||||
// Input Direction
|
||||
playerDirection.z = Number(moveForward) - Number(moveBackward);
|
||||
playerDirection.x = Number(moveRight) - Number(moveLeft);
|
||||
playerDirection.normalize(); // ensure consistent speed in all directions
|
||||
|
||||
if (moveForward || moveBackward) playerVelocity.z -= playerDirection.z * PLAYER_SPEED * delta;
|
||||
if (moveLeft || moveRight) playerVelocity.x -= playerDirection.x * PLAYER_SPEED * delta;
|
||||
|
||||
// Apply movement (Local to World)
|
||||
// We move the player Group based on its rotation
|
||||
const forward = new THREE.Vector3(0, 0, -1).applyQuaternion(player.quaternion);
|
||||
const right = new THREE.Vector3(1, 0, 0).applyQuaternion(player.quaternion);
|
||||
|
||||
// Re-map inputs to forward/right vectors
|
||||
// If pressing 'W' (playerDirection.z is 1), we move along forward vector
|
||||
if(moveForward) player.position.addScaledVector(forward, PLAYER_SPEED * delta * 0.5);
|
||||
if(moveBackward) player.position.addScaledVector(forward, -PLAYER_SPEED * delta * 0.5);
|
||||
if(moveRight) player.position.addScaledVector(right, PLAYER_SPEED * delta * 0.5);
|
||||
if(moveLeft) player.position.addScaledVector(right, -PLAYER_SPEED * delta * 0.5);
|
||||
|
||||
// Apply Jump/Gravity
|
||||
player.position.y += playerVelocity.y * delta;
|
||||
|
||||
// Floor Collision
|
||||
if (player.position.y < 0) {
|
||||
playerVelocity.y = 0;
|
||||
player.position.y = 0;
|
||||
canJump = true;
|
||||
}
|
||||
|
||||
// --- BULLET LOGIC ---
|
||||
for (let i = bullets.length - 1; i >= 0; i--) {
|
||||
const b = bullets[i];
|
||||
// Move bullet
|
||||
b.mesh.position.addScaledVector(b.velocity, delta);
|
||||
|
||||
// Remove if too old (2 seconds)
|
||||
if (time - b.creationTime > 2000) {
|
||||
scene.remove(b.mesh);
|
||||
bullets.splice(i, 1);
|
||||
}
|
||||
}
|
||||
|
||||
prevTime = time;
|
||||
renderer.render(scene, camera);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user