Concept:
For my week 8 project, I drew inspiration from the iconic “Jaws” movie. The basic idea was to recreate a virtual ocean where sharks roam and fish (or particles) swim around, constantly trying to avoid becoming shark food! The shark wanders around the canvas, much like how predators move unpredictably in real life, while the particles represent smaller fish, using a simple fleeing mechanism to stay out of the shark’s way.
I wanted to build a simple but dynamic scene that felt alive, where the fish-like particles would gracefully move around but change their behavior when a shark approached. The scene gives a sense of interaction between predator and prey, bringing the ocean to life.
Embedded Sketch:
Key Features:
- Wandering Sharks: The shark doesn’t just move in straight lines. Instead, it “wanders” around the screen, changing direction smoothly using noise-based movement. This gives the shark a more natural and unpredictable feel, like a real predator hunting its prey.
- Fleeing Particles (Fish): The particles (representing fish) swim around randomly, but when the shark gets too close, they immediately “flee.” I programmed them to avoid the shark by detecting when it comes within a certain distance and steering away, adding a subtle but clear predator-prey relationship.
- Flowing Ocean Background: To immerse the viewer in an underwater world, I added a flowing ocean background that serves as the setting for this shark-fish interaction. The ocean gives a calming effect while the shark-fish chase adds the excitement.
Code Highlight:
One piece of code I’m especially proud of is the fleeing mechanism for the particles. It’s simple but effective, and it adds a lot of realism to the scene. Here’s a snippet of how it works:
// Ball class to define wandering shark behavior class Ball { constructor() { this.position = createVector(random(width), random(height)); // Random starting position this.angle = random(TWO_PI); // Random initial angle for wandering this.speed = 2; // Speed of the shark's movement this.noiseOffset = random(1000); // Noise offset for smooth wandering } update() { // Use noise for smooth direction change this.angle += map(noise(this.noiseOffset), 0, 1, -0.1, 0.1); // Gradual direction change this.velocity = createVector(cos(this.angle), sin(this.angle)).mult(this.speed); // Velocity based on angle this.position.add(this.velocity); // Move the shark based on velocity // Keep the shark within the canvas, reverse direction if it hits edges if (this.position.x > width || this.position.x < 0) { this.position.x = constrain(this.position.x, 0, width); this.angle += PI; // Flip direction } if (this.position.y > height || this.position.y < 0) { this.position.y = constrain(this.position.y, 0, height); this.angle += PI; // Flip direction } this.noiseOffset += 0.01; // Increment noise for smooth wandering motion } display() { // Draw the shark image instead of a shape imageMode(CENTER); // Center the shark image on the position image(sharkImage, this.position.x, this.position.y, 100, 100); // Adjust size of the shark image } } // Point class for the particles (fish) class Point { constructor(xTemp, yTemp) { this.position = createVector(xTemp, yTemp); // Random initial position for particles this.velocity = createVector(0, 0); // Initial velocity this.color = color('blue'); // Color of the particles } // Display the particle display() { strokeWeight(2); // Set particle thickness stroke(this.color); // Set particle color point(this.position.x, this.position.y); // Draw particle at its current position } // Update particle's position based on noise update() { // Use Perlin noise for smooth, organic movement let n = noise(this.position.x * noiseScale, this.position.y * noiseScale, frameCount * noiseScale * noiseScale); let a = TAU * n; // Convert noise value to an angle this.velocity.x = cos(a); // Calculate new velocity based on angle this.velocity.y = sin(a); // Calculate new velocity based on angle this.position.add(this.velocity); // Update the particle's position // Reset particle to a random position if it goes off the screen if (!this.onScreen()) { this.position.x = random(width); this.position.y = random(height); } } // Check if the particle is still on the screen onScreen() { return ( this.position.x >= 0 && this.position.x <= width && this.position.y >= 0 && this.position.y <= height ); } // Flee behavior: particles avoid the shark (ball) avoid(ball) { let distance = dist(this.position.x, this.position.y, ball.position.x, ball.position.y); // Calculate distance to the shark let radius = 50; // Define the radius within which particles start fleeing if (distance < radius) { // Calculate direction to flee from the shark let flee = p5.Vector.sub(this.position, ball.position); flee.normalize(); // Normalize to ensure consistent speed flee.mult(2); // Scale the fleeing force this.position.add(flee); // Move the particle away from the shark } } }
This code makes each particle detect how close the shark is and react accordingly. When the shark enters a certain range, the particles move away, giving the illusion that they are fleeing in fear. It’s simple but adds so much character to the overall interaction.
Future Improvements:
While I’m happy with how the project turned out, there are always ways to make it better. Here are some ideas for future improvements:
- More Realistic Shark Movements: While the wandering behavior works, I’d love to add more realism to the shark’s movements—perhaps making it faster when it spots fish or slowing down after a “hunt.”
- Fish Grouping Behavior: Right now, the particles move independently, but it would be cool to introduce a “schooling” behavior, where fish move in groups. This could make the escape from the shark even more dynamic.
- Improved Visual Effects: Adding more ocean elements like bubbles, light rays, or even underwater sounds would elevate the experience and make it feel more like a deep-sea dive.