setup()
setup() is called once when the sketch starts. It initializes both rendering systems (p5.js and Three.js), creates all permanent objects (player, lights, grid), sets up audio, and starts the animation loop. Understanding how both libraries initialize together is key to building hybrid sketches.
🔬 These three numbers define the grid's structure: gridSize is how many cubes in each direction (20x20), cubeSize is how big each cube is, and spacing is the distance between cube centers. If you change gridSize to 10, what happens to the arena size and complexity?
const gridSize = 20;
const cubeSize = 3;
const spacing = 5;
🔬 The noise values at x*0.1 and z*0.1 control how the height varies - smaller multipliers create smoother, larger features. What happens if you change 0.1 to 0.05, making the noise output change more slowly across the grid?
for (let x = 0; x < gridSize; x++) {
for (let z = 0; z < gridSize; z++) {
const height = (noise(x * 0.1, z * 0.1) * 20) + threeGroundY;
function setup() {
// 1. p5.js Setup (for UI and logic)
const p5Canvas = createCanvas(windowWidth, windowHeight);
p5Canvas.id('p5Canvas'); // Assign an ID for CSS styling
// noCanvas(); // DO NOT call noCanvas() here, p5.js canvas is needed for UI
// Initialize player
player = new Player(width / 2, height - 50, 60, 20); // p5.js coords
// Initialize sounds
// Shoot sound: short, high-pitched sine wave
shootSound = new p5.Oscillator();
shootSound.setType('sine');
shootSound.freq(800);
shootSound.amp(0); // Start with 0 volume
shootSound.start();
// Hit sound: very short, slightly lower-pitched sine wave
hitSound = new p5.Oscillator();
hitSound.setType('sine');
hitSound.freq(400);
hitSound.amp(0);
hitSound.start();
// Explosion sound: short, low-pitched noise
explosionSound = new p5.Noise('white');
explosionSound.amp(0);
explosionSound.start();
// Game Over sound: sustained low-pitched square wave
gameOverSound = new p5.Oscillator();
gameOverSound.setType('square');
gameOverSound.freq(100);
gameOverSound.amp(0);
gameOverSound.start();
// Start audio context on user gesture (important for p5.sound)
userStartAudio();
// 2. three.js Setup (for 3D rendering)
scene = new THREE.Scene();
scene.background = new THREE.Color(0x1a1a1a); // Dark gray background
// Camera Setup
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 80, 70); // Position the camera to look down at the game arena
camera.lookAt(0, threeGroundY, threeEnemySpawnZ / 2); // Look towards the center of the arena
// Renderer Setup
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.domElement.id = 'threeCanvas'; // Give it an ID to differentiate from p5.js canvas
document.body.appendChild(renderer.domElement); // Add the three.js canvas to the DOM
// Move p5.js canvas on top
document.body.appendChild(p5Canvas.elt);
// Lighting
const ambientLight = new THREE.AmbientLight(0x404040); // Soft white light
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8); // White light, medium intensity
directionalLight.position.set(100, 100, 100); // Position it to cast light from the top-right
scene.add(directionalLight);
// Create Procedural Geometry (veck.io style grid)
gridGroup = new THREE.Group(); // Use a group to hold all the cubes
const gridSize = 20;
const cubeSize = 3;
const spacing = 5;
const colors = [
new THREE.Color(0x333333), new THREE.Color(0x555555),
new THREE.Color(0xaaaaaa), new THREE.Color(0xdddddd),
new THREE.Color(0xffffff)
];
for (let x = 0; x < gridSize; x++) {
for (let z = 0; z < gridSize; z++) {
const height = (noise(x * 0.1, z * 0.1) * 20) + threeGroundY; // Use p5.js noise for organic height, ensure base is at threeGroundY
const geometry = new THREE.BoxGeometry(cubeSize, height, cubeSize);
const colorIndex = floor(noise(x * 0.2, z * 0.2) * colors.length);
const material = new THREE.MeshStandardMaterial({
color: colors[colorIndex], metalness: 0.1, roughness: 0.6, emissive: new THREE.Color(0x000000)
});
const cube = new THREE.Mesh(geometry, material);
cube.position.x = (x - gridSize / 2) * spacing;
cube.position.y = height / 2; // Position so base is on the ground plane
cube.position.z = (z - gridSize / 2) * spacing;
gridGroup.add(cube);
}
}
scene.add(gridGroup);
// Camera Controls (initially enabled for start screen)
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
controls.screenSpacePanning = false;
controls.minDistance = 10;
controls.maxDistance = 200;
controls.maxPolarAngle = Math.PI / 2;
// Start the animation loop (three.js loop)
animate();
}
Line-by-line explanation (27 lines)
🔧 Subcomponents:
const p5Canvas = createCanvas(windowWidth, windowHeight);
Creates a p5.js canvas matching the window size for UI and text overlay
player = new Player(width / 2, height - 50, 60, 20);
Creates the Player object at the bottom-center of the screen with size 60x20
shootSound = new p5.Oscillator();
Initializes four p5.sound oscillators and noise generators for game audio
scene = new THREE.Scene();
Creates a Three.js scene that will hold all 3D objects
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
Creates a camera with 75° field of view positioned to look down at the game arena
for (let x = 0; x < gridSize; x++) {
for (let z = 0; z < gridSize; z++) {
Creates a 20x20 grid of cubes with varying heights using p5.js noise for organic appearance
const p5Canvas = createCanvas(windowWidth, windowHeight);- Creates a p5.js canvas that fills the entire window; stored in p5Canvas so we can manipulate it later
p5Canvas.id('p5Canvas');- Assigns the HTML id 'p5Canvas' to the p5.js canvas so CSS styling can target it
player = new Player(width / 2, height - 50, 60, 20);- Instantiates a new Player object centered horizontally and positioned 50 pixels from the bottom of the screen
shootSound = new p5.Oscillator();- Creates a new oscillator that will produce the shooting sound when amplitude is increased
shootSound.setType('sine');- Sets the oscillator to produce a smooth sine wave tone
shootSound.freq(800);- Sets the oscillator's frequency to 800 Hz, a high-pitched tone for the shoot sound
shootSound.amp(0);- Starts with amplitude 0 so no sound plays until explicitly triggered later
shootSound.start();- Begins the oscillator running continuously; its volume is controlled by amp() calls later
userStartAudio();- Activates the Web Audio Context in response to user interaction, required by browsers to play sound
scene = new THREE.Scene();- Creates an empty Three.js scene container that will hold all 3D objects
scene.background = new THREE.Color(0x1a1a1a);- Sets the scene's background color to dark gray (0x1a1a1a)
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);- Creates a perspective camera with 75° field of view, aspect ratio matching the window, and viewing range from 0.1 to 1000 units
camera.position.set(0, 80, 70);- Positions the camera 80 units up and 70 units back so it looks down at the arena from above
renderer = new THREE.WebGLRenderer({ antialias: true });- Creates a Three.js renderer using WebGL with antialiasing enabled for smooth edges
renderer.setSize(window.innerWidth, window.innerHeight);- Sets the renderer output to match the full window dimensions
document.body.appendChild(renderer.domElement);- Adds the Three.js canvas to the HTML page as a background element
document.body.appendChild(p5Canvas.elt);- Adds the p5.js canvas on top of the Three.js canvas so UI text appears above the 3D scene
const ambientLight = new THREE.AmbientLight(0x404040);- Creates soft background lighting that illuminates all objects evenly from all directions
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);- Creates a bright directional light source (like sunlight) with 80% intensity
directionalLight.position.set(100, 100, 100);- Positions the light source at (100, 100, 100) so it casts shadows from the top-right direction
const height = (noise(x * 0.1, z * 0.1) * 20) + threeGroundY;- Uses p5.js noise() to generate a random height for each cube, scaled by 20 units, ensuring all cubes sit on the same ground level
const colorIndex = floor(noise(x * 0.2, z * 0.2) * colors.length);- Uses different noise parameters to pick a color from the colors array, creating visual variation across the grid
cube.position.y = height / 2;- Positions each cube so its bottom rests on the ground plane (Three.js positions objects from their center)
gridGroup.add(cube);- Adds the cube to the gridGroup so the entire grid can be rotated as one object later
controls = new THREE.OrbitControls(camera, renderer.domElement);- Creates camera controls that let the user orbit around the scene by dragging the mouse
controls.enableDamping = true;- Enables inertial damping so the camera continues moving smoothly after the user stops dragging
animate();- Starts the Three.js animation loop that continuously renders the scene