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.



