One Shot 3D Games You Can Test Immediately using Three.JS Grok and Claude
Community Article
Published
April 17, 2025
I found myself wondering if I should try yet again for multiplayer games while rolling the dice and trying to write a few examples then upgrading them in my style knowledge having played everything over the years.
First try - quite a worthy attempt: https://huggingface.co/spaces/awacke1/GameOneShotsWithThreeJS
Grok
I'll create a simple Pac-Man style game using HTML5 canvas and JavaScript. The game will include a player (Pac-Man), a maze, collectible dots, and basic movement controls.
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Pac```html
ac-Man Game</title>
<style>
canvas {
border: 1px solid black;
display: block;
margin: 0 auto;
background-color: #000;
}
body {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
background-color: #f0f0f0;
}
#score {
position: absolute;
top: 10px;
left: 10px;
font-family: Arial, sans-serif;
font-size: 24px;
color: #000;
}
</style>
</head>
<body>
<div id="score">Score: 0</div>
<canvas id="gameCanvas" width="448" height="496"></canvas>
<script>
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
const tileSize = 16;
const map = [
[1,1,1,1,1,1,1,1,1,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,2,2,2,2,1,1,2,2,2,2,2,2,2,2,2,2,2,2,1],
[1,2,1,1,1,1,2,1,1,1,1,1,2,1,1,2,1,1,1,1,1,2,1,1,1,1,2,1],
[1,2,1,0,0,1,2,1,0,0,0,1,2,1,1,2,1,0,0,0,1,2,1,0,0,1,2,1],
[1,2,1,1,1,1,2,1,1,1,1,1,2,1,1,2,1,1,1,1,1,2,1,1,1,1,2,1],
[1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1],
[1,2,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,1,1,1,1,2,1],
[1,2,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,1,1,1,1,2,1],
[1,2,2,2,2,2,2,1,1,2,2,2,2,1,1,2,2,2,2,1,1,2,2,2,2,2,2,1],
[1,1,1,1,1,1,2,1,1,2,1,1,1,1,1,1,1,1,2,1,1,2,1,1,1,1,1,1],
[0,0,0,0,0,1,2,1,1,2,1,0,0,0,0,0,0,0,2,1,1,2,1,0,0,0,0,0],
[0,0,0,0,0,1,2,1,1,2,1,0,0,0,0,0,0,0,2,1,1,2,1,0,0,0,0,0],
[1,1,1,1,1,1,2,1,1,2,1,0,0,0,0,0,0,0,2,1,1,2,1,1,1,1,1,1],
[2,2,2,2,2,2,2,2,2,2,1,0,0,0,0,0,0,0,2,2,2,2,2,2,2,2,2,2],
[1,1,1,1,1,1,2,1,1,2,1,0,0,0,0,0,0,0,2,1,1,2,1,1,1,1,1,1],
[0,0,0,0,0,1,2,1,1,2,1,0,0,0,0,0,0,0,2,1,1,2,1,0,0,0,0,0],
[0,0,0,0,0,1,2,1,1,2,1,0,0,0,0,0,0,0,2,1,1,2,1,0,0,0,0,0],
[1,1,1,1,1,1,2,1,1,2,1,1,1,1,1,1,1,1,2,1,1,2,1,1,1,1,1,1],
[1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1],
[1,2,1,1,1,1,2,1,1,1,1,1,2,1,1,2,1,1,1,1,1,2,1,1,1,1,2,1],
[1,2,1,1,1,1,2,1,1,1,1,1,2,1,1,2,1,1,1,1,1,2,1,1,1,1,2,1],
[1,2,2,2,2,2,2,1,1,2,2,2,2,2,2,2,2,2,2,1,1,2,2,2,2,2,2,1],
[1,2,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,2,1,1,1,1,1,1,2,1],
[1,2,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,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,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,1,1,1,1,1,1,1,1,1]
];
let player = {
x: 14,
y: 23,
direction: 'right',
nextDirection: 'right',
speed: 0.1,
mouthAngle: 0,
mouthSpeed: 0.1
};
let score = 0;
const scoreDisplay = document.getElementById('score');
function drawMap() {
for (let y = 0; y < map.length; y++) {
for (let x = 0; x < map[y].length; x++) {
if (map[y][x] === 1) {
ctx.fillStyle = 'blue';
ctx.fillRect(x * tileSize, y * tileSize, tileSize, tileSize);
} else if (map[y][x] === 2) {
ctx.fillStyle = 'yellow';
ctx.beginPath();
ctx.arc(x * tileSize + tileSize / 2, y * tileSize + tileSize / 2, 2, 0, Math.PI * 2);
ctx.fill();
}
}
}
}
function drawPlayer() {
ctx.fillStyle = 'yellow';
ctx.beginPath();
const centerX = player.x * tileSize + tileSize / 2;
const centerY = player.y * tileSize + tileSize / 2;
let startAngle, endAngle;
if (player.direction === 'right') {
startAngle = player.mouthAngle;
endAngle = 2 * Math.PI - player.mouthAngle;
} else if (player.direction === 'left') {
startAngle = Math.PI + player.mouthAngle;
endAngle = Math.PI - player.mouthAngle;
} else if (player.direction === 'up') {
startAngle = Math.PI * 1.5 + player.mouthAngle;
endAngle = Math.PI * 1.5 - player.mouthAngle;
} else {
startAngle = Math.PI / 2 + player.mouthAngle;
endAngle = Math.PI / 2 - player.mouthAngle;
}
ctx.arc(centerX, centerY, tileSize / 2, startAngle, endAngle);
ctx.lineTo(centerX, centerY);
ctx.fill();
player.mouthAngle += player.mouthSpeed;
if (player.mouthAngle > Math.PI / 4 || player.mouthAngle < 0) {
player.mouthSpeed = -player.mouthSpeed;
}
}
function canMove(x, y) {
const tileX = Math.floor(x);
const tileY = Math.floor(y);
if (tileX < 0 || tileX >= map[0].length || tileY < 0 || tileY >= map.length) return false;
return map[tileY][tileX] !== 1;
}
function collectDot() {
const tileX = Math.floor(player.x);
const tileY = Math.floor(player.y);
if (map[tileY][tileX] === 2) {
map[tileY][tileX] = 0;
score += 10;
scoreDisplay.textContent = `Score: ${score}`;
}
}
function updatePlayer() {
let nextX = player.x;
let nextY = player.y;
if (player.nextDirection === 'right' && canMove(player.x + player.speed, player.y)) {
player.direction = 'right';
nextX += player.speed;
} else if (player.nextDirection === 'left' && canMove(player.x - player.speed, player.y)) {
player.direction = 'left';
nextX -= player.speed;
} else if (player.nextDirection === 'up' && canMove(player.x, player.y - player.speed)) {
player.direction = 'up';
nextY -= player.speed;
} else if (player.nextDirection === 'down' && canMove(player.x, player.y + player.speed)) {
player.direction = 'down';
nextY += player.speed;
}
if (player.direction === 'right' && canMove(nextX + player.speed, player.y)) {
nextX += player.speed;
} else if (player.direction === 'left' && canMove(nextX - player.speed, player.y)) {
nextX -= player.speed;
} else if (player.direction === 'up' && canMove(player.x, nextY - player.speed)) {
nextY -= player.speed;
} else if (player.direction === 'down' && canMove(player.x, nextY + player.speed)) {
nextY += player.speed;
}
player.x = nextX;
player.y = nextY;
collectDot();
}
document.addEventListener('keydown', (e) => {
switch (e.key) {
case 'ArrowRight':
player.nextDirection = 'right';
break;
case 'ArrowLeft':
player.nextDirection = 'left';
break;
case 'ArrowUp':
player.nextDirection = 'up';
break;
case 'ArrowDown':
player.nextDirection = 'down';
break;
}
});
function gameLoop() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawMap();
updatePlayer();
drawPlayer();
requestAnimationFrame(gameLoop);
}
gameLoop();
</script>
</body>
</html>
And then trying a second version educated on the first.
Quite a great second go with Claude to one-shot a great 3D in same tech which is immediately verifiable.
Code:
<!DOCTYPE html>
<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>
Final Space with Code:
https://huggingface.co/spaces/awacke1/GameOneShotsWithThreeJS
I find the magic of Three.JS indisputable for an amazing effect of using 3D.
Enjoy!
<video controls autoplay src="
">Aaron