xxxxxxxxxx
319
let fishes = []; // Array to hold all the fish
let targetPoints = []; // Array to hold the target points for fish formations
let formationMode = true; // Boolean to switch between formation and scatter modes
let timer = 0; // Timer to track the duration in each mode
const FORMATION_DURATION = 300; // Duration for formation mode in frames
const SCATTER_DURATION = 200; // Duration for scatter mode in frames
const NUM_FISH = 200; // Total number of fish to create
function setup() {
createCanvas(windowWidth, windowHeight); // Create a canvas that fits the window size
colorMode(RGB); // Set color mode to RGB
// Define natural colors for the fish
const naturalColors = [
{ main: color(135, 162, 176), accent: color(100, 130, 150), fin: color(90, 120, 140) },
{ main: color(180, 190, 195), accent: color(150, 160, 165), fin: color(140, 150, 155) },
{ main: color(205, 170, 125), accent: color(175, 140, 95), fin: color(165, 130, 85) },
{ main: color(95, 110, 125), accent: color(75, 90, 105), fin: color(65, 80, 95) },
{ main: color(150, 75, 50), accent: color(130, 55, 30), fin: color(120, 45, 20) },
{ main: color(70, 90, 110), accent: color(50, 70, 90), fin: color(40, 60, 80) },
{ main: color(190, 160, 140), accent: color(170, 140, 120), fin: color(160, 130, 110) }
];
// Create fish and add them to the fishes array
for (let i = 0; i < NUM_FISH; i++) {
fishes.push(new Fish(
random(width), // Random x position
random(height), // Random y position
random(15, 25), // Random size between 15 and 25
random(naturalColors) // Randomly select a color set for the fish
));
}
generateFormationPoints(); // Generate the initial formation points for the fish
}
function draw() {
background(25, 45, 70); // Set a dark blue background
drawUnderwaterEffect(); // Call function to draw underwater visual effects
timer++; // Increment the timer
// Switch to scatter mode if in formation mode and time exceeds the duration
if (formationMode && timer > FORMATION_DURATION) {
formationMode = false; // Switch to scatter mode
timer = 0; // Reset the timer
}
// Switch back to formation mode if in scatter mode and time exceeds the duration
else if (!formationMode && timer > SCATTER_DURATION) {
formationMode = true; // Switch back to formation mode
timer = 0; // Reset the timer
generateFormationPoints(); // Generate new formation points
}
// Update and display each fish based on the current mode
for (let fish of fishes) {
if (formationMode) {
// In formation mode, seek the target points
let target = targetPoints[fishes.indexOf(fish) % targetPoints.length]; // Get the corresponding target point
fish.seekTarget(target); // Make the fish seek the target
} else {
// In scatter mode, fish will scatter randomly
fish.scatter();
}
fish.update(); // Update the fish's position
fish.show(); // Draw the fish on the screen
}
}
function generateFormationPoints() {
targetPoints = []; // Reset the target points array
// Randomly select a formation shape
let shape = random(['doubleSpiral', 'wave', 'school', 'dna', 'vortex', 'shoal']);
let centerX = width / 2; // Calculate the center x position
let centerY = height / 2; // Calculate the center y position
let size = min(width, height) * 0.3; // Determine the size of the formation based on the canvas size
// Create target points based on the selected shape
switch(shape) {
case 'doubleSpiral':
// Generate points for a double spiral formation
for (let i = 0; i < NUM_FISH / 2; i++) {
let angle = map(i, 0, NUM_FISH / 2, 0, TWO_PI * 2);
let radius = map(i, 0, NUM_FISH / 2, 0, size);
targetPoints.push(createVector(
centerX + cos(angle) * radius, // Calculate x position
centerY + sin(angle) * radius // Calculate y position
));
targetPoints.push(createVector(
centerX + cos(angle + PI) * radius, // Calculate x position for the opposite side
centerY + sin(angle + PI) * radius // Calculate y position for the opposite side
));
}
break;
case 'wave':
// Generate points for a wave formation
for (let i = 0; i < NUM_FISH; i++) {
let x = map(i, 0, NUM_FISH, -size, size); // Calculate x position
let y = sin(x * 0.05) * size * 0.3; // Calculate y position using sine wave
targetPoints.push(createVector(
centerX + x, // Adjust x position based on the center
centerY + y // Adjust y position based on the center
));
}
break;
case 'school':
// Generate points for a school formation
let rows = 8; // Number of rows in the school
let cols = ceil(NUM_FISH / rows); // Calculate number of columns
for (let i = 0; i < rows; i++) {
for (let j = 0; j < cols; j++) {
let x = map(j, 0, cols, -size, size); // Calculate x position for the grid
let y = map(i, 0, rows, -size / 2, size / 2); // Calculate y position for the grid
x += random(-10, 10); // Add slight randomness to x position
y += random(-10, 10); // Add slight randomness to y position
targetPoints.push(createVector(centerX + x, centerY + y)); // Add the point to target points
}
}
break;
case 'dna':
// Generate points for a DNA-like formation
for (let i = 0; i < NUM_FISH; i++) {
let angle = map(i, 0, NUM_FISH, 0, TWO_PI * 3); // Calculate angle for each fish
let x = angle * 20; // Calculate x position
let y1 = sin(angle) * size * 0.2; // Calculate first y position
let y2 = sin(angle + PI) * size * 0.2; // Calculate second y position
if (i % 2 === 0) {
targetPoints.push(createVector(centerX - size + x, centerY + y1)); // Add one point
} else {
targetPoints.push(createVector(centerX - size + x, centerY + y2)); // Add alternate point
}
}
break;
case 'vortex':
// Generate points for a vortex formation
for (let i = 0; i < NUM_FISH; i++) {
let angle = map(i, 0, NUM_FISH, 0, TWO_PI * 3); // Calculate angle
let radius = map(i, 0, NUM_FISH, size, 0); // Calculate decreasing radius
let x = cos(angle) * radius; // Calculate x position
let y = sin(angle) * radius; // Calculate y position
targetPoints.push(createVector(centerX + x, centerY + y)); // Add the point
}
break;
case 'shoal':
// Generate points for a shoal formation
for (let i = 0; i < NUM_FISH; i++) {
let angle = noise(i * 0.1) * TWO_PI * 2; // Calculate angle using Perlin noise
let radius = noise(i * 0.1 + 1000) * size; // Calculate radius using noise
let x = cos(angle) * radius; // Calculate x position
let y = sin(angle) * radius; // Calculate y position
targetPoints.push(createVector(centerX + x, centerY + y)); // Add the point
}
break;
}
}
function drawUnderwaterEffect() {
noFill(); // No fill for the shapes
// Draw wavy lines to create an underwater effect
for (let i = 0; i < height; i += 30) {
stroke(200, 220, 255, 10); // Set stroke color with some transparency
beginShape(); // Begin a new shape
for (let x = 0; x < width; x += 50) {
let y = i + sin(frameCount * 0.01 + x * 0.01) * 5; // Calculate y position with sine wave
curveVertex(x, y); // Add vertex to the shape
}
endShape(); // End the shape
}
}
class Fish {
constructor(x, y, size, colors) {
this.position = createVector(x, y); // Set the fish's position
this.velocity = createVector(random(-1, 1), random(-1, 1)); // Random initial velocity
this.acceleration = createVector(); // Initialize acceleration
this.size = size; // Set fish size
this.maxSpeed = 2; // Set max speed for the fish
this.maxForce = 0.05; // Set max steering force
this.colors = colors; // Set colors for the fish
this.tailAngle = 0; // Initialize tail angle for movement
this.tailSpeed = random(0.05, 0.1); // Random speed for tail movement
}
seekTarget(target) {
let desired = p5.Vector.sub(target, this.position); // Calculate desired velocity
desired.setMag(this.maxSpeed); // Set desired velocity to max speed
let steer = p5.Vector.sub(desired, this.velocity); // Calculate steering force
steer.limit(this.maxForce); // Limit steering force
this.acceleration.add(steer); // Apply steering force to acceleration
}
scatter() {
// Occasionally apply a random force to simulate scattering
if (random(1) < 0.02) {
this.acceleration.add(p5.Vector.random2D().mult(0.1)); // Add random acceleration
}
// Keep fish within the canvas bounds
let margin = 100; // Margin from the edges
if (this.position.x < margin) this.acceleration.x += 0.05; // Move right if too far left
if (this.position.x > width - margin) this.acceleration.x -= 0.05; // Move left if too far right
if (this.position.y < margin) this.acceleration.y += 0.05; // Move down if too far up
if (this.position.y > height - margin) this.acceleration.y -= 0.05; // Move up if too far down
}
update() {
this.velocity.add(this.acceleration); // Update velocity with acceleration
this.velocity.limit(this.maxSpeed); // Limit velocity to max speed
this.position.add(this.velocity); // Update position
this.acceleration.mult(0); // Reset acceleration to zero
// Wrap around the screen if the fish goes off the edges
if (this.position.x < -50) this.position.x = width + 50; // Wrap left
if (this.position.x > width + 50) this.position.x = -50; // Wrap right
if (this.position.y < -50) this.position.y = height + 50; // Wrap up
if (this.position.y > height + 50) this.position.y = -50; // Wrap down
}
show() {
push(); // Save the current drawing state
translate(this.position.x, this.position.y); // Move to the fish's position
rotate(this.velocity.heading()); // Rotate based on fish's velocity
this.tailAngle = sin(frameCount * this.tailSpeed) * 0.3; // Calculate tail angle based on sine wave
rotate(this.tailAngle * 0.2); // Rotate the tail
noStroke(); // No outline for the fish
fill(this.colors.main); // Set fill color to the fish's main color
beginShape(); // Start drawing the fish shape
vertex(this.size * 0.5, 0); // Vertex for the fish's body
bezierVertex(
this.size * 0.5, -this.size * 0.3, // Control point 1
-this.size * 0.2, -this.size * 0.3, // Control point 2
-this.size * 0.4, 0 // End point
);
bezierVertex(
-this.size * 0.2, this.size * 0.3, // Control point 3
this.size * 0.5, this.size * 0.3, // Control point 4
this.size * 0.5, 0 // End point
);
endShape(); // End drawing the fish shape
push(); // Save the current state for the fin
translate(-this.size * 0.4, 0); // Move to the position for the fin
rotate(this.tailAngle); // Rotate the fin based on the tail angle
fill(this.colors.fin); // Set fill color for the fin
beginShape(); // Start drawing the fin shape
vertex(0, 0); // Vertex for the fin
bezierVertex(
-this.size * 0.3, -this.size * 0.4, // Control point 1
-this.size * 0.5, -this.size * 0.2, // Control point 2
-this.size * 0.3, 0 // End point
);
bezierVertex(
-this.size * 0.5, this.size * 0.2, // Control point 3
-this.size * 0.3, this.size * 0.4, // Control point 4
0, 0 // End point
);
endShape(); // End drawing the fin
pop(); // Restore the previous state
// Draw the tail fin
fill(this.colors.fin); // Set fill color for the tail fin
beginShape(); // Start drawing the tail fin
vertex(0, -this.size * 0.3); // Vertex for the tail fin
bezierVertex(
-this.size * 0.2, -this.size * 0.5, // Control point 1
-this.size * 0.3, -this.size * 0.4, // Control point 2
-this.size * 0.2, -this.size * 0.2 // End point
);
endShape(); // End drawing the tail fin
// Draw the secondary tail fin
push(); // Save the current state for the secondary tail fin
translate(0, this.size * 0.1); // Move to the position for the secondary fin
rotate(sin(frameCount * this.tailSpeed + PI) * 0.2); // Rotate the secondary fin
fill(this.colors.fin); // Set fill color for the secondary tail fin
beginShape(); // Start drawing the secondary tail fin
vertex(0, 0); // Vertex for the secondary fin
bezierVertex(
-this.size * 0.2, this.size * 0.1, // Control point 1
-this.size * 0.3, this.size * 0.2, // Control point 2
-this.size * 0.1, this.size * 0.3 // End point
);
endShape(); // End drawing the secondary tail fin
pop(); // Restore the previous state
// Draw spots on the fish
for (let i = -this.size * 0.3; i < this.size * 0.3; i += 3) {
for (let j = -this.size * 0.2; j < this.size * 0.2; j += 3) {
stroke(this.colors.accent); // Set stroke color for the spots
strokeWeight(0.5); // Set stroke weight
point(i, j); // Draw the point
}
}
// Draw the eye of the fish
fill(255); // Set fill color for the eye
noStroke(); // No outline for the eye
ellipse(this.size * 0.25, -this.size * 0.1, this.size * 0.15); // Draw the white part of the eye
fill(0); // Set fill color for the pupil
ellipse(this.size * 0.25, -this.size * 0.1, this.size * 0.08); // Draw the pupil
fill(255); // Set fill color for the reflection
ellipse(this.size * 0.27, -this.size * 0.12, this.size * 0.03); // Draw the reflection
pop(); // Restore the previous drawing state
}
}
function windowResized() {
resizeCanvas(windowWidth, windowHeight); // Resize the canvas when the window is resized
generateFormationPoints(); // Regenerate formation points to fit the new canvas size
}