Spaces:
Running
Running
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>3D Pac-Man Game</title> | |
<style> | |
body { | |
margin: 0; | |
overflow: hidden; | |
background-color: #000; | |
font-family: Arial, sans-serif; | |
} | |
#info { | |
position: absolute; | |
top: 10px; | |
left: 10px; | |
color: white; | |
font-size: 16px; | |
background-color: rgba(0, 0, 0, 0.5); | |
padding: 10px; | |
border-radius: 5px; | |
} | |
#gameOverScreen { | |
position: absolute; | |
top: 50%; | |
left: 50%; | |
transform: translate(-50%, -50%); | |
background-color: rgba(0, 0, 0, 0.8); | |
color: white; | |
padding: 20px; | |
border-radius: 10px; | |
text-align: center; | |
display: none; | |
} | |
#gameOverScreen button { | |
margin-top: 20px; | |
padding: 10px 20px; | |
background-color: yellow; | |
border: none; | |
border-radius: 5px; | |
cursor: pointer; | |
font-weight: bold; | |
} | |
#instructions { | |
position: absolute; | |
bottom: 10px; | |
left: 10px; | |
color: white; | |
font-size: 14px; | |
background-color: rgba(0, 0, 0, 0.5); | |
padding: 10px; | |
border-radius: 5px; | |
} | |
</style> | |
</head> | |
<body> | |
<div id="info">Score: <span id="score">0</span> | Dots Left: <span id="dotsLeft">0</span></div> | |
<div id="instructions">Controls: Arrow Keys to move, WASD to rotate camera, Q/E to raise/lower camera</div> | |
<div id="gameOverScreen"> | |
<h2>Game Over</h2> | |
<p>Your final score: <span id="finalScore">0</span></p> | |
<button id="restartButton">Play Again</button> | |
</div> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script> | |
<script> | |
// Game variables | |
let scene, camera, renderer, player, maze = []; | |
let score = 0, dotsLeft = 0; | |
let gameOver = false; | |
let cameraOffset = { x: 0, y: 5, z: 10 }; | |
let cameraRotation = { x: -0.5, y: 0, z: 0 }; | |
// Maze layout (1: wall, 2: dot, 0: empty space) | |
const map = [ | |
[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1], | |
[1,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,1], | |
[1,2,1,1,2,1,1,1,2,1,2,1,1,1,2,1,1,2,1], | |
[1,2,1,1,2,1,1,1,2,1,2,1,1,1,2,1,1,2,1], | |
[1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1], | |
[1,2,1,1,2,1,2,1,1,1,1,1,2,1,2,1,1,2,1], | |
[1,2,2,2,2,1,2,2,2,1,2,2,2,1,2,2,2,2,1], | |
[1,1,1,1,2,1,1,1,0,1,0,1,1,1,2,1,1,1,1], | |
[0,0,0,1,2,1,0,0,0,0,0,0,0,1,2,1,0,0,0], | |
[1,1,1,1,2,1,0,1,1,0,1,1,0,1,2,1,1,1,1], | |
[0,0,0,0,2,0,0,1,0,0,0,1,0,0,2,0,0,0,0], | |
[1,1,1,1,2,1,0,1,1,1,1,1,0,1,2,1,1,1,1], | |
[0,0,0,1,2,1,0,0,0,0,0,0,0,1,2,1,0,0,0], | |
[1,1,1,1,2,1,0,1,1,1,1,1,0,1,2,1,1,1,1], | |
[1,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,1], | |
[1,2,1,1,2,1,1,1,2,1,2,1,1,1,2,1,1,2,1], | |
[1,2,2,1,2,2,2,2,2,0,2,2,2,2,2,1,2,2,1], | |
[1,1,2,1,2,1,2,1,1,1,1,1,2,1,2,1,2,1,1], | |
[1,2,2,2,2,1,2,2,2,1,2,2,2,1,2,2,2,2,1], | |
[1,2,1,1,1,1,1,1,2,1,2,1,1,1,1,1,1,2,1], | |
[1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1], | |
[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1] | |
]; | |
// Initialize the game | |
function init() { | |
// Reset game variables | |
score = 0; | |
document.getElementById('score').textContent = score; | |
gameOver = false; | |
document.getElementById('gameOverScreen').style.display = 'none'; | |
maze = []; | |
dotsLeft = 0; | |
// Create a scene | |
scene = new THREE.Scene(); | |
scene.background = new THREE.Color(0x000000); | |
// Create a camera | |
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); | |
camera.position.set(cameraOffset.x, cameraOffset.y, cameraOffset.z); | |
camera.rotation.x = cameraRotation.x; | |
// Create a renderer | |
renderer = new THREE.WebGLRenderer({ antialias: true }); | |
renderer.setSize(window.innerWidth, window.innerHeight); | |
if (document.querySelector('canvas')) { | |
document.body.removeChild(document.querySelector('canvas')); | |
} | |
document.body.appendChild(renderer.domElement); | |
// Add lights | |
const ambientLight = new THREE.AmbientLight(0x404040, 1); | |
scene.add(ambientLight); | |
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.5); | |
directionalLight.position.set(0, 20, 10); | |
scene.add(directionalLight); | |
// Create the maze | |
createMaze(); | |
// Create player (Pac-Man) | |
createPlayer(); | |
// Add event listeners | |
window.addEventListener('resize', onWindowResize); | |
window.addEventListener('keydown', onKeyDown); | |
document.getElementById('restartButton').addEventListener('click', init); | |
// Start animation loop | |
animate(); | |
} | |
function createMaze() { | |
// Ground | |
const groundGeometry = new THREE.PlaneGeometry(map[0].length + 2, map.length + 2); | |
const groundMaterial = new THREE.MeshLambertMaterial({ color: 0x333333, side: THREE.DoubleSide }); | |
const ground = new THREE.Mesh(groundGeometry, groundMaterial); | |
ground.rotation.x = Math.PI / 2; | |
ground.position.set(map[0].length / 2 - 0.5, -0.5, map.length / 2 - 0.5); | |
scene.add(ground); | |
// Create maze elements | |
for (let z = 0; z < map.length; z++) { | |
maze[z] = []; | |
for (let x = 0; x < map[z].length; x++) { | |
if (map[z][x] === 1) { | |
// Create wall | |
const wallGeometry = new THREE.BoxGeometry(1, 1, 1); | |
const wallMaterial = new THREE.MeshLambertMaterial({ color: 0x0000ff }); | |
const wall = new THREE.Mesh(wallGeometry, wallMaterial); | |
wall.position.set(x, 0, z); | |
scene.add(wall); | |
maze[z][x] = { type: 'wall', object: wall }; | |
} else if (map[z][x] === 2) { | |
// Create dot | |
const dotGeometry = new THREE.SphereGeometry(0.1, 8, 8); | |
const dotMaterial = new THREE.MeshLambertMaterial({ color: 0xffff00 }); | |
const dot = new THREE.Mesh(dotGeometry, dotMaterial); | |
dot.position.set(x, 0, z); | |
scene.add(dot); | |
maze[z][x] = { type: 'dot', object: dot, collected: false }; | |
dotsLeft++; | |
} else { | |
maze[z][x] = { type: 'empty' }; | |
} | |
} | |
} | |
document.getElementById('dotsLeft').textContent = dotsLeft; | |
} | |
function createPlayer() { | |
// Create Pac-Man body (sphere with mouth) | |
const playerGeometry = new THREE.SphereGeometry(0.4, 32, 32, 0, Math.PI * 1.8); | |
const playerMaterial = new THREE.MeshLambertMaterial({ color: 0xffff00 }); | |
player = new THREE.Mesh(playerGeometry, playerMaterial); | |
// Set initial position (middle of the maze) | |
player.position.set(9, 0, 10); | |
player.rotation.y = Math.PI / 2; // Face right initially | |
scene.add(player); | |
// Player properties | |
player.userData = { | |
direction: { x: 0, z: 0 }, | |
nextDirection: { x: 0, z: 0 }, | |
speed: 0.05, | |
mouthOpen: true, | |
mouthSpeed: 0.05 | |
}; | |
} | |
function movePlayer() { | |
if (gameOver) return; | |
// Try to move in the next direction if requested | |
if (player.userData.nextDirection.x !== 0 || player.userData.nextDirection.z !== 0) { | |
const nextX = Math.floor(player.position.x + player.userData.nextDirection.x * player.userData.speed * 2); | |
const nextZ = Math.floor(player.position.z + player.userData.nextDirection.z * player.userData.speed * 2); | |
// Check if the next position is valid | |
if (nextX >= 0 && nextX < map[0].length && | |
nextZ >= 0 && nextZ < map.length && | |
map[nextZ][nextX] !== 1) { | |
player.userData.direction = {...player.userData.nextDirection}; | |
// Rotate player to face the direction of movement | |
if (player.userData.direction.x === 1) player.rotation.y = Math.PI / 2; | |
else if (player.userData.direction.x === -1) player.rotation.y = -Math.PI / 2; | |
else if (player.userData.direction.z === 1) player.rotation.y = Math.PI; | |
else if (player.userData.direction.z === -1) player.rotation.y = 0; | |
} | |
} | |
// Move player in the current direction | |
if (player.userData.direction.x !== 0 || player.userData.direction.z !== 0) { | |
const newX = player.position.x + player.userData.direction.x * player.userData.speed; | |
const newZ = player.position.z + player.userData.direction.z * player.userData.speed; | |
// Check if we're about to hit a wall | |
const nextTileX = Math.floor(newX + player.userData.direction.x * 0.4); | |
const nextTileZ = Math.floor(newZ + player.userData.direction.z * 0.4); | |
if (nextTileX >= 0 && nextTileX < map[0].length && | |
nextTileZ >= 0 && nextTileZ < map.length && | |
map[nextTileZ][nextTileX] !== 1) { | |
player.position.x = newX; | |
player.position.z = newZ; | |
// Tunnel teleport (if you go off one side, appear on the other) | |
if (player.position.x < 0) player.position.x = map[0].length - 1; | |
if (player.position.x >= map[0].length) player.position.x = 0; | |
if (player.position.z < 0) player.position.z = map.length - 1; | |
if (player.position.z >= map.length) player.position.z = 0; | |
// Collect dots | |
collectDots(); | |
} | |
} | |
// Animate Pac-Man's mouth | |
animateMouth(); | |
// Update camera position | |
updateCamera(); | |
} | |
function animateMouth() { | |
// Create the mouth animation effect | |
const playerGeometry = player.geometry; | |
if (player.userData.mouthOpen) { | |
playerGeometry.parameters.thetaLength -= player.userData.mouthSpeed; | |
if (playerGeometry.parameters.thetaLength <= Math.PI) { | |
player.userData.mouthOpen = false; | |
} | |
} else { | |
playerGeometry.parameters.thetaLength += player.userData.mouthSpeed; | |
if (playerGeometry.parameters.thetaLength >= Math.PI * 1.8) { | |
player.userData.mouthOpen = true; | |
} | |
} | |
player.geometry = new THREE.SphereGeometry( | |
0.4, 32, 32, 0, playerGeometry.parameters.thetaLength | |
); | |
player.material = new THREE.MeshLambertMaterial({ color: 0xffff00 }); | |
} | |
function collectDots() { | |
const x = Math.round(player.position.x); | |
const z = Math.round(player.position.z); | |
// Check if there's a dot at the player's position | |
if (x >= 0 && x < map[0].length && z >= 0 && z < map.length) { | |
if (maze[z][x] && maze[z][x].type === 'dot' && !maze[z][x].collected) { | |
// Collect the dot | |
maze[z][x].collected = true; | |
scene.remove(maze[z][x].object); | |
// Update score | |
score += 10; | |
document.getElementById('score').textContent = score; | |
// Update dots left | |
dotsLeft--; | |
document.getElementById('dotsLeft').textContent = dotsLeft; | |
// Check if all dots are collected | |
if (dotsLeft === 0) { | |
// Player wins | |
gameOver = true; | |
document.getElementById('gameOverScreen').style.display = 'block'; | |
document.getElementById('finalScore').textContent = score; | |
} | |
} | |
} | |
} | |
function updateCamera() { | |
// Camera follows player with offset | |
camera.position.x = player.position.x + cameraOffset.x; | |
camera.position.y = player.position.y + cameraOffset.y; | |
camera.position.z = player.position.z + cameraOffset.z; | |
// Look at player | |
camera.lookAt(player.position.x, player.position.y, player.position.z); | |
} | |
function onKeyDown(event) { | |
if (gameOver) return; | |
// Movement keys | |
switch (event.key) { | |
case 'ArrowUp': | |
player.userData.nextDirection = { x: 0, z: -1 }; | |
break; | |
case 'ArrowDown': | |
player.userData.nextDirection = { x: 0, z: 1 }; | |
break; | |
case 'ArrowLeft': | |
player.userData.nextDirection = { x: -1, z: 0 }; | |
break; | |
case 'ArrowRight': | |
player.userData.nextDirection = { x: 1, z: 0 }; | |
break; | |
// Camera controls | |
case 'w': | |
cameraOffset.y += 0.5; | |
break; | |
case 's': | |
cameraOffset.y -= 0.5; | |
break; | |
case 'a': | |
cameraOffset.x -= 0.5; | |
break; | |
case 'd': | |
cameraOffset.x += 0.5; | |
break; | |
case 'q': | |
cameraOffset.z -= 0.5; | |
break; | |
case 'e': | |
cameraOffset.z += 0.5; | |
break; | |
} | |
} | |
function onWindowResize() { | |
camera.aspect = window.innerWidth / window.innerHeight; | |
camera.updateProjectionMatrix(); | |
renderer.setSize(window.innerWidth, window.innerHeight); | |
} | |
function animate() { | |
requestAnimationFrame(animate); | |
movePlayer(); | |
renderer.render(scene, camera); | |
} | |
// Start the game | |
init(); | |
</script> | |
</body> | |
</html> |