setup()
setup() runs once when the sketch loads and is where all initialization happens. It creates the scene, camera, renderer, lighting, and spawns the boxes. The setTimeout delay is crucial—it ensures Three.js libraries are fully loaded before trying to use PointerLockControls.
🔬 This creates the box shape and red color. What happens if you change 0xff0000 (red) to 0xffff00 (yellow) or 0x00ff00 (green)?
// Target Boxes
const boxGeometry = new THREE.BoxGeometry(20, 20, 20);
const boxMaterial = new THREE.MeshLambertMaterial({ color: 0xff0000 }); // Red targets
function setup() {
// Tell p5.js not to create its default canvas, as three.js will create its own
noCanvas();
// Get score and instruction elements from HTML
scoreContainer = document.getElementById('score-container'); // Get the score container
instructionsElement = document.getElementById('instructions-container'); // Get the instructions container
scoreElement = document.getElementById('score');
// Defer three.js setup to ensure scripts are fully loaded and PointerLockControls is defined
setTimeout(() => {
// --- three.js Scene Setup ---
scene = new THREE.Scene();
scene.background = new THREE.Color(0x87ceeb); // Sky blue background
scene.fog = new THREE.Fog(0x87ceeb, 0, 750); // Simple fog
camera = new THREE.PerspectiveCamera(75, windowWidth / windowHeight, 1, 1000);
camera.position.y = 10; // Player height
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(windowWidth, windowHeight);
document.body.appendChild(renderer.domElement); // Add three.js canvas to the body
// --- PointerLockControls for FPS movement ---
// This line is now inside the setTimeout, which helps resolve the constructor error
controls = new THREE.PointerLockControls(camera, renderer.domElement);
scene.add(controls.getObject()); // Add camera object to the scene
// Instructions for the user
instructionsElement.addEventListener('click', function () {
controls.lock(); // Lock the mouse when clicking on instructions
});
controls.addEventListener('lock', function () {
instructionsElement.style.display = 'none';
scoreContainer.style.display = 'block'; // Show score container
});
controls.addEventListener('unlock', function () {
instructionsElement.style.display = 'block';
scoreContainer.style.display = 'none'; // Hide score container
});
// --- Event Listeners for Movement ---
document.addEventListener('keydown', onKeyDown);
document.addEventListener('keyup', onKeyUp);
// --- Raycaster for Shooting ---
raycaster = new THREE.Raycaster();
document.addEventListener('mousedown', onMouseDown); // Listen for mouse click (shooting)
// --- Add Objects to Scene ---
// Ground
const floorGeometry = new THREE.PlaneGeometry(2000, 2000, 100, 100);
floorGeometry.rotateX(-Math.PI / 2); // Rotate to be horizontal
const floorMaterial = new THREE.MeshBasicMaterial({ color: 0x00ff00, wireframe: false }); // Green ground
const floor = new THREE.Mesh(floorGeometry, floorMaterial);
scene.add(floor);
// Target Boxes
const boxGeometry = new THREE.BoxGeometry(20, 20, 20);
const boxMaterial = new THREE.MeshLambertMaterial({ color: 0xff0000 }); // Red targets
for (let i = 0; i < 20; i++) {
const box = new THREE.Mesh(boxGeometry, boxMaterial);
box.position.x = random(-900, 900);
box.position.y = random(10, 50); // Vary height
box.position.z = random(-900, 900);
scene.add(box);
boxes.push(box);
}
// Simple Ambient Light
const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
scene.add(ambientLight);
// Simple Directional Light
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
directionalLight.position.set(100, 100, 100);
scene.add(directionalLight);
// Start the three.js animation loop
animate();
}, 0); // 0ms delay ensures this block runs after all scripts are processed
}
Line-by-line explanation (20 lines)
🔧 Subcomponents:
scene = new THREE.Scene();
scene.background = new THREE.Color(0x87ceeb);
scene.fog = new THREE.Fog(0x87ceeb, 0, 750);
Creates the 3D scene, sets sky blue background color, and adds fog for depth effect
camera = new THREE.PerspectiveCamera(75, windowWidth / windowHeight, 1, 1000);
camera.position.y = 10;
Creates a perspective camera with 75-degree field of view and positions it 10 units above ground (player height)
for (let i = 0; i < 20; i++) {
const box = new THREE.Mesh(boxGeometry, boxMaterial);
box.position.x = random(-900, 900);
box.position.y = random(10, 50);
box.position.z = random(-900, 900);
scene.add(box);
boxes.push(box);
}
Creates 20 red target boxes at random locations across the map and stores them in the boxes array for later collision detection
const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
directionalLight.position.set(100, 100, 100);
scene.add(directionalLight);
Adds ambient light (global brightness) and directional light (sun-like light from above) so objects are visible and have shadows
noCanvas();- Tells p5.js not to create a default 2D canvas—Three.js will create its own 3D canvas instead
scoreContainer = document.getElementById('score-container');- Grabs the HTML element that holds the score display so we can show/hide it when the pointer is locked/unlocked
setTimeout(() => { ... }, 0);- Delays the Three.js initialization by 0 milliseconds to ensure all external scripts (like PointerLockControls) are fully loaded before we try to use them
scene = new THREE.Scene();- Creates an empty 3D scene—the container for all objects, lights, and the camera
scene.background = new THREE.Color(0x87ceeb);- Sets the background color to sky blue (hex color 0x87ceeb) so the scene doesn't appear black
scene.fog = new THREE.Fog(0x87ceeb, 0, 750);- Adds fog that gradually hides objects far away, making distant boxes fade into the sky for a realistic distance effect
camera = new THREE.PerspectiveCamera(75, windowWidth / windowHeight, 1, 1000);- Creates a camera with a 75-degree field of view (how wide you can see), correct aspect ratio for the window, near clipping plane at 1 unit, and far at 1000 units
camera.position.y = 10;- Sets the camera 10 units above ground, simulating an average player height so you're not looking from the ground up
renderer = new THREE.WebGLRenderer({ antialias: true });- Creates a WebGL renderer that will actually draw the 3D scene; antialias smooths jagged edges
renderer.setSize(windowWidth, windowHeight);- Makes the renderer canvas fill the entire window so the game is fullscreen
document.body.appendChild(renderer.domElement);- Adds the Three.js canvas to the HTML page so it's visible
controls = new THREE.PointerLockControls(camera, renderer.domElement);- Creates FPS controls that let the mouse look around and locks the pointer to the window for immersive gameplay
scene.add(controls.getObject());- Adds the camera (inside the controls object) to the scene so it can move and rotate within the 3D world
instructionsElement.addEventListener('click', function () { controls.lock(); });- When the player clicks on the instructions text, lock the mouse pointer and hide the instructions, showing the score instead
for (let i = 0; i < 20; i++) {- Loop 20 times to create 20 target boxes
box.position.x = random(-900, 900);- Position the box randomly between -900 and 900 units on the X axis (left-right), spread across the map
box.position.y = random(10, 50);- Position the box at a random height between 10 and 50 units so boxes don't all sit at the same level
box.position.z = random(-900, 900);- Position the box randomly between -900 and 900 units on the Z axis (forward-backward)
scene.add(box);- Adds the box to the 3D scene so it's rendered and visible
boxes.push(box);- Stores the box in the boxes array so we can later check if the raycast hit any of them