Berry Racer - a Game Programmed in Arduino and Played on a Custom PCB
by JanoschS1 in Circuits > Arduino
5907 Views, 43 Favorites, 0 Comments
Berry Racer - a Game Programmed in Arduino and Played on a Custom PCB
Berry Racer is a game that I came up with and programmed using Arduino's Software. The game is run on a Teensy microcontroller which is connected through a custom PCB (Printed Circuit Board) to a Screen, a Joystick, four buttons, and a piezo speaker. The board can be powered through the micro USB port on the Teensy or with a battery that can be plugged into the back of the PCB, there is also a switch to turn the circuit off when running on battery power. This Device and Program was designed and built by me with the help of Zane Cochran who was the instructor of the CRT 420 - Special Topics Course at Berry College for the Creative Technologies major.
This instructable will go into detail about how to build a GamePad like this one, however, I hope it is also helpful in case you want to build something slightly different. The video that is linked to this intro slide covers the entire build process as well as some of the programming steps that I took, however it does not go into a lot of details about programming. I try to explain why or how I am doing the things that I am doing and in the end, I criticize my shortcomings on this project, I definitely am proud of what I was able to accomplish with this project but there are so many more things that I want to improve in the future. Let me know if you would like to see a version 2 and what improvements you would make! But for this instructable, I will detail the steps I took to accomplish this version 1 GamePad.
The following is a brief overview of the design process and provides all necessary links to the components/equipment/software that is required and will act sort of like your Bill of Materials. Read through the entire thing and figure out what components and/or software you might need.
To begin, I figured out what Game I would make and what I wanted my GamePad to look like. You will need to use Inkscape to create a DXF file for the outline of the board. I then used easyEDA to design the board based on that outline and once I was happy with the board, I ordered the boards to be manufactured. Below is the list of components that I used on the board.
Pushbuttons (4) (If you have an Arduino starter kit, you might have the right ones laying around)
10k resistors (4) (if you have a starter kit you most likely have some 10k's leftover)
Teensy 3.2 (1)
Screen (1)
Joystick (1)
Slide switch (1)
Header Pins (1)
Piezo Buzzer (1)
Batteries (1)
The board will take about 1-2 weeks to be manufactured and shipped to your doorstep. Once you receive your boards, the next step is to gather your components and then solder them into the board. I had access to really nice soldering stations at the lab but any plain soldering iron will get the job done. The minimum order is 5 boards so you will have a couple of extras in case you screw something up with the soldering process. That being said, if you do screw up, try not to screw up with the Teensy or the screen as those are the most expensive components. Helping hands are nice to have when soldering but not required, using rubber bands can also a cheap and easy alternative to holding components into the board while you are soldering, you can actually see me doing this in the video along with helping hands. You'll also want to have some wire cutters and Kapton tape.
Once you have correctly soldered everything into the board, you can begin testing your components by uploading the code that I will provide. This will be the code I used for my class and I will provide explanations of the most important bits to hopefully help your understanding of the code. Part of this code will show you on the screen when you interact with the joystick or the buttons letting you know that everything is working. In order to do any of this, you will need Arduino's software (I recommend downloading the program instead of using the web version as I have not used the web version) as well as the driver for the Teensy (Follow instructions on PJRC page).
Once you have tested that your board and components work, you can go ahead and start programming your game! This instructable will show you how to program my game and hopefully also be helpful for programming a game of your own.
If you want to and have access to a 3D printer, you can also make a case for your GamePad. To model the case I used Fusion 360 but there are other options that you can use. I use Fusion because you can get it and the entire AutoCAD suite for free with a student email account.
If you find any mistakes in this Instructable, please let me know so that I can correct them right away! I am trying to be as detailed as possible but I am human and it is possible that I missed a step or am just straight-up wrong on something since it has been a while since I did this stuff.
I am also not claiming that this is the perfect way to go about making something like this, there are probably more efficient ways but this instructable will detail the steps that I took to make this GamePad and program BerryRacer.
Game and Board Shape Ideation
To start this project, I sat down and thought about what kind of game I wanted to make and what shape the PCB should be, keeping in mind that I wanted to make a case for it later.
Since the game was going to be 2D similar to early Mario games, options may seem somewhat limited, but there are actually a lot of options. Just google Classic Arcade Games for inspiration. Personally, I wanted to be the main character of my game and I wanted it to be related to something in my life. Thinking back to 101 where I built the Lazy-Go, I found inspiration. Berry College has a lot of deer and students won campus so I actually had to look out and dodge those when I was driving the Lazy-Go around campus at 35mph. So, I figured I would make a Lane Shifter Game where I am driving the Lazy Go dodging Students and Deer. The idea is that I would control the left and right movement of the character while the animated bitmaps of the students and deer came down the screen in one of the three lanes.
As fas as the shape of the GamePad goes, I pretty much just looked at classic gaming controllers on google and used a Super Nintendo controller to make an outline for my gamepad. You will need this outline for when you start designing in EasyEDA.
Let me know what game you decide to make!
Using Inkscape to Create a DXF File for a Board Outline
If you don't already have Inkscape, go to the download page, download it and install it. (It is free)
Open Inkscape, once it is open: File > Document Properties: on the Page tab, change your units to inches or mm whatever you prefer. (I will use mm because the Metric system is superior) then change the page size to roughly what size you want your controller to be. You can go measure an Xbox controller or something like that to get an idea for size. I will make the page size 150mm x 80mm. Once you see the box in the background change, you can exit out of the Document Properties Tab.
Next, find an image on google if you want to trace a controller and import it (File > Import). If you want to make something completely from scratch you can try that too. Once you have imported the image, resize it by holding ctrl and dragging on the diagonal arrow and do so until you have roughly covered your page. Then use the tools on the left to draw your outline. I used the "Draw Bezier Curves and Straight lines" tool (blue pen) to get a very rough outline of my board. Then using the "edit path by nodes" tool (black cursor with three blue points) to drag the lines and nodes around until it looks good. Once you click off of it, your outline should look pretty smooth. If you are happy with it, delete the picture and go to File > Save as and select Desktop Cutting Plotter (.dxf) under "Save as type". Save it and when the dialog box pops up, just make sure that you have the correct units, then hit ok. Leave that for now, we will come back to it when we start building the board.
EasyEDA - Schematic
If you already have an EasyEDA account great, if not you can create one for free here.
I'm not sure where it puts you when you set up your account, but try to get to the user center and then start a new project. Name it what you want and give it a description if you want but make sure it's set to private unless you want other people to have access to the board. Hit "create project" and then add a new schematic to the project. The schematic makes sure that you have all your components and that they are all connected correctly with each other.
One thing to note about 2 layered PCBs (Printed Circuit Boards) if you have never worked with them, is that they have a positive and a negative plane so a lot of things on the schematic will be connected to either a GND or VCC symbol.
Make sure you save your work periodically when working on the schematic.
Anyway, start by adding your Teensy 3.2 to the schematic. To add components to the schematic, go to the left of the page where it says Libraries and click on that. Then enter "Teensy 3.2 - ADV" into the search bar and next to where it says classes, make sure that you have User Contributed Selected (all the way to the right). You should see a Teensy 3.2 - ADV contributed by a user named ydethe. This is just one that I know works with the Teensy that I linked on the Intro page, there are probably others but for the sake of simplicity just use that one. Select it, click place and then place one near the center of the page on the schematic. When you have a component selected and it is red, you can press R to rotate it. You probably won't have to rotate the Teensy but it is helpful with some of the other components.
Next, add 4 buttons and 4 10K resistors, where you put them on the page does not really matter, just try to keep things organized. To do this, go to the libraries on the left again and type in "C110153" for the button and "C58673" for the 10K resistor (these will come from LCSC so make sure your classes are set to "LCSC" instead of "User Contributed"). When you select the object you want, click place and you'll be able to place as many as you want until you press escape.
Next, you want to wire them so that the button is connected to both VCC and GND with signal wires being attached to the Teensy I/O ports. To wire components together, go to the top right where it says wiring tools, the top-left tool (line with two dots) is for placing the wire, when you see dots you are creating a connection, you want to see that when you are connecting components but not when you are crossing lines. You can cross wires without connecting them inside the schematic environment but be careful when doing this. VCC symbol is directly below the wire tool and then the GND symbol is four to the right of the wire tool (says "NetFlag GND" when you hover your mouse over it). The ports that I will use are 5-8 (P5-P8), I would recommend using these as well as it will make things less confusing later on. I will include a PDF of the schematic so you can zoom in and out for all the detail.
Next, add the Joystick, search "Adafruit Joystick - ADV" (Contributed by magpie_lark) and place one in the schematic. Similar to the buttons it will need to be connected to both GND and VCC, however, the signal wire needs to be connected to an analog I/O pin since you are dealing with a potentiometer. Connect the X-axis to 22A8 (P22) and the Y-axis to 23A9 (P23), see attached PDF schematic below or attached pictures for clarification.
Once you have the Joystick, you can add the screen. Search "2.2 TFT LCD - ADV" (should say ILI9341 Screen in the description) and place it in the schematic. It is important that you hook this screen up correctly or else it will not communicate with the Teensy. I'll list all connections below, you can also refer to the attached schematic PDF or the images above.
VCC - VCC symbol
GND - GND symbol
CS - 10 (P10)
RST - 3.3V
DC - 9 (P9)
MOSI - 11 (P11)
SCK - 13 (P13.LED)
LED - VCC symbol
MISO - 12 (P12)
You are almost done with the Schematic! Only a couple more small things to do. Connect the top two pins (GND on the left and VIN on the right) of the Teensy to GND and VCC as shown.
To connect the Piezo buzzer, we will just use a double Male Header pin. To place this, go to the left where it says EELib and scroll down to the connectors and choose "Header-Male-2.54_1x2". Place two of these similar to how the schematic is showing them since we will also be using one to connect the battery.
The piezo will need one signal wire (analog) and one ground so connect one side of the Male Header pin to 20A6 (P20) and the other two ground. When you double click on the blue number of the header pins, you can change them to plus and minus signs if you want.
Finally, add a slide switch to the schematic, search "C92657" for this and wire up as shown. Note that this connects to the rest of the circuit through the GND and VCC plane and will turn the circuit on and off if, only a battery is attached to the circuit. If you have the Teensy Plugged in via USB connection the circuit will always be on.
Now that your schematic is finished, you can click on "Convert to PCB" [the box between the Schematic Library Wizard (star icon) and the tools (wrench and screwdriver icon)] to convert your schematic to PCB. When you click this button it will take you into the PCB building environment which is where all your components from the schematic will populate.
Downloads
EasyEDA - Designing Your PCB Part 1 (Setting Up Your Components)
After clicking Convert to PCB, you will get a dialog box in the PCB design environment prompting you for some basic settings, chose your units; mine will be mm (metric is superior, change my mind), you want 2 copper layers (GND and VCC), and you can leave the rest alone because you will delete the rectangular board outline to replace it with the DXF file we made earlier. Click Apply.
Again, make sure you save your work periodically while in the PCB design environment.
So this environment is similar to the schematic environment but there are some major differences to watch out for. The biggest being; which layer you are working on which is signified by the pencil in the "Layers and Objects" box. In the top right is a colorful box called "Layers and Objects". The layers you will be working in are "Toplayer", "BottomLayer", "TopSilkLayer", "BottomSilkLayer", and "BoardOutline". Another super helpful thing is the Grid size and Snap Size on the far right panel. I keep my Grid size at 50mm and I'll change my snap size based on how specifically I want to place components. The smaller it is, the more specific you can get and if you set it at 1mm - 5mm it can be very helpful in lining up your components.
Anyways, you should see a big pink rectangle which is the default board outline, click on it and delete it. All your components should be below that outline, jumbled together, we will worry about those in a minute. Go to the top left next to where it says EasyEDA in blue and click on the folder > Import > DXF... Again make sure you have the correct units and make sure the Layer you are importing this DXF to is the BoardOutLine Layer. Then click "select a DXF file..." and pick yours from where ever you saved it and then place it somewhere on the grid (I prefer the origin at 0,0 which you can see on the rulers at the edge of the black background). Zoom in on your board outline and make sure that there are no gaps, if there are gaps, just click on one of the lines and drag one of the green dots to close the gap. As long as there is overlap, you are good to go, don't worry about the lines not snapping together. Just make sure you make your snap size something really small like .01mm before adjusting the green dots.
If you really want to, I'll upload my DXF file here so you can use it if you wish, I just wouldn't recommend it as I threw it together in 5 minutes to make this instructable to make sure I wasn't skipping any steps, so it's just not that good.
Once you have your board outline ready to go, you can start placing your components however you want. just click and drag. I will put the screen in the middle, the Joystick on the Left, the four buttons on the right, the Teensy, four resistors and two Male Headers will go on the back. I only place the resistors on the back so I have more room when soldering the buttons but it's not required. Also, the Headers don't technically need to be on the back either since they are just two holes but when they silkscreen the boards you will have the outline drawn on the back and I will add "+" and "-" symbols later to remind me how to plug in the battery and piezo. To place a component on the back, select it, look on the right under "Component Attributes" > "Layer" and select "BottomLayer".
Tips for placing components; think about how your fingers would sit and interact with the joystick and buttons, don't be too close to the edge of the board but don't be too far to the center. Make sure the Teensy is placed so that you have easy access to plug it in. Place the Switch so that the part that moves is hanging off the board. Try to watch the blue lines a little bit and try to keep them from crossing. See pictures for clarity on placements.
Downloads
EasyEDA - Designing Your PCB Part 2 (Connecting Everything and Finishing Your Board)
Okay, once you have your components where you want them, now we start dealing with the mess of blue lines. If you hover over the points (or zoom a lot) connected by the blue lines, some of them say GND or VCC, if this is the case you don't need to connect them, they will be connected by the planes, don't worry you'll see later. To connect the other components, go to "PCB Tools" and use the wire tool (looks just like the wire tool from the schematic). Click on one end of the blue line and create a path that doesn't hit any other paths or component connections, besides the one you are trying to connect to. When the pencil is on the top layer (red square), you will be drawing paths on the top layer, if you click on the blue square, you can draw paths on the bottom layer which can help you avoid other paths. This part can be frustrating as there are many paths and connections you need to avoid. You may have to move around or rotate the buttons (or other components) to try to get less crossing blue lines, once you do, I recommend working outwards from the middle of the board to connect all components.
See my picture for how I ran my paths, note that I only had to use one blue path on the BottomLayer of the board to avoid crossing into another path on the TopLayer.
Once you have all non-VCC and GND connections wired up, use the copper area tool (dashed rectangle under the first Pacman looking tool on PCB Tools tab) to connect them. Click on the copper area tool, then draw a rough rectangle all around the board outline, right-click to complete it. If you were editing the top layer, this should have turned the board red, click on the red rectangle that is now surrounding the outline, go over to the right where it says CopperArea Properties and make sure the Net says GND. Now go and edit the Bottom layer and do the same thing, but make the Net; "VCC", this should remove the remaining blue lines. Go over to the left where it says, Design Manager and make sure it says you have all your nets connected, run a DRC Error check by clicking the refresh arrow next to "DRC Errors". If it still says (0), then your board is completely connected.
Now that your board is functionally finished, you can add text on the front or back by adding text to the TopSilkLayer or BottomSilkLayer. The Text tool is the T in the PCB Tools Panel. You need to click on the eyes next to TopLayer and BottomLayer in order to see any test that you place on the BottomSilkLayer. I will also add text next to my header pins to help me identify which pin does what when I get the physical board.
If you decide to move any of your components and this messes up the copper area, click on the copper area rectangle and then click "Rebuild CopperArea" under the CopperArea Properties tab.
If you think you will be making a case you will need a way to attach the PCB to the case. The way I think makes sense is to put some holes into the PCB board which you can do by using the "hole" tool which is located in the top right of the PCB Tools panel. You can place the holes by clicking and then when you are done, click on them again to access the properties tab. here you can change the diameter of the hole to fit a screw that you would use or whatever you would be using to attach the PCB to your case.
Save your work.
If you click on the camera in the top toolbar, then click 3D view, you can view your board in 3D. Notice how the 3D model shows the silkscreen outlines for each component and the text that I added.
Congratulations! Your board should now be completed which means you can order it to be manufactured! To do this, click on the G in the top toolbar (next to where it says BOM) to create your Gerber file which is what manufacturers use to build your boards. It will ask you to check DRC again, I just say yes even though we already did it. Then choose how many you want, what color you want your board and leave all the other settings the same. Then click "Order at JLCPCB", you'll be directed to a JLCPCB webpage which is the manufacturer. It should cost under $10 and be at your doorstep in less than 2 weeks.
Soldering All Your Components Onto the Board
Soldering is one of the most satisfying parts of this project if everything fits together just right. You'll want to have a soldering iron, wire cutters, Kapton tape (it's sort of expensive, you could use another tape if you wanted to), rubber bands and some helping hands.
To start, I put the buttons on the board and soldered them into place, they are really easy as you can bend the legs to have them grab the board. Next, I added the 10K resistors, as shown in the video, I added them on the backside of the board to give me some more room. Then once they were soldered into place, I cut off the excess wire from the resistors.
Next, I added the Teensy and soldered all the legs into place and cut off the excess as close to the board as I could. You need to cut those if your teensy is on the backside of the screen, and then insulate the teensy with tape from the screen so that you are not creating any short circuits.
Then add the screen, make sure that when you push the screen pins in, they do not go in all the way, if you push them in all the way the screen will sit crooked on the board which is visually annoying and harder to fit your case later on. I took a screenshot from the video that sort of shows this (its a little blurry). It is easier to see what I mean in the video.
For the header pins, you can try to use tape or hot glue to hold them in place while you are soldering, these are sort of difficult to keep in place so it is up to you how you want to do it. Lastly, add the joystick and solder it into place. Your board is now completed and ready for testing.
Programming Setup
Before you can start testing and programming your game, you will need to make sure you have all the right software, drivers and libraries.
First off, if you don't have Arduino, download the latest version for it here.
If you have never used a Teensy with Arduino's software, you'll need to get Teensyduino which you can get here. This downloads all the necessary files into your Arduino libraries and allows you to choose the Teensy board while you are in Arduino. Just follow the installer and instructions on the webpage. The most important part is that when you have Arduino open, you go to Tools > Board: click on it and chose "Teensy 3.2 / 3.1". The second most important thing is that you set your Port to the correct one, it should say "(Teensy)" next to it.
When you have this, try to upload a blank sketch to the board. If you don't have any errors, you can move onto the next step which is running the testing code. If you do get an error, google the error to see if you can figure out what the issue is or comment below.
Program Overview
I am going to go ahead and upload the entire code that I used for my GamePad so you can follow along as I break it down in the next steps. Just click on any of the arduino files and the whole program should load with all the tabs.
We learned about all the coding aspects throughout the semester with other projects and mini-labs but for this final project, we were given some starter code to get us started. This starter code organized a basic layout for the program with different tabs and consisted of programming concepts we had learned earlier in the semester. This starter code was programmed by Zane Cochran who is the instructor for most of the CRT classes at Berry College. The different tabs in Arduino all work together but have different functions which I will cover briefly here in this step.
The main tab loads in the various libraries required, sets up different variables and runs the main loop of the program.
ProgRacer is the code for my game which is long and has quite a lot going on. I will try to explain the most important pieces, however, I will not go into every piece of code. If I don't explain something and you are stuck trying to figure it out, feel free to ask down in the comments!
Next is the about page which I never messed with, Zane had set it up for us so that we could add some information about ourselves, he made a template with his own face on it and the rickroll theme song playing which is still there so enjoy that.
progController lets you test your controls (Joystick and buttons) and shows you that the code is interpreting the functions correctly or incorrectly. For example, some joysticks when soldered onto the boards would be pushed to the right but the code interpreted it as being pushed to the left. This is a simple way to see if you have any issues with software and hardware communication or simple programming issues.
progLunar is all the code for Zane's game which is about landing a rocket on the moon in specific safe zones without crashing. I will not go into detail with his code as I didn't write it but it is there if you are curious.
progMain controls the main menu screen which is where you are brought when you turn the GamePad on and where you go after you finish the games. This lets you choose which game you want to play, you can access the about page, and you can get to the controller testing program.
progSplash is a very simple piece of code that displays the splash screen when you first turn on the gamepad.
systemControls is where the microcontroller reads the buttons and the joysticks to determine if the user is pressing the buttons or moving the joystick. You can actually control how fast it is reading these which will allow you to customize your game even more.
systemInfo can tell you the framerate of your game while you are playing it to help you determine any lags or glitches but I don't think anybody ended up using it in the class. I figured I might as well leave it in there though, maybe someone will find it useful. That is all I will say about this tab of the program.
systemSound sets up some basic functions that allow you to use sound in your games, in the menu or whatever else you choose to trigger sound. I never ended up using it in my game but Zane had it set up to trigger when you move around the menu, interact with the controls in the testing program and on his about page so you can look there for examples of how to use the sound functions that he created.
BerryRacerCode: the Main Loop and Bitmaps
Open the code that I gave you and follow along as I explain what the important bits do. Again, if I skip something and you have a question feel free to comment here or on the YouTube video I made.
The first tab is the main tab of the program and contains both the setup and loop function that every Arduino sketch needs to work properly. Before those functions, variables are defined and libraries called in.
To use the LCD screen you will need to include "SPI.h" and "ILI9341_t3.h". These two libraries can be added by going to Sketch > Include Library > Manage Libraries and then search for both of them in the dialog box that pops up. Install them and you should be good to go. The screen also needs to have 2 pins defined (Data and Chip) and needs to be initialized. Notice that in that same block of code we initialized two global variables screenX and screenY, these represent the dimensions of the screen (320 x 240 pixels). These variables can be used across all tabs and functions as they are global which is important because they are useful in position things that are going to be drawn on your screen.
Next, variables and arrays are created to keep track of what each of the controls are doing. The piezo speaker is connected to pin 20.
After that comes the setup portion. Notice the variable "state", this variable will control what state your controller is in. If you look in the loop you can see that there is a switch case that chooses different functions based on the sate. There are 6 cases, one for each function, we have a splash screen, the main menu function, controller debugging, the about page, and the two games. Each part of the program has instructions on how to change the state, after 3 seconds of showing the splash screen the state is changed to 1 which will take you to the main menu. Here the state is changed based on what item in the menu you choose. If you want to play berry racer, the state is set to 5 which triggers all the game code to start running. When you lose or beat the game, it triggers some bitmaps to display before changing the state back to the Main Menu State (1).
In setup, we initialize Serial Comms which is pretty standard if you want to debug things by printing them to Serial. The LCD screens work by turning on specific pixels with colors that you chose. The libraries the ILI9341 uses allow you to draw bitmaps, draw shapes, print text and more. Here is a really good guide as to how to use these screens and how they work. To use this thought you need to have the tft.begin(); code. Set rotation changs the orientation of how things are printed, you may need to change this based on how you design your PCB. fillScreen does exactly what it sounds like. It fills the screen with black, which we do every time the GamePad is turned on. It happens very fast before drawing the logo. The last thing done in set up is setting all the buttons pins to input.
One more thing I want to cover here is Bitmaps. Bitmaps are very important and used in almost every tab of the program. They allow you to display pictures and graphics but since these screens work by turning on each individual pixel, you cant just give it a JPEG file or something like that. It needs instructions for each pixel. To do this, go to Image2cpp and upload an image file you want. Then under Image Settings, make the canvas size 320x 240 if you want to cover the entire screen. Make it smaller if you are using a bitmap for your character sprite. Make the background black, these pixels will be left off. You can mess around with the brightness threshold, the scaling and the centering until you get an image you are happy with. Then under output, chose "Arduino code, single bitmap" and make your identifier what you want your bitmap to be called. Leave draw mode as horizontal and then click Generate code.
Now you have all the code but the formatting sucks and if you paste this in your program it will take a ton of space in your program. Use the textfixer tool to remove your line breaks and then copy that into your program, if you do this correctly you should see a very long line of code.
Now to actually draw a bitmap look at progSplash. You will need to have the "const unsigned char splash [] PROGEMEM = {BITMAP CODE DATA}" which comes from image2cpp. Use "tft.drawBitmap(0, 0, splash, 320, 240, ILI9341_PINK);" to actually draw the Bitmap that in this case was called "splash". The "0, 0" part is the coordinates where the image is drawn at, "320, 240" is the size of the image and ILI9341_PINK is the standard color we chose to make the logo. Just like ILI9341_BLACK from the fill screen earlier, there are standard colors you can choose from when using the screen. I'm not sure what all the colors are. If you want a different color, try it and see if they have it.
If you want images in monochrome(shades of white), you need to go to ditherlicious. Go there open the photo you want, you can mess around with the conversion, contrast, and Exposure but it usually just makes it worse. Save the photo and then go to image2cpp.
ProgMain
This tab controls the main menu screen of the GamePad. The tab is broken into 4 sections as seen by the comments in the code.
The first section sets up the required variables, there are booleans to keep track of when the menu should be initialized and when it should be ended. The curslection variable keeps track of which item in the menu the user is hovering over.
Then there is a string array with the actual texts that will be displayed on the screen along with their corresponding cases in the gameModes array. Since Berry Racer is the second item in the string array, the second item in gameModes is 5 (5 was the case/state that runs the game). You can change how many of these items there are, in this case, there are 4.
There is also a bitmap stored here.
The initMain function draws things that won't change in the main menu like the GameBoy graphic and the Main Menu title. It also will play a jingle every time the menu is initialized.
endMain clears the screen, sets the state to whatever you chose and then resets the booleans and the current selection.
runMain does the drawing of the text and highlighting of the selected text. You see that this function accesses the getControls functions which tells the Teensy to listen to any inputs at every 250ms (getControls(500) would get them every 500ms). When buttonStatus[1] the y-axis of the joystick is 1 or -1, up or down, it triggers the current selection to change, forces the menu to update (updateMain), and plays a sound (sound(20, 100) 20 is the note and 100 is the duration).
Then to update the screen, all the text is redrawn over the old text. One of the sort of weird things about these screens is that nothing goes away once it is drawn onto. So if you write something with white text, you will have to draw a black rectangle over it. The other way to do it is to use tft.setTextColor(color1, color2) which will actually fill in the background of your text. So what is happening here is that all games (menu items) are written in white text with a black background, except for the current selection. That one is drawn in black text and white background which gives it the highlighted effect. color 1 is the text color and color 2 is the background color. To adjust where things are written, you can use tft.setCursor, here, we started 5 pixels from the left side of the screen and then spaced out the lines by 20 pixels. This will change if your text size is different.
SystemControls
The systemControls tab is pretty simple when you break down each part for the joysticks and the buttons. There are really two main parts to this tab. Updating the status of each control, and actually reading each of the pins associated with their specific inputs. Technically there is a third which is clearing the controls but that wasn't necessary for my game as it was just looking at what was happening with my joystick in real-time. I'm actually not entirely sure why Zane put that there but I'm sure he had a reason for it.
You don't need to mess with any of this code but I will try to cover the general idea of it anyways in case you are curious. The joystick is basically two potentiometers so you are analog reading those values. When the value was over 1000 for the x-axis potentiometer, the joystick was pushed to the left so we set x = -1. When the joystick was pushed right, values read were under 100 so that's what triggers x to be set to 1. Now, these reading must be debounced or else you would get a ton of signals from your teensy saying that the joystick is left or right. As mentioned earlier, when getControls(250) is called, it samples the joystick every 250 ms. This is done with timers, the same thing happens with the buttons. however, the buttons are even easier and all pretty much the same as you are just monitoring a high or low signal and not analog. You can hold the button down or joystick in one direction and every 250ms the Teensy will use that signal.
ProgController
This tab draws the debug program that allows you to see your controls visualized on screen. This tab is a great resource that shows how the tft library works with the screen. Objects are being drawn, text is placed, and sized and changed. Sound is also played every time one of the controls is used. I won't go into detail here and the only other tab that I will cover at this point is ProgRacer as that is where the most important bits will be for programming a game like this. Feel Free to explore any tab of the program though!
ProgRacer
There is a lot going on here so I won't spend forever covering every little detail but like the rest of the code covered in the instructable, I will try to cover the ideas in each section to help explain what is going on. Rember you can always ask questions below in the comment section.
At the beginning of the program, there are a bunch of bitmaps being stored. A splash screen, the character sprite (which I'm not really happy with, I'd fix this in version 2), a win and a loss screen. Then there are a bunch of bitmaps for each enemy. This is because the Deer and People are animated, there are a bunch of bitmaps that are slightly different being drawn very fast like a flipbook. One is drawn, a black rectangle erases it and then the slightly different next bitmap is drawn.
I chose the sprites to be 32x26 pixels so the enemies and the main character are all the same size and would fit into the background that I was planning on drawing.
There are 3 levels to the game so I initialize the level variable as level1. The isBR variable is used to keep track of when the game should actually be running, it becomes true when the runBerryRacer() function is called by selecting Berry racer in the main menu (the state/case is changed to 5). After isBR, a bunch of other variables are set up to track the position of the Lazy-Go, when to draw the LG(lazy go) bitmap. Then come enemy variables and game statistic variables. I am using parallel arrays to keep track of the enemies, there can be 9 at most as that is as many as I could track. This is more than can actually fit on a screen while keeping the game possible. Xpositions are the three lanes that enemies and the lazy go can be in. Then there is how enemies can be in staging, this is an area at the top the screen and makes the game possible, if there was 3 in staging, an enemy would spawn in every lane and it would be impossible to get past.
runBerryRacer() is the main function that runs this game and it is pretty much just calling on differnt functions that are written further down in the code. There is the getControls funtiosn that is sampling the user inputs every 150 ms, a funttion that takes care of drawing the lazy go at the right time and right place. Then enemy arrays are populated with enemies in random lanes. These bitmaps must be updated, moved down on the screen and then erased. There is a fucntion that has to check for a collision between the player and the enemy, a simstaple health bar funtion that works by seeing how long you are in contact with an enemy. You can touch enemies but the longer you are colliding with them, the more health you lose. This could be changed to instant death on touching or there could be a damage multiplier based on the level, however, the game is already pretty hard. Finally there is the winLose funtion that monitors you health and progress to determine if you win or lose the game, once you win or lose, you exit the game.
initBR:() When the game is first run and at the beginning of each level initBR is run. This sets enemy spawn interval and travel speeds as well as resetting them. The health bar gets drawn, text is written and the lanes are drawn. Then the lazy go sprite is drawn in the middle to start with. This all happens very fasy but technivally while this is happening, the user has no control over the game yet.
drawLazyGo(): All this function does is change the x-position of where the main character bitmap is drawn. When the joystick is left or right, a black rectangle gets drawn over where the bitmap was, then the xpos is chaged and drawLG is set true which triggers a new Lazy Go being drawn at the new location. I need to add somewhere another trigger that redraws the LG when it experiencing a collision. Currenlty if you don't move the character and get hit by an enemy, that enemy erases the LG and you won't see another LG drawn until you change its position. I'll save that for a V2 if I ever get around to it.
createEnemy(): The isEnemy[] array keeps track of how many enemies there are but it also keeps track of what kind of enemy it should be. It does this by storing a 0, a 1, or a 2 in each of the spots. 0 means there is not an enemy in that slot, 1 means there is deer and 2 means there is a person. If there is a deer or a person and they are less than 40 pixels onto the screen (origin is top left so at the top y = 0) they are counted as being in staging. If there are 2 or less enemies in staging and the spawn interval has been met, a new enemy will spawn. Everytime there is a spawn, isEnemy[i] = random(3); chooses either nothing, a deer or a person. This is done to change things up more for the player and gets rid of some of the lag, when there are more than 3 animated bitmaps on the screen, the game actually starts to slow down significantly.
updateEnemy(): The animation interval is 100ms so every 100ms the people and deer are updated. This works by keeping track of which person and which deer bitmap should be shown. Every time the timer triggers, the whichDeer and Which person increases by one indicating the next bitmap should be shown. There are 5 bitmaps for the Deer and 6 for the person, the % divides and then looks at the remainder. so if whichDeer gets to 6, it gives a remainder of 1 which restarts the animation at the 1st bitmaps. Then there are timers to update each the deer and the person, here the enemies are erased, postitions are updated and then the next bitmap for the animation is chosen through a switch case.
eraseEnemy(): sets enemies back to 0 in the isEnemy array which effectively erases them from existance, freeing up that slot in the array for a new spawn.
collision(): Also very simple, this just checks the positions of the enemies agiant the postion of the Lazy Go.
displayHealth(): Draws the healthbar on the left and subtacts from the health variable every singe time (every frame rate) there is a collision. This means when enemies are traveling faster in the higher levels,they actually do less damage. There should probably be some sort of multiplier but I'll leave that up to you guys to add if you want to!
winLose(): progresses you through the levels by looking at your distance/score and what level you are currenly on. If you make it through all levels you win. You lose by having a health that is less than one. When you win or lose some splash screens are displayed. When you pass a level you get a small break with a message telling you to get ready for the next level.
Exit(): I pretty much used this mainly while debugging the game. Instead of waiting to lose, I can just press the button that matches with buttonStatus[2] to go right back to the menu. In my case buttonStatus[2] was keeping track of my top button also called A in systemControls tab.
That's it! You've made an entire game and GamePad from scratch! Feel free to look through zane's game as well if you are trying to make your own to look for programming inspiration!
Modeling?
This part is completely up to you, this Instructable is already super long and I don't want to add another tutorial to it about how to use Fusion 360 or another 3D modeling software. So I won't go into detail here about how we made them.
We used Fusion 360 to make our cases, attached are the pictures of the renderings for you guys to see, unfortunately, we had some issues with our 3D printers (Zane still claims it was me but I'm gonna blame the printers lol) so they didn't turn out great. I talk about how disappointed I was with the case in the video because it didn't fit that well and it wasn't all that comfortable. I actually prefer playing the GamePad without the case how it is now. Maybe V2 will be different...
But anyways, thanks for reading, hopefully this instructable was somewhat helpful and not too much of a jumbled mess. There will probably be a bunch of typos, I think the site stopped spell checking me once I got to the code explanations, idk. Anyways, go check out my YouTube Channel or instructable profile for my other projects!