Learning Go by examples: part 5 - Create a Game Boy Advance (GBA) game in Go
Aurélie Vache
Posted on August 11, 2021
After created an HTTP REST API server, our first CLI (Command Line Interface) application in Go and our Bot for Discord, what can we do now?
What about coding your own Game Boy Advance game?!
Why a Game Boy Advance app/game?
As you may have already noticed in my "Understanding Kubernetes in a visual way" video serie on YouTube (in the background) I am a retrogamer fan.
I am a Developer (and Ops) for more than 15 years, I love retrogaming and I love to play with Go.
But we can only code a game for GBA in C, isn't it? Well, it's not perfectly true, with TinyGo we can code in Go and build for a lot of microcontrollers, WebAssembly... and Game Boy Advance (GBA) machine! Go is really cool, we can do many things with this language!
And, for fun!
So what about creating a game for one of my favorite portable console?
Game Boy Advance
The Game Boy Advance (GBA) was one of the handheld video games console produced by Nintendo released in 2001. In this console you can find a 240x160 (3:2 aspect ratio) 15-bit color LCD display, a multidirectional pad (D-pad), "A", "B", "L", "R", "START" & "SELECT" buttons for actions.
The GBA CPU is based on a 32-bit ARM7TDMI core with embedded memory.
One of the strength of this console is to be backward compatible with Game Boy and Game Boy Color games.
GBA games are loaded in a game cartridge:
If you are interested about the technical specification of this console, you can find a lot of useful information in this article.
Initialization
We created our Git repository in the previous article, so now we just have to retrieve it locally:
$ git clone https://github.com/scraly/learning-go-by-examples.git
$ cd learning-go-by-examples
We will create a folder go-gopher-gba
for our CLI application and go into it:
$ mkdir go-gopher-gba
$ cd go-gopher-gba
Now, we have to initialize Go modules (dependency management):
$ go mod init github.com/scraly/learning-go-by-examples/go-gopher-gba
go: creating new go.mod: module github.com/scraly/learning-go-by-examples/go-gopher-gba
This will create a go.mod
file like this:
module github.com/scraly/learning-go-by-examples/go-gopher-gba
go 1.16
Before to start our super Game Boy Advance (GBA) application, as good practice, we will create a simple code organization.
Create the following folders organization:
.
├── README.md
├── bin
├── go.mod
That's it? Yes, the rest of our code organization will be created shortly ;-).
TinyGo
TinyGo is a Go compiler for embedded systems and to the modern web.
You can compile and run TinyGo programs on a lot of microcontroller boards such as the BBC micro:bit, the Arduino Uno, the Nintendo Switch and the Game Boy Advance ;-).
TinyGo can also produce WebAssembly (WASM) code which is very compact in size. You can compile programs for web browsers, as well as for server and edge computing environments that support the WebAssembly System Interface (WASI) family of interfaces.
When I discovered TinyGo in the begining of 2020 I was just: WoW, it's awesome!!!!
First, we will install TinyGo. If you have a MacOS:
$ brew tap tinygo-org/tools
$ brew install tinygo
Check TinyGo is correctly installed:
$ tinygo version
tinygo version 0.19.0 darwin/amd64 (using go version go1.16.5 and LLVM version 11.0.0)
Let's create our app!
What do we want?
This simple Game Boy Advance app/game should:
- Display a screen with "Gopher" text and "Press START button"
- Display two gophers
- When you press START button: your Gopher player just appear
- With multidirectional arrows you can move your Gopher at left, right, top or bottom
- When you press A button: your Gopher jump :-D
- When you press SELECT button, you go back to "Start" screen
It seems to be cool for a first start, let's do it!
OK, so let's start!
As we can see in the TinyGo website, the Game Boy Advance is a handheld videogame platform based on the ARM7TDMI microcontroller. It's a useful information and we can find in this page the link to the machine
package for GBA... and that's all, we will talk about the lack of real documentation later :-).
Nevertheless, a lack of documentation and up-to-date working example will not stop us. So I will show you the code and explain to you step by step.
As we will display text messages in our app, we will create a fonts
folder and put inside the fonts in Go that we want to use:
fonts
├── freesansbold24pt7b.go
└── gophers58pt.go
You can find theses fonts in my GitHub repository.
After this prerequisite, we will create the code of our app. In order to do this, we will create a gopher.go
file and copy/paste the following code into it.
First, Go code is organized into packages. So, we initialize the package, called main, and all dependencies/librairies we need to import and use in our main file:
package main
import (
"image/color"
"machine"
"runtime/interrupt"
"runtime/volatile"
"unsafe"
"github.com/scraly/learning-go-by-examples/go-gopher-gba/fonts"
"tinygo.org/x/tinydraw"
"tinygo.org/x/tinyfont"
)
Then, we initialize a list of variables:
var (
//KeyCodes / Buttons
keyDOWN = uint16(895)
keyUP = uint16(959)
keyLEFT = uint16(991)
keyRIGHT = uint16(1007)
keyLSHOULDER = uint16(511)
keyRSHOULDER = uint16(767)
keyA = uint16(1022)
keyB = uint16(1021)
keySTART = uint16(1015)
keySELECT = uint16(1019)
// Register display
regDISPSTAT = (*volatile.Register16)(unsafe.Pointer(uintptr(0x4000004)))
// Register keypad
regKEYPAD = (*volatile.Register16)(unsafe.Pointer(uintptr(0x04000130)))
// Display from machine
display = machine.Display
// Screen resolution
screenWidth, screenHeight = display.Size()
// Colors
black = color.RGBA{}
white = color.RGBA{255, 255, 255, 255}
green = color.RGBA{0, 255, 0, 255}
red = color.RGBA{255, 0, 0, 255}
// Google colors
gBlue = color.RGBA{66, 163, 244, 255}
gRed = color.RGBA{219, 68, 55, 255}
gYellow = color.RGBA{244, 160, 0, 255}
gGreen = color.RGBA{15, 157, 88, 255}
// Coordinates
x int16 = 100 //TODO: horizontally center
y int16 = 100 //TODO: vertically center
)
As you can see, we define several variables that we will use in our code, in order to avoid a code complex to read and in order to not copy/paste RGB colors code in several pieces of code.
Then, we define our main()
function that:
- configure and register the display (the console's screen)
- call the function that will draw our texts and Gophers
- plug an
update()
function in an interrupt signal - define an infinite loop to avoid exiting the application
func main() {
// Set up the display
display.Configure()
// Register display status
regDISPSTAT.SetBits(1<<3 | 1<<4)
// Display Gopher text message and draw our Gophers
drawGophers()
// Creates an interrupt that will call the "update" fonction below, hardware way to display things on the screen
interrupt.New(machine.IRQ_VBLANK, update).Enable()
// Infinite loop to avoid exiting the application
for {
}
}
Let's implement our drawGophers()
function:
func drawGophers() {
// Display a textual message "Gopher" with Google colors
tinyfont.DrawChar(display, &fonts.Bold24pt7b, 36, 60, 'G', gBlue)
tinyfont.DrawChar(display, &fonts.Bold24pt7b, 71, 60, 'o', gRed)
tinyfont.DrawChar(display, &fonts.Bold24pt7b, 98, 60, 'p', gYellow)
tinyfont.DrawChar(display, &fonts.Bold24pt7b, 126, 60, 'h', gGreen)
tinyfont.DrawChar(display, &fonts.Bold24pt7b, 154, 60, 'e', gBlue)
tinyfont.DrawChar(display, &fonts.Bold24pt7b, 180, 60, 'r', gRed)
// Display a "press START button" message - center
tinyfont.WriteLine(display, &tinyfont.TomThumb, 85, 90, "Press START button", white)
// Display two gophers
tinyfont.DrawChar(display, &fonts.Regular58pt, 5, 140, 'B', green)
tinyfont.DrawChar(display, &fonts.Regular58pt, 195, 140, 'X', red)
}
As I want to display text messages, I used TinyFont package with fonts we put in fonts
folder... and I used it to display our Gophers too!
For the moment we can't display an image and load a sprite so I had to find a way to display our cute Gophers ;-).
Then, we need to implement our update()
function:
func update(interrupt.Interrupt) {
// Read uint16 from register regKEYPAD that represents the state of current buttons pressed
// and compares it against the defined values for each button on the Gameboy Advance
switch keyValue := regKEYPAD.Get(); keyValue {
// Start the "game"
case keySTART:
// Clear display
clearScreen()
// Display gopher
tinyfont.DrawChar(display, &fonts.Regular58pt, x, y, 'B', green)
// Go back to Menu
case keySELECT:
clearScreen()
drawGophers()
// Gopher go to the right
case keyRIGHT:
// Clear display
clearScreen()
x = x + 10
// display gopher at right
tinyfont.DrawChar(display, &fonts.Regular58pt, x, y, 'B', green)
// Gopher go to the left
case keyLEFT:
// Clear display
clearScreen()
x = x - 10
// display gopher at right
tinyfont.DrawChar(display, &fonts.Regular58pt, x, y, 'B', green)
// Gopher go to the down
case keyDOWN:
// Clear display
clearScreen()
y = y + 10
tinyfont.DrawChar(display, &fonts.Regular58pt, x, y, 'B', green)
// Gopher go to the up
case keyUP:
// Clear display
clearScreen()
y = y - 10
tinyfont.DrawChar(display, &fonts.Regular58pt, x, y, 'B', green)
// Gopher jump
case keyA:
// Clear display
clearScreen()
// Display the gopher up
y = y - 20
tinyfont.DrawChar(display, &fonts.Regular58pt, x, y, 'B', green)
// Clear the display
clearScreen()
// Display the gopher down
y = y + 20
tinyfont.DrawChar(display, &fonts.Regular58pt, x, y, 'B', green)
}
}
Hum... Aurélie, is it possible to explain to us what you have done in this function?
In fact, previously we registered to KeyPad so everytime a button and/or a multidirectional arrow is pressed, the update()
function is called so into it. Then I create a switch case
that allow me to execute code depending on the button pressed.
And, finally, I don't find any existing method that clear screen, or that fill a color in the entire screen, GBA machine TinyGo implementation don't have the needed function. So I use a trick: I create a small function that draw a rectangle with the full size of the screen in black (as black is the background color of the display screen).
func clearScreen() {
tinydraw.FilledRectangle(
display,
int16(0), int16(0),
screenWidth, screenHeight,
black,
)
}
Install needed dependencies
We use external packages/dependencies in gopher.go
file, so we have to install them:
$ go get tinygo.org/x/tinydraw
$ go get tinygo.org/x/tinyfont
TinyFont
TinyGo is not a standalone way to build Go app for microcontrollers and lightweight hardware but the main GitHub repository contains also several useful tools like TinyFont.
TinyFont is afont/text package for TinyGo displays (based on Adafruit's GFX library).
TinyFont repository contains several fonts you can use in your apps and import like this:
import (
"tinygo.org/x/tinyfont/freemono"
"tinygo.org/x/tinyfont/freesans"
"tinygo.org/x/tinyfont/freeserif"
)
If you want to use your personal fonts, you can convert it from BDF format to "TinyGo" compatible format with tinyfontgen tool.
Usage example:
$ tinyfontgen --package my-font --fontname MyFontRegular12pt MyFont-Regular-12pt.bdf --output MyFont-Regular-12pt.go --all
TinyDraw
TinyDraw is an useful tool that allow you to draw geometrics figures (based on Adafruit GFX library).
Thanks to TinyDraw you can easily draw geometrics forms:
white = color.RGBA{255, 255, 255, 255}
green = color.RGBA{0, 255, 0, 255}
red = color.RGBA{255, 0, 0, 255}
// ...
tinydraw.Line(&display, 100, 100, 40, 100, red)
tinydraw.Rectangle(&display, 30, 106, 120, 20, white)
tinydraw.FilledRectangle(&display, 34, 110, 112, 12, green)
tinydraw.Circle(&display, 120, 30, 20, white)
tinydraw.FilledCircle(&display, 120, 30, 16, red)
tinydraw.Triangle(&display, 120, 102, 100, 80, 152, 46, white)
tinydraw.FilledTriangle(&display, 120, 98, 104, 80, 144, 54, green)
Gopher font
As we can't display images and sprites for the moment in GBA hardware with TinyFont, I needed to find a way to display our favorite Gophers, the trick is to use an awesome existing Gopher font created by Jaana Dogan (@rakyll), thanks!!
And you can even play with this font in the 2ttf playground.
GBA emulator
It's time to test our app, but if you don't have a real Game Boy Advance (GBA) retro portable videogame console, it's not a problem because several emulator exists.
TinyGo recommend, and use, mGBA, a Game Boy Advance software emulator so we will install it to test our apps.
First, download mGBA, untar it and then copy it in your PATH:
$ cp ./bin/mgba /usr/local/bin/mgba
As usual, check the executable is working correctly:
$ mgba --version
mgba 0.9.2 (f6d5f51d231053cc8a1778b7a139096d2bcf7324)
Cool!
You can run mGBA with the command line tool mgba
, (located in bin
folder), but you have also the possibility to run the visual application (located in Applications
):
By default, controls are mapped to the keyboard like this:
A: X
B: Z
L: A
R: S
Start: Enter
Select: Backspace
Test it!
It's time to test our little game!
$ tinygo run -target=gameboy-advance gopher.go
tinygo:ld.lld: warning: lld uses blx instruction, no object with architecture supporting feature detected
This command will compile the code, execute mGBA emulator and load your app.
Awesome!
Now, press the "START" button (the ENTER touch in your keyboard), you should see a green Gopher appear at the center of the screen.
With multidirectional arrows you can move the Gopher at left, right, bottom and top.
And if you press the "A" button (X in the keyboard by default), Gopher should do a jump :-).
You can watch the following video with the demo:
Build it!
Your application is ready, you can build it.
For that, like the previous articles, we will use Taskfile in order to automate our common tasks.
So, for this app too, I created a Taskfile.yml
file with this content:
version: "3"
tasks:
run:
desc: Run the app
cmds:
- GOFLAGS=-mod=mod tinygo run -target=gameboy-advance gopher.go
build:
desc: Build the GBA app
cmds:
- GOFLAGS=-mod=mod tinygo build -size short -o bin/gopher.gba -target=gameboy-advance gopher.go
build-mgba:
desc: Build the GBA app for mGBA
cmds:
- GOFLAGS=-mod=mod tinygo build -size short -o bin/gopher.elf -target=gameboy-advance gopher.go
- mv bin/gopher.elf bin/gopher.gba
mgba:
desc: Load the game
cmds:
- mgba bin/gopher.gba
Thanks to this, we can build our app easily:
$ task build
task: [build] GOFLAGS=-mod=mod tinygo build -size short -o bin/gopher.gba -target=gameboy-advance gopher.go
tinygo:ld.lld: warning: lld uses blx instruction, no object with architecture supporting feature detected
code data bss | flash ram
4128 28536 3116 | 32664 31652
$ ll bin/gopher.gba
-rwxr-xr-x 1 aurelievache staff 229K 7 aoû 18:53 bin/gopher.gba
Tips and tricks
build-mgba:
desc: Build the GBA app for mGBA
cmds:
- GOFLAGS=-mod=mod tinygo build -size short -o bin/gopher.elf -target=gameboy-advance gopher.go
- mv bin/gopher.elf bin/gopher.gba
Wait a minute, what is this strange tricks in the build-mgba
task?
Currently, there is a bug with mGBA: tinygo build
command don't build a correct GBA ROM that mGBA can load and run correctly.
$ tinygo build -target=gameboy-advance -o bin/gba-display.gba gba-display.go
tinygo:ld.lld: warning: lld uses blx instruction, no object with architecture supporting feature detected
$ file bin/gba-display.gba
bin/gba-display.gba: data
When you run it through mGBA emulator, the game crash:
$ mgba bin/gba-display.gba
The game crashed!
So after having a discussion with TinyGo contributors, if I build to an .elf
file and then copy it to a .gba
file, it should working... and it's true.
So if you have the same bug like me, you know the trick ;-).
If you want to load it through VisualBoyAdvance app, another GBA emulator, you don't need the trick, the following command will run a working format:
$ tinygo build -size short -o bin/gopher.gba -target=gameboy-advance gopher.go
Test on a real handset!
I don't like emulator, I prefer "physical" retrogaming console so I wanted to test with a real handset. I've got the chance to have one GBA and one GBA SP console in working condition, but how can I transfer my gopher.gba
file into my console?
In order to do that you can use a "linker", like the "EZ Flash Omega" I bought. It allow you to copy your .gba
files into a microSD card you put in this particular GBA cartridge.
It's time to test ... crossed fingers!
The little Gopher app is working in the Game Boy Advance SP! :-)
Easy! Isn't it?
Well ... If I tell you it was very easy to create this game (this simple app with cute Gophers), it's a lie ^^.
TinyGo is a really good tool but one of the pain point is the documentation and concrete up-to-date and working examples :-(.
I will be totally honest to you. I knew & know nothing about microcontrollers, electronics and hardware so without concrete examples, without tutorials, without a good documentation, it was not easy to create this app but I'm very happy to created it and I hope with this article will motivate you to test TinyGo in your side :-).
If you want to test TinyGo, you can play with it through the existing playground.
Moreover, with TitiMoby, we created a fresh new GitLab repository tinygo-examples containing several working and concrete examples in TinyGo with Adafruit PyGamer, GBA... and soon WebAssembly.
Don't hesitate to add your own TinyGo examples! :-)
Conclusion
As you have seen in this article and previous articles, it's possible to create applications in Go: CLI, REST API, Bot for Discord... and also apps for microcontrollers and retro consoles! :-)
All the code of our little Gopher GBA app in Go is available in: https://github.com/scraly/learning-go-by-examples/tree/main/go-gopher-gba
In the following articles we will create others kind/types of applications in Go.
Hope you'll like it.
Posted on August 11, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.