So I took a whole weekend to make this game…
Description
In this simple game, the player controls a spacecraft whose mission is to collect StarBits, however, they must avoid colliding with meteors or they will risk loosing all the progress they have made. Score is displayed on the upper left corner. When the player collides with a meteor, the spaceship becomes red for a while and the score keeps decreasing until they get out of the way of the meteor.
Idea
I got inspiration from one of Daniel Shiffman’s Coding Challenges titled Starfield and from Super Mario Galaxy in which Mario collects this star shaped things called Starbits.
Process
After watching the video and playing a little of Mario Galaxy with a friend, I decided to write down the classes and functions that I thought I would need for this assignment. I did this mainly because last assignment was very chaotic for me in terms of knowing what I needed to do, so I decided to write how I was thinking of making the code rather than just go at it.
This is what I ended up with:
I decided to star with the background and do the star object first because I thought that after watching the video, I could do it in a breeze, after all Shiffman’s video is 13 minutes.
I took a whole day to figure out how to do just the background. Also, the field that I was making only appeared on the lower right hand corner. Like this:
So I texted on the Discord chat and Professor Shiloh and Professor Aaron came up with a solution, thank God.
Anyways, moving on.
After doing the Stars, I proceeded to make the spaceship. I did two versions of it: one controlled with the keyboard and one with the mouse. I wasn’t sure which one to leave because I felt like they had a different range of motion and deciding would rely on how the meteors and starbits.
Once the controls for the spaceship were done and the image of it was resized, I decided to make the meteors.
(Just a side note: I know that I am actually doing meteroids and not meteors, but meteors is just easier on my poor brain).
I think this was the hardest part of the process. The meteors were just not cooperating with me. When they were immobile and I just placed them at random positions, they looked beautiful, but then, when I started to make the functions to move them, it was just horrible. My idea was to do a similar mechanic to the stars and increase the meteor’s sizes as it moved through the screen. Nonetheless, when I tried to resize it, the more time I left it moving, the blurrier the image got.
Like this:
After spending almost a day in useless attempts to better this, I decided to text on the Discord group and professor Aaron recommended using the scale() function along with changing the values I used to map the size of the meteor.
It worked, but I still had problems with the fact that the resizing was not stopping but getting way past the size that I wanted it to be. The problem was that I wasn’t aware that the map function did not restrain the value from continuing to increase, so I had to learn how to use constrain(). Furthermore, I also had to learn how to use the push/popMatrix() and the push/popStyle().
For the Starbits, I decided to use the star() function from the Processing reference. I had issues figuring out how to change the position of the star itself (every time I tried, what changes was the rotation or the size, but not the position). I don’t know if it is just me, but I found that some of the explanations given on the Processing reference could use a little more information about what the values passed down in the function represent.
After figuring out how to change the position of the Starbit, it was time to do the collisions. Professor Aaron taught me how to do it in an easier way than how I was doing it. There was a problem however with the values that the keyboard was giving. I printed them out and they were way too big in comparison with the location of the canvas. I later found out it was because the function was multiplying the value of the keyboard by two. I reused the same logic for detecting collisions to collect the Starbits. I then used the collision to change the tint of the spaceship to red when it got hit and to generate a new starbit in a random location when the player collected it.
When I put it all together, I didn’t really like the movement of the spaceship with the keyboard. It was too linear for the player to be able to avoid colliding with the meteors, so I opted for the mouse controls instead.
Finally, it was time to do the score. I had to watch a tutorial for the score and learn how to use the text() and its style functions. I placed conditions to increase the score when collecting the Starbit and decrease it when colliding with a meteor. I forgot to call the functions in the main which is why when I placed the code within the assignment, it wasn’t working.
I cried of happiness when I started to see the score change in the screen.
As a final touch, I decided to make the color of the score become red when the score dropped below 0.
Code for the Main Tab
// make variables and initialize them with the sizes of the array for the Stars int sizeStars = 1000; //make variable of class spaceship Spaceship spacecraft; //make variable of class meteor Meteor meteor; //make variable of class Starbit StarBit starBit; //make array of class Star Star[] stars = new Star[sizeStars]; //make variable of class score Score score; void setup() { //set size and background size(1000, 700); background(0); //call constructor for spacecraft spacecraft = new Spaceship(); //call constructor for meteor meteor = new Meteor(); //call constructor for Starbit starBit = new StarBit(); //call constructor for score score = new Score(); //create a Star object for each element in the array for (int i = 0; i < sizeStars; i++) { stars[i]= new Star(); } } void draw() { background(0); //display the Starbit using the function starBit.display(); //update the position of the spacecraft spacecraft.updatePosition(); //show the spacecraft spacecraft.show(); //update the position of the meteor meteor.updatePosition(); //show the meteor meteor.show(); //check if there has been a collision to decrease score score.collided(); //check if player collected Starbits to increase score score.collected(); // check if the spacecraft changed tint from collision spacecraft.changeTint(); //check if player collected Starbit to create a new Starbit starBit.createStarBit(); //display current score score.display(); //change position of the stars in the canvas translate(width/2, height/2); //update the position of the stars and show them in the canvas for (int i = 0; i < sizeStars; i++) { stars[i].updatePosition(); stars[i].show(); } }
Meteor Class
class Meteor { //create varaible to hold image of the meteor PImage meteor; //create variables for the x, y and z position of the stars float x, y, z; //create variable to hold the direction (speed) at which the meteor will move float xDir, yDir; //constructor Meteor() { //load image of the meteor to the variable meteor = loadImage("meteor.PNG"); //initialize the position of the meteor at random location x = random(width); //y can be left at 0 because the image size is 0 at the beginning y = 0; //assign random speed in the y direction yDir = random(7, 20); /*assign random speed in the x position depending on whether the value of x is less than the width*/ if (x < width/2) { xDir = random(7, 20); } else { xDir = random(-20, -7); } //testing at the center //x = width/2; //y = height/2; //assign random value to z z = random(width); } //function to reset the values of Meteor void reset() { x = random(width); y = 0; z = width; yDir = random(7, 20); if (x < width/2) { xDir = random(7, 20); } else { xDir = random(-20, -7); } } //function to update the position of the stars as they move void updatePosition() { //add speed to x and y x += xDir; y += yDir; //changing the z position z = z - 10; // re-set the positions if (z < 0) { reset(); } } //show the meteor in the canvas void show() { //make variable that increases the size of the meteor as it travels through the screen float size = map(z, 0, width, 0.8, 0.02); //limit how bit the size can get size = constrain(size, 0.02, 0.8); //show the image pushMatrix(); //translate to position of meteor so that it scales at the center of the image translate(x, y); //scale the meteor to its current size scale(size); //show the meteor //center the image pushStyle(); imageMode(CENTER); image(meteor, 0, 0); popStyle(); popMatrix(); } }
Spaceship Class
class Spaceship { //create variable to hold image of the Spaceship PImage spaceship; //create variables for the x, y position of the spacecraft int x, y; int speed = 5; boolean collided, collected; //constructor Spaceship() { //initialize the x and y position at the center x = width/2; y = height/2; //load image of the meteor to the variable spaceship = loadImage("SpaceShip.PNG"); } //function to update the position of the spacecraft by tracking the mouse position void updatePosition() { //assign the position of the mouse to the x and y variables of the spaceship x = mouseX; y = mouseY; } //show the spaceship in the canvas void show() { //place the cursors at the center of the image imageMode(CENTER); //draw the spacecraft where the mouse is located spaceship.resize(200, 200); image(spaceship, x, y ); } //returns true if the player had "collected" the starbit or false if they haven't boolean checkCollect() { //make variable to calculate distance between the spaceship and the Starbit float distance = dist(mouseX, mouseY, starBit.xPos, starBit.yPos); //radius of space that the starBit occupies float radius = 30; /*if distance between the starbit and the spaceship is smaller than the radius, then the player has collected the starbit*/ if (distance < radius) { return true; } //else, player hasn't collected the starbit else { return false; } } //returns true if the player has "collided" with a meteor or false if they haven't boolean checkCollide() { //make variable to calculate distance between the spaceship and the meteor float distance = dist(mouseX, mouseY, meteor.x, meteor.y); //radius of space that the meteor occupies float radius = 100; /*if distance between the meteor and the spaceship is smaller than the radius, then the player has collided with the meteor*/ if (distance < radius) { return true; } //else, player hasn't collided with the meteor else { return false; } } //change tint of the spaceship to red if the collision happened void changeTint() { collided = checkCollide(); //collected = checkCollect(); if (collided == true) { pushStyle(); tint(255, 0, 0); image(spaceship, mouseX, mouseY); popStyle(); } } }
Star Class
class Star { //create variables for the x, y and z position of the stars float x, y, z; //constructor Star() { //initialize the position of the stars at random locations x = random(-width, width); y = random(-height, height); z = random(width); } //function to update the position of the stars as they move void updatePosition() { //decrease value of z z = z - 20; //reset the star's position if (z < 1) { x = random(-width, width); y = random(-height, height); z = width; } } //show the star in the canvas void show() { fill(255); noStroke(); //divide the x and y position by x and map it with the width and height to move the stars float newXpos = map(x/z, 0, 1, 0, width); float newYpos = map(y/z, 0, 1, 0, height); //increase radius of the star as the get closer to the end of the canva float radius = map(z, 0, width, 10, 0); //use a circle to draw the stars circle(newXpos, newYpos, radius); } }
StarBit Class
class StarBit { //make variables for x and y position of the Starbit int xPos, yPos; /*radius1 is for how big the starBit will be, radius2 is for the dents between the points of the StarBit*/ float radius1 = 20; float radius2 = 40; //make variable for the number of points in the starBit int numPoints = 5; //make variables to hold the red, green and blue colors of the rgb int r, g, b; //make bolean to check if StarBit was collected boolean collected; //constructor StarBit() { /*assign random x and y positions to the starbits (the 35 is so that they don't) appear outside the canva*/ xPos = round(random(35, width-35)); yPos = round(random(35, height-35)); //assign random values for the rgb r = round(random(255)); g = round(random(255)); b = round(random(255)); } //create a new starbit void newStarBit() { xPos = round(random(35, width-35)); yPos = round(random(35, height-35)); r = round(random(255)); g = round(random(255)); b = round(random(255)); } //make shape of starbit (taken from Processing refence) void star(float x, float y, float radius1, float radius2, int npoints) { float angle = TWO_PI / npoints; float halfAngle = angle/2.0; beginShape(); for (float a = 0; a < TWO_PI; a += angle) { float sx = x + cos(a) * radius2; float sy = y + sin(a) * radius2; fill(r, g, b); vertex(sx, sy); sx = x + cos(a+halfAngle) * radius1; sy = y + sin(a+halfAngle) * radius1; vertex(sx, sy); } endShape(CLOSE); } //display the starBit void display() { pushMatrix(); //make the star rotate translate(xPos, yPos); rotate(frameCount/ -100.0); //draw the starbit star(0, 0, radius1, radius2, numPoints); popMatrix(); } //create starBit if the current starBit had been collected void createStarBit() { //asign value returned by spacecraft.checkCollect()to variable collected = spacecraft.checkCollect(); if (collected == true) { newStarBit(); } } }
Score Class
class Score { //make variable to hold the score int score; //make variable that will be used to display the score String s; //variables to check for staBit collection and meteor collisions boolean collected, collide; //constructor Score() { //initialize score to 0 score = 0; s = "Score: " + score; } //increase score if Starbit has been collected void collected() { //assign value returned by spacecraft.checkCollect() collected = spacecraft.checkCollect(); //increase score if starbit was collected if (collected == true) { score ++; } //update s with the new score s = "Score: " + score; } //decrease score if there was a collision with a meteor void collided() { //assign value returned by spacecraft.checkCollide(); collide = spacecraft.checkCollide(); //if collided, decrease score until player gets away from the meteor if (collide == true) { score --; } //update s with the new score s = "Score: " + score; } //display the score void display() { //if score is below 0, show the text in red if (score < 0) { textSize(20); textAlign(LEFT); fill(255, 0, 0); text(s, 10, 20); } //else show them in white else { textSize(20); textAlign(LEFT); fill(255); text(s, 10, 20); } } }
Result:
Final Thoughts
I really like how this assignment turned out, but I must admit it was very difficult. I feel like most of this project was me doing something, seen it work when tested, then seen it failed once I implemented it in the actual code and asking for help. To be honest, I feel like most of the time I was asking for help because I had no idea of what I was doing wrong or how to start debugging.
I think one of my biggest problem was keeping track of whether I was calling the functions or not. I made a lot of classes and a lot of versions of the project because I was scared of messing up what I had, yet that also made it hard because I had one place in which I was testing the score, one in which I was testing the collisions, etc, and when I placed them together, it was very easy to forget to add stuff to the main tab. I guess this is something that I will fix with practice.
Another thing that was difficult was centering the images and keeping track of the points of reference used. At some point it became very confusing to know which point of reference was been used by which object. Another thing was that I didn’t really understood the difference between push/popMatrix and push/popStyle. Now I know, thank to Prof. Aaron. This made me realize that a lot of my problems could be fixed more easily if only I knew the correct syntax.
What I think was what made me very slow in my process, however, was coming up with the logic of the code. It was very extremely difficult to visualize what I was doing and think of other ways to accomplish something when my initial idea failed. I also think that this is something that will become easier the more I practice, but man it was hard (and at times discouraging), but still, after taking a break I feel like I was able to solve many issues by myself.
I am extremely happy with this mini game and after completing it I had a lot of fun playing (I even sent it to my parents so they could play).
This is the link to the GitHub with the code. If you decide to play it, I hope you enjoy it!