Concept:
I was fascinated by the idea of creating a game that could be controlled using hand movements, and I successfully brought that idea to life using p5.js and Arduino. Inspired by the game ‘Stick Hero,’ I decided to recreate it with a unique twist. In this game, the player’s objective is to achieve a high score by helping their character cross platforms using a growing stick. By clenching their fist, the player can extend the stick to bridge the gap between platforms. The challenge lies in finding the perfect stick length—not too short to fall short of the other platform, and not too long to overshoot it.
p5.js:
The p5.js code represents a game where players navigate platforms by using a growing and rotating stick.
- Global variables are declared for images and sounds.
- Images and sounds are preloaded to ensure smooth gameplay.
- The Game class is defined, responsible for managing game objects and states.
- The class includes functions for start checking, score display, platform removal, game over detection, state management, platform and stick creation, and game display.
- The Player class represents the player character, incorporating functions for setting the destination, sticking to platforms, moving, and displaying.
- The Platform class represents the platforms and includes functions for movement and display.
- The Stick class represents the growing and rotating stick, featuring functions for growth and rotation.
- The setup and draw functions are defined for game initialization and updates.
- Event handlers for mouse clicks and releases are implemented to capture player interactions.
- The code also includes a callback function for reading serial data from an Arduino, enabling hand-controlled gameplay.
The code creates an engaging game where players maneuver their character across platforms using a stick, striving to reach the end without falling off. The game dynamically changes based on mouse interactions and player movements, and the score is constantly displayed during gameplay.
p5.js Code:
// Declaring the global variables let bg_img; let player_img; let success; let failure; // prelaoding the images and sounds function preload() { start_screen = loadImage("start_screen.png"); bg_img = loadImage("bg1.jpeg"); player_img = loadImage("sprite.png"); success = loadSound("success-sound-effect.mp3"); failure = loadSound("failure.mp3"); } // Setting up the canvas and creating the game object class Game { // Declaring the variables constructor() { this.platforms = []; this.gaps = []; this.widths = []; this.stick = false; this.state = 0; this.score = 0; this.start_x = 217; this.start_y = 352; this.start_w = 98; this.start_h = 30; // Making an array of all the possible gaps between the platforms for (let g = 4; g < 61; g++) { this.gaps.push(g * 5); } // Making an array of all the possible widths of the platforms for (let w = 8; w < 31; w++) { this.widths.push(w * 5); } // Making the first three platforms let x = 0; for (let i = 0; i < 2; i++) { let gap = random(this.gaps); this.create_platform(x); x = x + this.platforms[this.platforms.length - 1].w + gap; } // Making the player this.player = new Player( this.platforms[0].x + this.platforms[0].w, this.platforms[0].y ); } // Function to display the game check_start() { if ( mouseX >= this.start_x && mouseX <= this.start_x + this.start_w && mouseY >= this.start_y && mouseY <= this.start_y + this.start_h ) { this.state = 1; } } // Function to display the start screen display_score() { textSize(20); noStroke(); fill(0); text("Score: " + this.score, 20, 30); } // remove the platform once it is out of the screen remove_platform() { if (this.platforms[0].x <= -this.platforms[0].w) { this.platforms.shift(); } } // Function to check if the game is over check_game_over() { if (this.state === 8 && this.player.x === this.player.destination_x) { failure.play(); this.state = 9; } } // Function to display the game over screen display_game_over() { background(bg_img); textSize(20); noStroke(); fill(0); textSize(30); text("Score: " + this.score, width / 2 - 50, height / 2-50); text("Game Over", width / 2 - 70, height / 2); text("Click to restart", width / 2 - 88, height / 2 + 50); } state_manager() { // Function to manage the states of the game if (this.state === 0) { // State 0 displays the game instructions. image(start_screen,0,0); } else if (this.state === 2) { // State 2 detects a mouse click and grows the stick. this.create_stick(); this.stick.grow(); } else if (this.state === 3) { // State 3 detects a mouse release and rotates the stick. this.stick.rotate(); } else if (this.state === 4) { // State 4 checks if the player has reached the platform after the stick has finished rotating, and transitions to state 5 or state 8 accordingly. this.player.set_destination(); } else if (this.state === 5) { // State 5 moves the player towards their destination. this.player.move(); } else if (this.state === 6) { // State 6 determines the new positions of the platforms and the player once the player reaches their destination platform. this.set_platforms_destination(); } else if (this.state === 7) { // State 7 moves the platforms and the player towards their destination. for (var i = 0; i < this.platforms.length; i++) { this.platforms[i].move(); this.player.stick_to_platform(); } this.remove_platform(); } else if (this.state === 8) { // State 8 moves the player towards the end of the stick and checks if they have reached it. this.player.move(); this.check_game_over(); } else if (this.state === 9) { // State 9 ends the game and displays the game over screen as soon as the player reaches the end of the stick that is not on the platform. this.display_game_over(); } } // Function to create a new platform create_platform(x) { let w = random(this.widths); let y = height - 100; let p = new Platform(x, y, w, 100); this.platforms.push(p); } // Function to set the destination of the platforms set_platforms_destination() { this.create_platform(width); this.platforms[0].destination_x = -this.platforms[0].width; this.platforms[1].destination_x = 0; this.platforms[2].destination_x = this.platforms[1].w + random(this.gaps); game.state = 7; } // Function to create a new stick create_stick() { if (!this.stick) { this.stick = new Stick( this.platforms[0].x + this.platforms[0].w, this.platforms[0].y,3,0); } } // Function to display the game display() { background(bg_img); this.state_manager(); if (game.state != 9 && game.state != 0) { this.display_score(); for (let j = 0; j < this.platforms.length; j++) { this.platforms[j].display(); this.player.display(); if (this.stick != false) { this.stick.display(); } } } } } // class for player class Player { constructor(x, y) { this.w = 30; this.h = 50; this.x = x - this.w; this.destination_x = x; this.v = 5; this.y = y - this.h; this.position = 0; } // Setting Destination of the player so that he moves after the stick is down set_destination() { if ( game.stick.x_2 >= game.platforms[1].x && game.stick.x_2 <= game.platforms[1].x + game.platforms[1].w ) { this.destination_x = game.platforms[1].x + game.platforms[1].w - this.w; game.score += 1; success.play(); game.state = 5; } else { this.destination_x = game.stick.x_2; game.state = 8; } } // Setting player's x equal to the platform so it moves along with it stick_to_platform() { if (game.platforms.length === 2) { this.x = game.platforms[0].x + game.platforms[0].w - this.w; if (game.platforms[0].x === 0) { game.state = 1; } } else { this.x = game.platforms[1].x + game.platforms[1].w - this.w; if (game.platforms[1].x === 0) { game.state = 1; } } } // Function to move the player according to his destination move() { if (this.x < this.destination_x) { this.x += this.v; this.position = (this.position + 1) % 7; } else if (this.x > this.destination_x) { this.x -= this.v; } else if ( this.x == this.destination_x && this.x > game.platforms[0].x + game.platforms[0].w && game.state === 5 ) { game.stick = false; game.state = 6; } } // Display the player using the respective position from the sprite sheet display() { let c = player_img.get(this.position * 109, 0, 109, 120); image(c, this.x, this.y, this.w, this.h); } } // Declaring the platform class class Platform { constructor(x, y, w, h) { this.x = x; this.destination_x = x; this.y = y; this.w = w; this.h = h; this.v = 5; } // Function to move the platform according to its destination move() { if (this.x != this.destination_x) { this.x = this.x - this.v; } } // Display the platform display() { noStroke(); fill(color("#808080")); //"#6d4a3b" rect(this.x, this.y, this.w, this.h); stroke(0); } } // Declaring the Stick class class Stick { constructor(x, y, w, h) { this.l = h; this.x_1 = x; this.y_1 = y; this.x_2 = x; this.y_2 = this.y_1 + this.l; this.angle = PI / 2; } // Function to grow the stick grow() { this.y_2 -= 5; this.l += 5; } // Rotate the Stick according when the mouse if released and check if the rotation is complete rotate() { this.angle -= PI / 64; this.x_2 = this.x_1 + this.l * cos(this.angle); this.y_2 = game.platforms[0].y - this.l * sin(this.angle); if (this.angle <= 0) { game.state = 4; } } // Display the stick display() { stroke("#808080"); strokeWeight(2); line(this.x_1, this.y_1, this.x_2, this.y_2); strokeWeight(4); } } function setup() { createCanvas(550, 450); game = new Game(); } function draw() { clear(); game.display(); } // Perform functions when mouse is clicked according to the state of the game function mousePressed() { if (game.state === 0) { game.check_start(); } else if (game.state === 1) { game.state = 2; } else if (game.state === 9) { game = new Game(); game.state = 1; } } function readSerial(data) //call back function { if (data != null) //if the data received is not null { console.log(data); if (game.state === 0) { // game.check_start(); } else if (game.state === 1 && data > 1008) { game.state = 2; } else if (game.state === 9) { // game = new Game(); // game.state = 1; } else if( game.state ===2 && data < 1008) { game.state = 3; } } let redlight= 2; let greenlight = 1; if(game.state === 2) { let sendToArduino = 1 + "\n"; writeSerial(sendToArduino); } else if(game.state === 9) { let sendToArduino = 2 + "\n"; writeSerial(sendToArduino); } else { sendToArduino = 0 + "\n"; writeSerial(sendToArduino); } } function keyPressed() //if any key is pressed, then set up serial { setUpSerial(); } // Shift the state when the mouse is released function mouseReleased() { if (game.state === 2) { game.state = 3; } }
Arduino:
The code reads analog input from a flex sensor connected to pin A4 and controls two LEDs (green and red) connected to pins 8 and 2, respectively.
In the `loop()` function:
– The flex sensor’s analog value is read using `analogRead()` and stored in the `value` variable.
– The analog value is printed to the serial monitor using `Serial.println()`.
– If input is available from p5.js, the code reads the value and checks for specific conditions.
– Based on the brightness value, the green and red LEDs are controlled by turning them on or off using `digitalWrite()`.
The code utilizes analog input from a flex sensor to control the brightness of two LEDs connected to pins 8 and 2, based on input received from p5.js via serial communication.
Arduino Code:
//Constants: const int flexPin = A4; // Pin A4 to read analog input const int ledPin = 8; // Green LED pin const int ledPin2 = 2; // Red LED pin // Variables: int value; // Save analog value void setup() { Serial.begin(9600); // Begin serial communication pinMode(ledPin, OUTPUT); pinMode(ledPin2, OUTPUT); } void loop() { value = analogRead(flexPin); // Read analog input from flex sensor Serial.println(value); // Print the analog value to the serial monitor delay(100); // Small delay // Wait for input from p5.js while (Serial.available()) { int brightness = Serial.parseInt(); if (Serial.read() == '\n') { // Control the LEDs based on the received brightness value if (brightness == 1) { digitalWrite(ledPin, HIGH); brightness = 0; } else { digitalWrite(ledPin, LOW); } if (brightness == 2) { digitalWrite(ledPin2, HIGH); brightness = 0; } else { digitalWrite(ledPin2, LOW); } } } }
User Testing:
Improvements:
Difficulty Progression: Enhance the gameplay experience by implementing a progressive difficulty system. As the player progresses, introduce challenges such as faster platform movement, shorter time limits to place the stick, or additional obstacles. This will keep players engaged and provide a sense of accomplishment as they overcome increasingly difficult levels.
Power-ups: Introduce exciting power-ups or bonuses that players can collect during gameplay. These power-ups could temporarily slow down platform movement, extend the stick’s length, grant extra lives, or introduce other unique abilities. Power-ups add depth, strategy, and an element of surprise to the game, making it more enjoyable and rewarding.
What I am proud of:
I am proud in successfully bringing my initial concept to life. Creating a game that can be controlled by hand movements is truly amazing. I also quite like the game dynamic as it is very visually appealing. It has been a joy to see the enjoyment that people experience while playing the game, as it has been positively received during testing with various individuals.