Arcade Platform
Let’s explore the Arcade platform, which we plan to use in our dungeon program. Here’s how I like to learn a new system: by example.
The Python Arcade Library looks quite good. I quote from its history page:
The Python Arcade Library was created by Paul Vincent Craven. Paul was a professor of Computer Science at Simpson College in Iowa. He created the library to teach his students how to program games in Python. The first public pre-release was in March 2016.
There is no question the focus of the library is on education. It’s designed to be easy to use and to teach programming concepts.
The library has grown since then and is now used by people all over the world. The library is mostly used in schools and by hobbyists, but have also been used in various other settings such as visualization, simulation and prototyping. As the library grows it’s starting to be used in more professional settings as well.
What sold me on trying this one was looking at the examples on the site. There are many, ranging from a plain window to complete side-scroller games. The library is object-oriented, and there is extensive documentation, much of it quite readable. But the examples sell it for me. I think we learn things like programming more from the doing of it than from reading about it. although both are important. So examples give us a place to start and build upon.
I’ve only been playing with Arcade for an hour or so, setting up a PyCharm project to run some of the examples, so I know essentially nothing, and you should take any statements about how it works as based on knowing essentially nothing. It’ll be more “what I’m gleaning so far” than “what I now know”.
As with most such systems, Arcade has a standard function or method that you fill in to do your drawing. I believe Arcade calls it on_draw, as it is a kind of event. It also has on_update, which is a convenient place to update all the objects that need updating. One interesting aspect of drawing is that there seem to be three performance levels, roughly, as I [mis]understand them:
- Draw each element of the picture one at a time, with individual draw-this-or-that methods;
- Buffer elements individually to a shape list and send the list to the GPU;
- Create complex lists based on elementary structures and send those to the GPU as one item.
I see by trying to write the above that I don’t quite understand. So let’s spend the morning exploring three examples that Arcade provides. I’ll pluck them into my experimental project, run them one at a time, and we’ll look them over together.
The slow one uses about 0.170 seconds per frame, drawing 9600 rectangles. The one that uses lists seems to be requiring 0.0005 seconds per frame. The most complex one is showing about 0.0004 seconds per frame. It appears that the middle one jitters between 4 and 7, the complex one between 3 and 5. I wonder how much of the time is drawing the text, which I’ll show you below.
Right. The numbers don’t change when I stop drawing the rectangles. All the time is in drawing the text.
Anyway, we’re here to learn the techniques, not to debug the benchmarks.
Here’s the sort of thing that gets displayed by each program:

Let’s start with the simplest one and begin to learn how this Arcade thing works. These examples are lifted directly from the Arcade site, whose license permits this, if I am not mistaken.
Direct-Drawing Version
I’ll dissect the program into a few segments, to show how I look at it. First constants and main:
import arcade
import timeit
WINDOW_WIDTH = 1200
WINDOW_HEIGHT = 800
WINDOW_TITLE = "Shape List Demo 1"
SQUARE_WIDTH = 5
SQUARE_HEIGHT = 5
SQUARE_SPACING = 10
def main():
""" Main function """
# Create a window class. This is what actually shows up on screen
window = arcade.Window(WINDOW_WIDTH, WINDOW_HEIGHT, WINDOW_TITLE)
# Create the GameView
game = GameView()
# Show GameView on screen
window.show_view(game)
# Start the arcade game loop
arcade.run()
if __name__ == "__main__":
main()
Pretty standard boilerplate, make a window, make a view (remind me to learn what a view is), show the view in the window. (Does this mean we could have different views in one window?) Tell arcade to run. (This is like calling main_loop in pygame, I imagine.)
OK, nothing to see here. What about the GameView?
WINDOW_WIDTH = 1200
WINDOW_HEIGHT = 800
WINDOW_TITLE = "Shape List Demo 1"
SQUARE_WIDTH = 5
SQUARE_HEIGHT = 5
SQUARE_SPACING = 10
class GameView(arcade.View):
""" Main application class. """
def __init__(self):
super().__init__()
self.background_color = arcade.color.DARK_SLATE_GRAY
self.draw_time = 0
def on_draw(self):
"""
Render the screen.
"""
# This command has to happen before we start drawing
self.clear()
# Start timing how long this takes
draw_start_time = timeit.default_timer()
# --- Draw all the rectangles
for x in range(0, WINDOW_WIDTH, SQUARE_SPACING):
for y in range(0, WINDOW_HEIGHT, SQUARE_SPACING):
arcade.draw_rect_filled(arcade.rect.XYWH(x, y, SQUARE_WIDTH, SQUARE_HEIGHT),
arcade.color.DARK_BLUE)
# Print the timing
output = f"Drawing time: {self.draw_time:.3f} seconds per frame."
arcade.draw_text(output, 20, WINDOW_HEIGHT - 40, arcade.color.WHITE, 18)
self.draw_time = timeit.default_timer() - draw_start_time
Seems all very clear at first glance. We basically ignore the timeit stuff, although it is tempting to move the draw time subtraction ahead of the drawing of the text, to see if we get better information.
Other than that, we clear the screen and draw 1200/10 times 800/10 = 9600 small equally-spaced filled rectangles. The rectangles are 5x5 pixels on 10x10 pixel centers.
I note in passing that we draw our tiles one at a time now, so converting to Arcade might be very straightforward, with one interesting exception:
Arcade, as any reasonable system would do, places the point 0,0 in the obviously correct lower left corner, not the sorely mistaken top left corner as some other systems <cough>pygame</cough> do.
The above is just my opinion, man. And also absolute truth.
What else do we notice? Well, this is interesting, look carefully:
arcade.draw_rect_filled(arcade.rect.XYWH(x, y, SQUARE_WIDTH, SQUARE_HEIGHT),
arcade.color.DARK_BLUE)
The draw_rect_filled method is receiving arcade.rect.XYWH(...)! In pygame, we have this:
pygame.draw.rect(screen, self.room.color,
(dx, dy, cell_size, cell_size))
Pygame draws a rectangle given coordinates and size. Arcade draws a rectangle object:
arcade.rect.XYWH(x, y, SQUARE_WIDTH, SQUARE_HEIGHT)
One of the inconvenient things about pygame, though we haven’t much encountered it in our current project, is that you don’t have object like rectangles and triangles and ellipses: you can only draw them. You could roll your own objects but then you’d wind up disassembling them to feed into the draw.rect function.
I think we’ll find that having objects will pay off. Whether it does or not, it is a significant difference between Arcade and pygame.
There’s not much else that I see here. Let’s look at the intermediate-speed program now.
Shape List Version
We see one small change in main:
def main():
""" Main function """
# Create a window class. This is what actually shows up on screen
window = arcade.Window(WINDOW_WIDTH, WINDOW_HEIGHT, WINDOW_TITLE)
# Create and setup the GameView
game = GameView()
game.setup()
# Show GameView on screen
window.show_view(game)
# Start the arcade game loop
arcade.run()
After we create the GameView, we tell it to set up. We’ll work up to that. First, though, we see a much simpler on_draw:
def on_draw(self):
"""
Render the screen.
"""
# This command has to happen before we start drawing
self.clear()
# Start timing how long this takes
draw_start_time = timeit.default_timer()
# --- Draw all the rectangles
self.shape_list.draw()
output = f"Simple Drawing time: {self.draw_time:.6f} seconds per frame."
arcade.draw_text(output, 20, WINDOW_HEIGHT - 40, arcade.color.WHITE, 18)
self.draw_time = timeit.default_timer() - draw_start_time
Where we had that big loop in the simplest version, we have a single call to self.shape_list.draw(). So all our rectangles must be in that list. Now we explore setup:
def setup(self):
# --- Create the vertex buffers objects for each square before we do
# any drawing.
self.shape_list = arcade.shape_list.ShapeElementList()
for x in range(0, WINDOW_WIDTH, SQUARE_SPACING):
for y in range(0, WINDOW_HEIGHT, SQUARE_SPACING):
shape = arcade.shape_list.create_rectangle_filled(
center_x=x,
center_y=y,
width=SQUARE_WIDTH,
height=SQUARE_HEIGHT,
color=arcade.color.DARK_BLUE,
)
self.shape_list.append(shape)
Let me say right here in front of everyone that I don’t really know what a “vertex buffer object” is. I think they are some internal graphics system magic. Wikipedia tells me this:
A vertex buffer object (VBO) is an OpenGL feature that provides methods for uploading vertex data (position, normal vector, color, etc.) to the video device for non-immediate-mode rendering. VBOs offer substantial performance gains over immediate mode rendering primarily because the data reside in video device memory rather than system memory and so it can be rendered directly by the video device. These are equivalent to vertex buffers in Direct3D.
OK, reading this code, we see that we are creating shape instances, each of which will be a filled rectangle, with those parameters, and we add all 9600 of them to the shape_list, which, we all recall, we drew in one go in on_draw. We also recall that that reduced the time to draw from about 0.17 seconds to about 0.0005 seconds, over 300 times faster. So that’s interesting.
Probably there are lots of create methods on shape list. A glance at the documents shows me rectangles, triangles, ellipses, lines, and polygons. We’ll probably learn more as we work.
Complex Version
Let’s see what the even faster complex version is like. main and on_draw are exactly the same. The only difference is in the setup:
def setup(self):
self.shape_list = arcade.shape_list.ShapeElementList()
# --- Create all the rectangles
# We need a list of all the points and colors
point_list = []
color_list = []
# Now calculate all the points
for x in range(0, WINDOW_WIDTH, SQUARE_SPACING):
for y in range(0, WINDOW_HEIGHT, SQUARE_SPACING):
# Calculate where the four points of the rectangle will be if
# x and y are the center
top_left = (x - HALF_SQUARE_WIDTH, y + HALF_SQUARE_HEIGHT)
top_right = (x + HALF_SQUARE_WIDTH, y + HALF_SQUARE_HEIGHT)
bottom_right = (x + HALF_SQUARE_WIDTH, y - HALF_SQUARE_HEIGHT)
bottom_left = (x - HALF_SQUARE_WIDTH, y - HALF_SQUARE_HEIGHT)
# Add the points to the points list.
# ORDER MATTERS!
# Rotate around the rectangle, don't append points caty-corner
point_list.append(top_left)
point_list.append(top_right)
point_list.append(bottom_right)
point_list.append(bottom_left)
# Add a color for each point. Can be different colors if you want
# gradients.
for i in range(4):
color_list.append(arcade.color.DARK_BLUE)
shape = arcade.shape_list.create_rectangles_filled_with_colors(point_list, color_list)
self.shape_list.append(shape)
We quickly see that we are just creating a single shape, where in the previous version we created one shape per rectangle.
Here we create a long list of corner pixel addresses, four for each rectangle, in one list, and in another list four colors, all blue, one per corner. The comment suggests that we can have different colors per point. I think I’d like to try that to see what happens.
After we create the long lists of points and colors, we tell the shape list create_rectangles_filled_with_colors. Our timing runs seem to indicate that this is faster, but we need better timing.
Looking again at shape list’s API, I see similar methods for triangles and ellipses. At this writing I do not see what those might be good for, although I suppose we can draw anything with enough tiny rectangles, triangles, and ellipses. Maybe. We’ll see if we learn more.
I am curious about the rectangles and colors. First I’ll make the spacing and sizing larger.

Now I’m going to change the colors in the list:
# Add a color for each point. Can be different colors if you want
# gradients.
for i in range(2):
color_list.append(arcade.color.DARK_BLUE)
for i in range(2):
color_list.append(arcade.color.CRIMSON)
And we get this:

I imagine we could get other effects with varying colors, all gradients of course.
I think we’ve done enough for today. Let’s sum up.
Summary
We’ve seen three different ways of drawing rectangles, one at a time, which is slow if you consider 50,000 per second to be slow, or as a batch in a shape_list, which draws around 20 million rectangles per second. And then the rather arcane scheme of setting up corners and colors, which, based on moving the clock read, can draw around a billion rectangles per second. So that’s nice.
It seems to me that using Arcade to draw our trivial few thousand rectangles won’t be at all difficult. Of course, once we do that, we’ll want to start making more of a game. Prior to that, I’ll be looking at more of the Arcade examples.
I should mention that Arcade comes with over 800 assets built into it, including a lot from the Kenney site, which I have used in the past and are quite nice. I suppose the true game-makers make all their own textures, but we aren’t here to play with pixels, we are here to play with code, so we’ll make use of whatever free resources we can find.
So far, I’m finding Arcade to be rather fine. It seems more mature than pygame. Not that it’s older: I don’t know which one came first. Arcade has more object orientation in it, and it can clearly make decent use of the OpenGL facilities. I only wish that Apple hadn’t frozen their version, though I think the limitations won’t hurt us here.
What we see today is an example of how I like to learn a new framework. I work with examples, read them, draw what conclusions I can from them, and often bash them a little bit to try options and check my understanding. Given where we are with our little dungeon, I expect we’ll try plugging Arcade in as soon as tomorrow or the next day: we have learned how to draw a rectangle and I think I see how to draw a line and a dot.
What could go wrong? I’m sure we’ll find out. See you next time!