HAILSTORM HAVOC
My game draws significant inspiration from the hailstorm that occurred in Abu Dhabi a few weeks ago.
I was captivated by the idea of creating a game where the player must dodge hail. I envisioned a game centered around a car avoiding the hail, however, this concept proved to be overly complex, and after trial and error, I was able to overcome this challenge. To play the game: players must move the car away from the falling hail using the mouse click function. Interestingly, the mouse click function in my game serves two purposes, a feature I was unaware of until Pi assisted me in rectifying a misunderstanding. This allowed me to control both the car and a sprite, adding a fascinating layer to the gameplay.
function mousePressed() {
console.log("Mouse Press");
//just flipping between modes 0 and 1 in this example
clouds.forEach((cloud) => cloud.startAnimation());
}
function mouseReleased() {
clouds.forEach((cloud) => cloud.stopAnimation());
}
How the game works:
-
- You must press Click to start
- To move the car you have to press the mouse
- The longer the press the further right the ball will go
- Dodge the hail (white balls) and avoid touching the red line as both are game over
- Press “space” to restart the game
This is my game:
If it doesn’t work, visit:
https://editor.p5js.org/Afrabinjerais/sketches/q0cF4mKOG
The game is straightforward yet incorporates all the coding components we have learned to date. Its design is uncomplicated, effectively conveying the intended message. I got inspired by this p5 sketch and, the link takes you to a simple game on P5, which has a similar concept to my game, where the objects are falling from the sky.
https://editor.p5js.org/jordanBlueshift/sketches/vSO_bzkaD
My favorite aspect of my game is the cloud sprites, which fidget whenever the mouse is clicked, creating the impression that the clouds are generating hail. On another hand, I also encountered a significant challenge when integrating sound effects to play each time the score increased, which was undoubtedly the most difficult part for me to implement.
time += 1;
if (frameCount % 60 == 0) {
score++;
}
function scoreUpdate() {
// score += 10;
if (score != prevScore) {
scoreSound.play();
}
prevScore = score;
fill(128, 128, 128, 150);
rect(width - 100, 5, 75, 20, 5);
fill(255);
text("SCORE: " + int(score), width - 65, 15);
}
The issue stemmed from the points increasing too rapidly. By reducing the timer and implementing a modulo operation as suggested by the professor, I was able to resolve this problem. Looking ahead, or given more time, I would be eager to experiment with transforming the background to be interactive and making it move to visualize a street. Although my initial attempt at this modification was unsuccessful, I am keen on dedicating time to delve into unfamiliar areas of coding to make this feature a reality.
This is the code for the game:
let gameMode = 0; // Variable to store the current game mode
let musicSound; // Variable to hold the music sound object
let gameoverSound; // Variable to hold the game over sound object
let scoreSound; // Variable to hold the score sound object
var landscape; // Variable to store the landscape graphics
var car_diameter = 15; // Diameter of the ball
var bomb_diameter = 10; // Diameter of the bombs
var xpoint;
var ypoint;
var zapperwidth = 6; // Width of the zapper
var numofbombs = 10; // Number of bombs
var bombposX = []; // Array to store X positions of bombs
var bombposY = []; // Array to store Y positions of bombs
var bombacceleration = []; // Array to store acceleration of each bomb
var bombvelocity = []; // Array to store velocity of each bomb
var time = 0; // Variable to track time, usage context not provided
var timeperiod = 0; // Variable to store a time period, usage not clear without further context
var score = 0; // Variable to store the current score
var posX; // X position, usage context not provided
var inMainMenu = true; // Boolean to check if the game is in the main menu
var prevScore = 0; // Variable to store the previous score
let font; // Variable to store font, usage context not provided
//Cloud Variables
let spritesheet;
let oneDimensionarray = [];
function preload() {
spritesheet = loadImage("clouds.png");
musicSound = loadSound("sounds/song.mp3");
gameoverSound = loadSound("sounds/gameover.mp3");
scoreSound = loadSound("sounds/score.mp3");
glassbreak = loadSound("sounds/glassbreaking.wav");
font = loadFont("fonts/Inconsolata_Condensed-Light.ttf");
car = loadImage("car.png");
car2 = loadImage("car2.png");
}
// Cloud class starts
class Cloud {
constructor(x, y, speed, stepSpeed, scale) {
this.x = x;
this.y = y;
this.scale = scale; // Add scale property
this.speed = speed;
this.stepSpeed = stepSpeed;
this.step = 0;
this.facingRight = false; // Initially moving to the left
this.animationTimer = null;
}
move() {
if (this.facingRight) {
this.x += this.speed;
if (this.x > width + spritesheet.width / 4) {
this.x = -spritesheet.width / 4; // Wrap around to the left side
}
} else {
this.x -= this.speed;
if (this.x < -spritesheet.width / 4) {
this.x = width + spritesheet.width / 4; // Wrap around to the right side
}
}
}
display() {
push();
if (!this.facingRight) {
scale(-this.scale, this.scale); // Apply scale with horizontal flip
image(oneDimensionarray[this.step], -this.x, this.y);
} else {
scale(this.scale, this.scale); // Apply scale
image(oneDimensionarray[this.step], this.x, this.y);
}
pop();
}
advanceStep() {
this.step = (this.step + 1) % 8;
}
startAnimation() {
this.facingRight = true;
clearInterval(this.animationTimer);
this.animationTimer = setInterval(() => this.advanceStep(), this.stepSpeed);
}
stopAnimation() {
this.facingRight = false;
clearInterval(this.animationTimer);
}
}
let clouds = [];
// Cloud class ends
function setup() {
createCanvas(640, 480);
textAlign(CENTER);
musicSound.play();
var temp00 = 0,
temp01 = -20;
// A while loop that increments temp01 based on temp00 until temp01 is less than the canvas height
while (temp01 < height) {
temp00 += 0.02; // Increment temp00 by 0.02 in each loop iteration
temp01 += temp00; // Increment temp01 by the current value of temp00
timeperiod++; // Increment timeperiod in each iteration
}
// Calculate the initial position of posX based on zapperwidth and car_diameter
posX = zapperwidth + 0.5 * car_diameter - 2;
// Set xpoint and ypoint relative to the width and height of the canvas
xpoint = 0.7 * width; // Set xpoint to 70% of the canvas width
ypoint = height - 0.5 * car_diameter + 1; // Set ypoint based on the canvas height and car_diameter
initbombpos(); // Call the initbombpos function (presumably initializes bomb positions)
imageMode(CENTER); // Set the image mode to CENTER for drawing images centered at coordinates
// Initialize variables for width and height based on sprite sheet dimensions divided by specific values
let w = spritesheet.width / 2;
let h = spritesheet.height / 4;
// Nested for loops to extract sprite images from the sprite sheet and push them to oneDimensionarray
for (let y = 0; y < 4; y++) {
for (let x = 0; x < 2; x++) {
oneDimensionarray.push(spritesheet.get(x * w, y * h, w, h)); // Get sub-images from spritesheet and add to oneDimensionarray
}
}
// Create 3 clouds with horizontal offsets, different speeds and scales
clouds.push(new Cloud(width / 8, height / 9, 0, 100, 0.9)); // First cloud
clouds.push(new Cloud((2 * width) / 5, height / 9, 0, 100, 1.2)); // Second cloud
clouds.push(new Cloud((2 * width) / 2, height / 9, 0, 200, 1.0)); // Third cloud
}
function draw() {
background(58, 66, 94);
if (gameMode == 0) {
clouds.forEach((cloud) => {
cloud.display();
});
textFont(font);
fill(255);
textSize(50); // Larger text size for the game title
textAlign(CENTER, CENTER); // Align text to be centered
text('HAILSTORM HAVOC', width / 2, height / 2 - 40);
textSize(16); // Smaller text size for the directions
// Draw the directions right below the game title
text('DIRECTIONS:\n click mouse to dodge hail\n the longer the press the further right\n the car will go\n\n AVOID the red line - crossing it means game over', width / 2, (height / 2) + 50);
textSize(20);
text('Click to start!', width / 2, (height / 2) + 140);
}
else if (gameMode == 1) {
clouds.forEach((cloud) => {
cloud.move();
cloud.display();
});
fill(239, 58, 38);
rect(0, 0, zapperwidth, height);
scoreUpdate();
fill(255);
noStroke();
for (var i = 0; i < numofbombs; i++) {
ellipse(bombposX[i], bombposY[i], bomb_diameter, bomb_diameter);
}
updatebombpos();
// ellipse(xpoint, ypoint, car_diameter, car_diameter);
image(car,xpoint, ypoint-30, car_diameter*5, car_diameter*5);
xpoint -= 3;
// Check if the mouse is pressed and the xpoint is within the canvas boundaries
if (mouseIsPressed && xpoint + 0.5 * car_diameter < width) {
xpoint += 6; // Move the xpoint to the right by 6 units
}
// Check if xpoint is less than or equal to posX or if a collision with a bomb has occurred
if (xpoint <= posX || bombCollistonTest()) {
gameover(); // Call the gameover function if either condition is true
}
// Increment the score every 60 frames
time += 1;
if (frameCount % 60 == 0) {
score++; // Increase score by 1
}
}
}
function updatebombpos() {
// Iterate over each bomb
for (var i = 0; i < numofbombs; i++) {
bombvelocity[i] += bombacceleration[i]; // Update the velocity of the bomb by adding its acceleration
bombposY[i] += bombvelocity[i]; // Update the Y position of the bomb based on its velocity
}
if (time > timeperiod) {
initbombpos(); // Reinitialize the positions of the bombs by calling the initbombpos function
time = 0;
}
}
function initbombpos() {
for (var i = 0; i < numofbombs; i++) {
bombacceleration[i] = random(0.02, 0.03);
bombvelocity[i] = random(0, 5);
bombposX[i] = random(zapperwidth + 0.5 * car_diameter, width);
bombposY[i] = random(-20, -0.5 * car_diameter) + 190;
}
} //This function initializes the position and motion properties of each bomb by assigning random values within specified ranges.
function bombCollistonTest() {
var temp = 0.5 * (car_diameter + bomb_diameter) - 2;
var distance;
// Iterate over each bomb to check for a collision
for (var i = 0; i < numofbombs; i++) {
distance = dist(xpoint, ypoint, bombposX[i], bombposY[i]);
if (distance < temp) {
return true;
}
}
return false;
} //This function checks for collisions between the player and each bomb by comparing the distance between them to a threshold. If any bomb is too close (within the threshold), it returns true (collision detected). Otherwise, it returns false.
function gameover() {
image(car2,xpoint, ypoint-30, car_diameter*5, car_diameter*5);
musicSound.pause();
glassbreak.play();
gameoverSound.play();
textFont(font);
fill(255);
textSize(50); // Set the text size
textAlign(CENTER, CENTER); // Align text to the center
text("GAME OVER", width / 2, height / 2 - 20); // Center the text horizontally and vertically
textSize(15); // Decreased text size for "press space to restart" text
text("Press space to restart", width / 2, height / 2 + 20); // Positioned below "GAME OVER" text
noLoop();
}
function scoreUpdate() {
// score += 10;
if (score != prevScore) //// Play the scoring sound only if the current score has changed from the previous score
{
scoreSound.play();
}
prevScore = score;
fill(128, 128, 128, 150);
rect(width - 100, 5, 75, 20, 5);
fill(255);
text("SCORE: " + int(score), width - 65, 15);
}
function keyPressed() {
if (keyCode === 32) {
// Check if the key pressed is space (keyCode 32)
restartGame(); // Call the function to restart the game
}
}
function mousePressed() {
if (gameMode==0)
gameMode=1;
console.log("Mouse Press");
//just flipping between modes 0 and 1
clouds.forEach((cloud) => cloud.startAnimation());
}
function mouseReleased() {
clouds.forEach((cloud) => cloud.stopAnimation());
}
function restartGame() {
// Reset all game variables to their initial values
musicSound.play();
gameoverSound.pause();
time = 0;
score = 0;
posX = zapperwidth + 0.5 * car_diameter - 2;
xpoint = 0.5 * width;
ypoint = height - 0.5 * car_diameter + 1;
initbombpos();
// Restart the game loop
loop();
}
//This function resets the game environment and variables to their initial state, essentially restarting the game. It resumes background music, pauses any game over sound, resets score and time, repositions the player and bombs, and restarts the game loop.
Enjoy!
References of pictures:
https://bnnbreaking.com/finance-nav/al-ains-unprecedented-hailstorm-a-costly-blow-to-the-car-sales-sector
https://bnnbreaking.com/weather/uaes-al-ain-transformed-unprecedented-hailstorm-blankets-streets-in-white