Frogger - Implementing the Cars: Difference between revisions

From Arduboy Wiki
Jump to navigation Jump to search
(Created page with "== Implementing the Cars == In this section, we will focus on adding the cars to the game play. The code needed will track their locations, move them from right to left and then reposition them on the right-hand side of the screen so they drive endlessly. Although this seems simple enough, we will quickly cover a number of programming concepts. These same concepts will be covered again in future steps so do not be concerned if they don’t all make sense the first ti...")
 
No edit summary
 
(31 intermediate revisions by the same user not shown)
Line 1: Line 1:
== Implementing the Cars ==
 
 
== Tutorial Instructions ==
 
This tutorial is designed to be completed in order and builds on the code from the previous sections. However, you can jump to any section and download the completed code to that point and continue from that section onwards.
 
The tutorial utilises two files which can be downloaded using the following links. To download the files, right click on the filename and select 'Save Link As …'
 
* [https://github.com/filmote/Frogger_Tutorial/blob/main/2.%20Implementing%20the%20Cars/ExampleCode/Frogger.ino Frogger.ino]
* [https://github.com/filmote/Frogger_Tutorial/blob/main/2.%20Implementing%20the%20Cars/ExampleCode/Images.h Images.h]
 
Throughout the tutorials, you will be prompted to complete sections of code utilising the knowledge you have just gained. If you are unable to complete the code you can simply download the starting code presented at the start of the next section as it will contain a valid solution.
 
Other sections in this tutorial:
 
* [[Frogger_-_Getting Started|getting started]]
* implementing the cars
* [[Frogger - Mixing up the Car Movements|mixing up the car’s movements]]
* [[Frogger_-_Adding_the_Frog|adding the player’s frog]]
* [[Frogger_-_Collision_Detection_and_Explosion_Graphics|adding collision detection and explosion graphics]]
* [[Frogger_-_Adding_Logs_and_Turtles|adding logs and turtles]]
* [[Frogger_-_Turtle_Submerge_Logic|adding turtle submerge logic]]
* [[Frogger_-_Adding_Home_Logic|adding home logic]]
* [[Frogger_-_Adding_Score_Logic|adding score logic]]
* [[Frogger_-_Adding_a_Title_and_Game_Over_Screen|adding a title and game over screen]]
 
 
== Overview ==


In this section, we will focus on adding the cars to the game play.  The code needed will track their locations, move them from right to left and then reposition them on the right-hand side of the screen so they drive endlessly.  Although this seems simple enough, we will quickly cover a number of programming concepts.  These same concepts will be covered again in future steps so do not be concerned if they don’t all make sense the first time.
In this section, we will focus on adding the cars to the game play.  The code needed will track their locations, move them from right to left and then reposition them on the right-hand side of the screen so they drive endlessly.  Although this seems simple enough, we will quickly cover a number of programming concepts.  These same concepts will be covered again in future steps so do not be concerned if they don’t all make sense the first time.
Line 29: Line 56:
</pre>
</pre>


Switching back to the <code>Frogger.ino</code> file, I have defined an `enum` called `ObstacleType` that defines these three car types.  In later steps when we add logs and turtles, we will extend the enumeration to include these extra items – hence I have called it <code>ObstacleType</code> rather than <code>VehicleType</code>.
Switching back to the <code>Frogger.ino</code> file, I have defined an <code>enum</code> called <code>ObstacleType</code> that defines these three car types.  In later steps when we add logs and turtles, we will extend the enumeration to include these extra items – hence I have called it <code>ObstacleType</code> rather than <code>VehicleType</code>.


We will use this enumeration later to categorise the vehicles and allow us to draw the correct graphic when it comes time to draw the screen.
We will use this enumeration later to categorise the vehicles and allow us to draw the correct graphic when it comes time to draw the screen.
Line 37: Line 64:
     Car,
     Car,
     Truck,
     Truck,
     Van,
     Van
};
};
<pre>
</pre>






=== Defining a Collection of Cars ===
== Defining a Collection of Cars ==


In this version of Frogger, I have limited the number of cars to six or two per road lane.  In order to keep track of each car’s location and type, we could define a series of variables such as ..
In this version of Frogger, I have limited the number of cars to six or two per road lane.  In order to keep track of each car’s location and type, we could define a series of variables such as ..
Line 103: Line 130:




=== Initial placement of the Vehicles ===
== Initial placement of the Vehicles ==


Before we start moving the cars and trucks around, we need to place them initially and define what type (car, truck or van) they are.  Thinking ahead, we will need to be able to reset the position of the cars, the score and number of lives left between games later so it is best to add this code to a function that we will be able to reuse.  I have called it `resetGame()` as shown below.
Before we start moving the cars and trucks around, we need to place them initially and define what type (car, truck or van) they are.  Thinking ahead, we will need to be able to reset the position of the cars, the score and number of lives left between games later so it is best to add this code to a function that we will be able to reuse.  I have called it `resetGame()` as shown below.
Line 113: Line 140:
     cars[1] = { 180, 32, ObstacleType::Truck };
     cars[1] = { 180, 32, ObstacleType::Truck };
     cars[2] = {  64, 41, ObstacleType::Truck };
     cars[2] = {  64, 41, ObstacleType::Truck };
     cars[3] = { 110, 41, ObstacleType::Van };
     ...
    cars[4] = {  45, 50, ObstacleType::Car };
 
    cars[5] = {  70, 50, ObstacleType::Van };
}
</pre>
</pre>




As you can see, the cars are individually populated using a notation called 'Brace Initialization'.  In this format, the parameters of the structure are defined in a comma separated list in the '''same order they are declared in the structure itself'''.  Thus, for the first car in the array (index 0), the 'x' value is set to '130', the 'y' value to '32' and the obstacle type to ‘Car’.
As you can see, the cars are individually populated using a notation called 'Brace Initialization'.  In this format, the parameters of the structure are defined in a comma separated list in the '''same order they are declared in the structure itself'''.  Thus, for the first car in the array (index 0), the 'x' value is set to '130', the 'y' value to '32' and the obstacle type to ‘Car’.
<div  style="width: 600px; padding: 15px; border: 1px solid; background-color: ligthgrey; box-shadow: 10px 10px 5px grey;">
<span style="font-size: 160%">Your Turn</span>
Download the sample code and complete the following tasks:
The <code>resetGame()</code> function has only specified the starting location for three of the six vehicles in game. Add the remaining 3 vehicles at locations (110, 41), (45, 50) and (70, 50) and of types Van, Car and Van, respectively.
</div>


We can now add a call to <code>resetGame()</code> into the default <code>setup()</code> function to ensure the cars are initialised.
We can now add a call to <code>resetGame()</code> into the default <code>setup()</code> function to ensure the cars are initialised.
Line 136: Line 174:




=== Moving the Cars ===
== Moving the Cars ==


Now that we have an array of cars, we can start moving them from right to left.  However, as we want the cars to move to the far right-hand side after they have travelled across the screen, we will use a function to move them rather than simply decrementing the car structure’s `x` value.
Now that we have an array of cars, we can start moving them from right to left.  However, as we want the cars to move to the far right-hand side after they have travelled across the screen, we will use a function to move them rather than simply decrementing the car structure’s `x` value.


The function <code>moveCars()</pre> achieves this by detecting when the vehicle is completely out of view on the left-hand side and, if so, adding a large amount to place it beyond the screen width to the right.  Our vehicles are range between '10' and '17' pixels in width therefore when rendered at a position of '-20', they are not visible at all.
The function <code>moveCars()</code> achieves this by detecting when the vehicle is completely out of view on the left-hand side and, if so, adding a large amount to place it beyond the screen width to the right.  Our vehicles are range between '10' and '17' pixels in width therefore when rendered at a position of '-20', they are not visible at all.


<pre>
<pre>
Line 151: Line 189:
</pre>
</pre>


We can then move the cars using the <code>moveCars()</pre> function in our main <code>loop()</code> by calling the function consecutively for each car in the array.
We can then move the cars using the <code>moveCars()</code> function in our main <code>loop()</code> by calling the function consecutively for each car in the array.




=== Rendering the Cars Onscreen ===
<div  style="width: 600px; padding: 15px; border: 1px solid; background-color: ligthgrey; box-shadow: 10px 10px 5px grey;">
<span style="font-size: 160%">Your Turn</span>
 
In the main <code>loop()</code>, add some code that iterates through the array of cars. For each car found, call the <code>moveCars()</code> function passing both the car and the increment to move the car each frame. As we are moving from right to left, this should be a value of -1.
 
</div>
 
 
 
== Rendering the Cars Onscreen ==


After all that leg-work, we are ready to actually render the cars on the screen.  The <code>drawCar()</code> function, shown below, takes a car obstacle as a parameter.  It then tests to see what obstacle type it is before rendering the appropriate image.  The use of the <code>ObstacleType</code> enum makes it really clear what each `if` statement is testing.
After all that leg-work, we are ready to actually render the cars on the screen.  The <code>drawCar()</code> function, shown below, takes a car obstacle as a parameter.  It then tests to see what obstacle type it is before rendering the appropriate image.  The use of the <code>ObstacleType</code> enum makes it really clear what each `if` statement is testing.
Line 172: Line 219:
}
}
</pre>
</pre>
<div  style="width: 600px; padding: 15px; border: 1px solid; background-color: ligthgrey; box-shadow: 10px 10px 5px grey;">
<span style="font-size: 160%">Your Turn</span>
Add two additional if statements to handle the remaining two car types, Truck and Van. Their image names are Car_Truck and Car_Van respectively.
</span>


Now that we have a simple function that renders a single car, we can wrap this up into an overall function that renders the road sides, traffic lanes and all six cars.
Now that we have a simple function that renders a single car, we can wrap this up into an overall function that renders the road sides, traffic lanes and all six cars.


<pre>
</div>
 
 
void drawScreen() {
void drawScreen() {


Line 205: Line 262:
If you compile the code and upload it to your device, you will see the cars moving from right to left continuously.  Pretty cool, huh?
If you compile the code and upload it to your device, you will see the cars moving from right to left continuously.  Pretty cool, huh?


Wow, that was a lot of work to do just to get some cars moving!  But the good news is that you have covered a lot of ground – used an <pre>enum</pre> to describe the obstacle types, used a simple <pre>struct</pre> to hold the information about each car and created an array of cars from that same structure.
Wow, that was a lot of work to do just to get some cars moving!  But the good news is that you have covered a lot of ground – used an <code>enum</code> to describe the obstacle types, used a simple <code>struct</code> to hold the information about each car and created an array of cars from that same structure.


But .. you may have noticed that the cars all move at the same speed and the gap between them is constant which would make avoiding them really simple.  In the next section, we will rework the car movements a little to spice the game up.
But .. you may have noticed that the cars all move at the same speed and the gap between them is constant which would make avoiding them really simple.  In the next section, we will rework the car movements a little to spice the game up.
----
Prev [[Frogger_-_Getting Started|getting started]], Next [[Frogger - Mixing up the Car Movements|mixing up the car’s movements]]

Latest revision as of 10:21, 17 June 2024


Tutorial Instructions

This tutorial is designed to be completed in order and builds on the code from the previous sections. However, you can jump to any section and download the completed code to that point and continue from that section onwards.

The tutorial utilises two files which can be downloaded using the following links. To download the files, right click on the filename and select 'Save Link As …'

Throughout the tutorials, you will be prompted to complete sections of code utilising the knowledge you have just gained. If you are unable to complete the code you can simply download the starting code presented at the start of the next section as it will contain a valid solution.

Other sections in this tutorial:


Overview

In this section, we will focus on adding the cars to the game play. The code needed will track their locations, move them from right to left and then reposition them on the right-hand side of the screen so they drive endlessly. Although this seems simple enough, we will quickly cover a number of programming concepts. These same concepts will be covered again in future steps so do not be concerned if they don’t all make sense the first time.

As mentioned in the Getting Started section, above, the code is split into two files named Frogger.ino and Images.h.

Opening the Images.h file reveals the encoded graphic data for the three vehicle types used in the game – a small or compact car, a truck and a van. This encoded data was created using the tools and process described here.

const uint8_t PROGMEM Car_Small[] = {
10, 6,
0x1e, 0x3f, 0x3f, 0x3f, 0x21, 0x3f, 0x3f, 0x21, 
0x3f, 0x1e, 
};

const uint8_t PROGMEM Car_Truck[] = {
17, 6,
0x3f, 0x21, 0x3f, 0x3f, 0x1e, 0x3f, 0x3f, 0x3f, 
0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 
0x3f, 
};

const uint8_t PROGMEM Car_Van[] = {
11, 6,
0x1e, 0x3f, 0x21, 0x3f, 0x2d, 0x2d, 0x3f, 0x2d, 
0x2d, 0x3f, 0x1e, 
};

Switching back to the Frogger.ino file, I have defined an enum called ObstacleType that defines these three car types. In later steps when we add logs and turtles, we will extend the enumeration to include these extra items – hence I have called it ObstacleType rather than VehicleType.

We will use this enumeration later to categorise the vehicles and allow us to draw the correct graphic when it comes time to draw the screen.

enum ObstacleType {
    Car,
    Truck,
    Van
};


Defining a Collection of Cars

In this version of Frogger, I have limited the number of cars to six or two per road lane. In order to keep track of each car’s location and type, we could define a series of variables such as ..

int16_t car1_x;
uint8_t car1_y;
ObstacleType car1_type;           

int16_t car2_x;
uint8_t car2_y;
ObstacleType car2_type;         
...

.. and so on for the six cars. Although this is a valid approach, it is not very efficient as we cannot perform a single task on each of the cars as the code must use the correct variable name for each of the six cars.

Another approach is to make the variables an array like that shown below.

int16_t car_x[6];
uint8_t car_y[6];
ObstacleType car_type[6];           

With this approach, we could write a single function that uses an index to refer to the information about a single car that the function is operating over. For example, the function below will draw the car nominated by the index value in its correct position.

void drawCar(uint8_t index) {

    Sprites::drawOverwrite(car_x[index], car_y[index] ..

}

An even better approach is to use a structure to group all the related variables about a single car together. This approach allows us to then create an array of car obstacles which we can then refer to individually.

struct Obstacle {

    int16_t x;
    uint8_t y;
    ObstacleType type;           

};

Obstacle cars[6];

Our example drawCar() function can be changed to accept a single car parameter (of structure type Obstacle) as shown below. In addition to being much more readable that the example above, the use of structures allows us to do other tricks which we will see in future steps.

void drawCar(Obstacle car) {

    Sprites::drawOverwrite(car.x, car.y ..

}


Initial placement of the Vehicles

Before we start moving the cars and trucks around, we need to place them initially and define what type (car, truck or van) they are. Thinking ahead, we will need to be able to reset the position of the cars, the score and number of lives left between games later so it is best to add this code to a function that we will be able to reuse. I have called it `resetGame()` as shown below.

void resetGame() {

    cars[0] = { 130, 32, ObstacleType::Car };
    cars[1] = { 180, 32, ObstacleType::Truck };
    cars[2] = {  64, 41, ObstacleType::Truck };
    ...

}


As you can see, the cars are individually populated using a notation called 'Brace Initialization'. In this format, the parameters of the structure are defined in a comma separated list in the same order they are declared in the structure itself. Thus, for the first car in the array (index 0), the 'x' value is set to '130', the 'y' value to '32' and the obstacle type to ‘Car’.


Your Turn

Download the sample code and complete the following tasks:

The resetGame() function has only specified the starting location for three of the six vehicles in game. Add the remaining 3 vehicles at locations (110, 41), (45, 50) and (70, 50) and of types Van, Car and Van, respectively.


We can now add a call to resetGame() into the default setup() function to ensure the cars are initialised.

void setup() {
  
    arduboy.begin();
    arduboy.setFrameRate(45);
    arduboy.initRandomSeed();

    resetGame();

}   


Moving the Cars

Now that we have an array of cars, we can start moving them from right to left. However, as we want the cars to move to the far right-hand side after they have travelled across the screen, we will use a function to move them rather than simply decrementing the car structure’s `x` value.

The function moveCars() achieves this by detecting when the vehicle is completely out of view on the left-hand side and, if so, adding a large amount to place it beyond the screen width to the right. Our vehicles are range between '10' and '17' pixels in width therefore when rendered at a position of '-20', they are not visible at all.

void moveCars(Obstacle &car, int8_t increment) {

    car.x = car.x + increment;
    if (car.x <- 20) { car.x = car.x + 148; }

}

We can then move the cars using the moveCars() function in our main loop() by calling the function consecutively for each car in the array.


Your Turn

In the main loop(), add some code that iterates through the array of cars. For each car found, call the moveCars() function passing both the car and the increment to move the car each frame. As we are moving from right to left, this should be a value of -1.


Rendering the Cars Onscreen

After all that leg-work, we are ready to actually render the cars on the screen. The drawCar() function, shown below, takes a car obstacle as a parameter. It then tests to see what obstacle type it is before rendering the appropriate image. The use of the ObstacleType enum makes it really clear what each `if` statement is testing.

The drawSelfMasked() function will render an image at the specified coordinates. The third parameter is the name of the image itself (as defined in our Images.h file) and the fourth parameter is an index for images that contain more than one frame. For now, you can ignore the fourth parameter and set it to a default value of zero. We will use the index parameter in a later step when we implement the turtles. If you are unfamiliar with the various draw methods of the Sprites class, it might be worth reviewing the Sprites Library page for more information.

void drawCar(Obstacle car) {

    if (car.type == ObstacleType::Car) {

        Sprites::drawSelfMasked(car.x, car.y, Car_Small, 0);

    }

    ...
}


Your Turn

Add two additional if statements to handle the remaining two car types, Truck and Van. Their image names are Car_Truck and Car_Van respectively.

Now that we have a simple function that renders a single car, we can wrap this up into an overall function that renders the road sides, traffic lanes and all six cars.


void drawScreen() {

   // Draw road, verge, etc ..
   arduboy.drawLine(0, 26, 127, 26);
   arduboy.drawLine(0, 30, 127, 30);
   arduboy.drawLine(0, 57, 127, 57);
   // Draw road lines ..
   
   for (uint8_t x = 8; x < 128; x = x + 12) {
       arduboy.drawLine(x, 39, x + 4, 39);
       arduboy.drawLine(x, 48, x + 4, 48);
   }


   // Draw cars ..
   for (uint8_t i = 0; i < NUMBER_OF_CARS; i++) {
       drawCar(cars[i]);
   }

}

If you compile the code and upload it to your device, you will see the cars moving from right to left continuously. Pretty cool, huh?

Wow, that was a lot of work to do just to get some cars moving! But the good news is that you have covered a lot of ground – used an enum to describe the obstacle types, used a simple struct to hold the information about each car and created an array of cars from that same structure.

But .. you may have noticed that the cars all move at the same speed and the gap between them is constant which would make avoiding them really simple. In the next section, we will rework the car movements a little to spice the game up.



Prev getting started, Next mixing up the car’s movements