The Python Arcade Library¶
Get Started Here¶
Examples |
Installation |
Tutorials |
Social |
Games |
API |
Distribute Your Game |
|
Source Code |
Performance |
Learn |
Related Libraries |
Quick Arcade Library Introduction Video
Arcade is an easy-to-learn Python library for creating 2D video games. It is ideal for people learning to program, or developers that want to code a 2D game without learning a complex framework.
Get Started Here¶
Installation¶
Arcade can be installed like any other Python Package. Arcade needs support for OpenGL 3.3+. It does not run on Raspberry Pi or Wayland. If on Linux, sound support needs at least GLIB 2.29+. For detailed instructions see Installation Instructions.
Starting Tutorials¶
If you are already familiar with basic Python programming, follow the Simple Platformer or Real Python article. If you are just learning how to program, see the Learn Arcade book.
Arcade Skill Tree¶
Basic Drawing Commands - See How to Draw with Your Computer, Drawing Primitives
ShapeElementLists - Batch together thousands of drawing commands into one using a ShapeElementList. See examples in Faster Drawing with ShapeElementLists.
Sprites - Almost everything in Arcade is done with sprites.
Moving player sprites
Mouse - Collect Coins - Mouse
Keyboard - Sprite Move By Keyboard
Keyboard, slightly more complex but handles multiple keypresses better: Better Move By Keyboard
Keyboard with acceleration, de-acceleration: Acceleration and Friction
Keyboard, rotate and move forward/back like a space ship: Move Sprites By Angle
Game Controller - Game Controller/Joystick
Game controller buttons - Supported, but documentation needed.
Sprite collision detection
Basic detection - Learn arcade book on collisions, Collect Coins - Mouse
Understanding collision detection and spatial hashing
Sprite Hit boxes
Changing - Supported, but documentation needed.
Drawing - Supported, but documentation needed.
Avoid placing items on walls - Randomly Place Coins, But Away From Walls And Other Coins
Sprite drag-and-drop - Supported, but documentation needed.
Drawing sprites in layers
Sprite animation
Change texture on sprite when hit - Change coins
Moving non-player sprites
Bouncing - Sprite Bouncing Coins
Moving towards player - Sprites That Follow The Player
Moving towards player, but with a delay - Sprites That Follow The Player 2
Space-invaders style - Slime Invaders
Can a sprite see the player? - Line of Sight
A-star pathfinding - A-Star Path Finding
Shooting
Player shoots straight up - Shoot Bullets Upwards
Enemy shoots every x frames - Have Enemies Periodically Shoot
Enemy randomly shoots x frames - Have Enemies Randomly Shoot
Player aims - Aim and Shoot Bullets
Enemy aims - Have Enemies Aim at Player
Multi-hit - Supported, but documentation needed.
Physics Engines
SimplePhysicsEngine - Platformer tutorial Step 3 - Add User Control, Learn Arcade Book Simple Physics Engine, Example Move with Walls
PlatformerPhysicsEngine - From the platformer tutorial: Step 4 - Add Gravity,
Ladders - Platformer tutorial Step 10 - Add Ladders, Properties, and a Moving Platform
Using the physics engine on multiple sprites - Supported, but documentation needed.
Pymunk top-down - Supported, needs docs
Pymunk physics engine for a platformer - Pymunk Platformer
Pymunk shooting - Support under development.
View management
Minimal example of using views - Minimal Views Example
Using views to add a pause screen - Using Views for a Pause Screen
Using views to add an instruction and game over screen - Using Views for Instruction and Game Over Screens
Window management
Scrolling - Move with a Scrolling Screen
Add full screen support - Full Screen Example
Allow user to resize the window - Resizable Window
Map Creation
Programmatic creation
Procedural Generation
TMX map creation - Platformer tutorial: Step 8 - Use a Map Editor
Layers - Platformer tutorial: Step 8 - Use a Map Editor
Multiple Levels - Work with levels and a tiled map
Object Layer - Supported, but documentation needed.
Hit-boxes - Supported, but documentation needed.
Animated Tiles - Supported, but documentation needed.
Sound - Learn Arcade book sound chapter
Spatial sound Sound Demo
Particles - Particle Systems
GUI
Buttons
Text boxes
OpenGL Frame buffers
Lights - Lighting Demo
Wrap-around - Supported, but documentation needed.
Mini-map - Mini-Map Defender
Bloom/glow effect - Bloom-Effect Defender
Writing your own shader - Supported, but documentation needed.
Installation Instructions¶
Arcade runs on Windows, Mac OS X, and Linux.
Arcade requires Python 3.6 or newer. It does not run on Python 2.x.
Select the instructions for your platform:
Setting Up a Virtual Environment In PyCharm¶
A Python virtual environment (venv) allows libraries to be installed for just a single project, rather than shared across everyone using the computer. It also does not require administrator privilages to install.
Assuming you already have a project, follow these steps to create a venv:
Step 1: Select File…Settings

Step 2: Click “Project Interpreter”. Then find the gear icon in the upper right. click on it and select “Add”

Step 3: Select Virtualenv Environment from the left. Then create a new
environment. Usually it should be in a folder called venv
in your main
project. PyCharm does not always select the correct location by default, so
carefully look at the path to make sure it is correct, then select “Ok”.

Now a virtual environment has been set up. The standard in Python projects
is to create a file called requirements.txt
and list the packages you
want in there.
PyCharm will automatically ask if you want to install those packages as soon as you type them in. Go ahead and let it.

Installation on Windows¶
To develop with the Arcade library, we need to install Python, then install Arcade.
Step 1: Install Python¶
Install Python from the official Python website:
https://www.python.org/downloads/
When installing Python, make sure to add Python to the path (1) before clicking the Install button (2).

After that, you can just close the dialog. There’s no need to increase the path length, although it doesn’t hurt anything if you do.

Step 2: Install The Arcade Library¶
If you install Arcade as a pre-built library, there are two options on how to do it. The best way is to use a “virtual environment.” This is a collection of Python libraries that only apply to your particular project. You don’t have to worry about libraries for other projects conflicting with your project. You also don’t need “administrator” level privileges to install libraries.
Install Arcade with PyCharm and a Virtual Environment¶
If you are using PyCharm, setting up a virtual environment is easy. Once you’ve got your project, open up the settings:

Select project interpreter:

Create a new virtual environment. Make sure the venv is inside your project folder.

Now you can install libraries. PyCharm will automatically ask to add them if you have a file called requirements.txt that lists the required libraries.

Note
If you are using Python 3.8, the “Numpy” and “Pillow” libraries might try to build themselves from scratch, which will probably error out. To fix, we can manually update “pip” by opening a a PyCharm terminal, and then typing the following into the terminal:
python -m pip install -U --force-reinstall pip
Restart PyCharm (or exit and restart the terminal) and then attempt to install Arcade again.
Install Arcade using the command line interface¶
If you prefer to use the command line interface (CLI), then you can install arcade directly using pip:
pip3 install arcade
If you happen to be using pipenv, then the appropriate command is:
python3 -m pipenv install arcade
Installation on the Mac¶
Go to the Python website and download Python.

Then install it:

Download and install PyCharm. The community edition is free, and WAY better than IDLE.
Download the zip file (or use git) for the Arcade template file.
https://github.com/pythonarcade/template

After you’ve downloaded it, open up the zip file, and pull out the template folder to your desktop or whereever you’d like to save it. Then rename it to your project name.
Start PyCharm, and select File…Open and select the folder you just created.
When creating opening the new project, create a virtual environment like so:

If that doesn’t work, (sometimes PyCharm seems to ignore that, or maybe that step got skipped) go into PyCharm…settings, then “Project interpreter” on the right side, click the easy-to-miss gear icon and “Add”

…Then set it like so:

You should get a warning at the top of the screen that ‘arcade’ is not installed. Go ahead and install it. Then try running the starting template.
Installation on Linux¶
Ubuntu Instructions¶
The Arcade library is Python 3.6+ only. Most versions of Linux come with
Python 2.x. You’ll need to install Python 3 and use it instead of the
built-in Python 2.x. (Usually on Linux and Mac, you can type python3
instead of python
once installed. Same with pip3
instead of
pip
to install packages to Python 3.x)
Install Python 3 and some image dependencies:
apt update && sudo apt install -y python3-dev python3-pip libjpeg-dev zlib1g-dev
Check that you have at least Python 3.6 with:
python3 -V
You need at least version 3.6. If you have that, you should be ready to install Arcade:
sudo pip3 install arcade
Raspberry Pi Instructions¶
Arcade required OpenGL graphics 3.3 or higher. Unfortunately the Raspberry Pi does not support this, Arcade can not run on the Raspberry Pi.
Installation From Source¶
First step is to clone the repository:
git clone https://github.com/pvcraven/arcade.git
Or download from:
Next, we’ll create a linked install. This will allow you to change files in the arcade directory, and is great if you want to modify the Arcade library code. From the root directory of arcade type:
pip install -e .
To install additional documentation and development requirements:
pip install -e .[dev,docs]
# .. or separately
pip install -e .[dev]
pip install -e .[docs]
Pygame Comparison¶
The Python Arcade Library has the same target audience as the well-known Pygame library. So how do they differ?
Features that the Arcade Library has:
Draws stationary sprites much faster. See Drawing Stationary Sprites
Supports Python 3 type hinting.
Thick ellipses, arcs, and circles do not have a moiré pattern.
Ellipses, arcs, and other shapes can be easily rotated.
Uses standard coordinate system you learned about in math. (0, 0) is in the lower left, and not upper left. Y-coordinates are not reversed.
Has built-in physics engine for platformers.
Supports animated sprites.
API documentation for the commands is better.
Command names are consistent. For example, to add to a sprite list you use the
append()
method, like any other list in Python. Pygame usesadd()
.Parameter and command names are clearer. For example, open_window instead of set_mode.
Less boiler-plate code than Pygame.
Basic drawing does not require knowledge on how to define functions or classes or how to do loops.
Encourages separation of logic and display code. Pygame tends to put both into the same game loop.
Runs on top of OpenGL 3+ and Pyglet, rather than the old SDL1 library. (Currently PyGame is in the process of moving to SDL2.)
With the use of sprite lists, uses the acceleration of the graphics card to improve performance.
Easily scale and rotate sprites and graphics.
Images with transparency are transparent by default. No extra code needed.
Lots of How-To Example Code.
Features that Pygame has that the Arcade Library does not:
Has better performance for moving sprites
Python 2 support
Does not require OpenGL, so works on Raspberry Pis
Has better support for pixel manipulation in a memory buffer that isn’t displayed on screen.
Things that are just different:
Sound support: Pygame uses the old, unsupported Avbin library. Arcade uses SoLoud. Supports panning and volume.
Sample Games Made With Arcade¶
Have a sample game you’d like to share here? E-mail paul@cravenfamily.com.
Ready or Not?¶
Ready or Not? a local multiplayer action RPG by Akash S Panickar.
Age of Divisiveness¶

Age of Divisiveness by Patryk Majewski, Krzysztof Szymaniak, Gabriel Wechta, Błażej Wróbel
Multiplayer LAN game with strong Civilization I and old Settlers vibe! Very extensive.
Adventure¶
Stellar Arena Demo¶
The Great Skeleton War¶
The Great Skeleton War, an intense tower defense game, where there’s always something new to discover.
Kayzee¶
PyOverheadGame¶

PyOverheadGame, a 2D overhead game where you go through several rooms and pick up keys and other objects.
Two Worlds¶
Two Worlds, a castle adventure with a dungeon and caverns underneath it.
Simpson College Spring 2017 CMSC 150 Course¶
These games were created by first-semester programming students.
\How-To Example Code¶
Quick API Index¶
The arcade
module¶
Window and View Classes¶
Classes:
NoOpenGLException
View
Window
Functions:
get_screens
open_window
Constants and Data Types:
MOUSE_BUTTON_LEFT
MOUSE_BUTTON_MIDDLE
MOUSE_BUTTON_RIGHT
OpenGL Texture Management¶
Classes:
Matrix3x3
Texture
Functions:
cleanup_texture_cache
load_spritesheet
load_texture
load_texture_pair
load_textures
make_circle_texture
make_soft_circle_texture
make_soft_square_texture
trim_image
Draw Text¶
Classes:
CreateText
Text
Functions:
create_text
draw_text
draw_text_2
get_text_image
render_text
Constants and Data Types:
DEFAULT_FONT_NAMES
Isometric Map Support (incomplete)¶
Functions:
create_isometric_grid_lines
isometric_grid_to_screen
screen_to_isometric_grid
Sprites¶
Classes:
AnimatedTimeBasedSprite
AnimatedTimeSprite
AnimatedWalkingSprite
AnimationKeyframe
PyMunk
Sprite
SpriteCircle
SpriteSolidColor
Functions:
get_distance_between_sprites
Constants and Data Types:
FACE_DOWN
FACE_LEFT
FACE_RIGHT
FACE_UP
Misc Utility Functions¶
Functions:
lerp
lerp_vec
rand_angle_360_deg
rand_angle_spread_deg
rand_in_circle
rand_in_rect
rand_on_circle
rand_on_line
rand_vec_magnitude
rand_vec_spread_deg
Particle¶
Classes:
EternalParticle
FadeParticle
LifetimeParticle
Particle
Functions:
clamp
Constants and Data Types:
FilenameOrTexture
Support for Drawing Commands¶
Functions:
calculate_hit_box_points_detailed
calculate_hit_box_points_simple
get_four_byte_color
get_four_float_color
get_points_for_thick_line
make_transparent_color
rotate_point
Sprite Lists¶
Classes:
SpriteList
Functions:
check_for_collision
check_for_collision_with_list
get_closest_sprite
get_sprites_at_exact_point
get_sprites_at_point
Particle Emitter¶
Classes:
EmitBurst
EmitController
EmitInterval
EmitMaintainCount
Emitter
EmitterIntervalWithCount
EmitterIntervalWithTime
Buffered Draw Commands¶
Classes:
Shape
ShapeElementList
Functions:
create_ellipse
create_ellipse_filled
create_ellipse_filled_with_colors
create_ellipse_outline
create_line
create_line_generic
create_line_generic_with_colors
create_line_loop
create_line_strip
create_lines
create_lines_with_colors
create_polygon
create_rectangle
create_rectangle_filled
create_rectangle_filled_with_colors
create_rectangle_outline
create_rectangles_filled_with_colors
create_triangles_filled_with_colors
get_rectangle_points
Constants and Data Types:
TShape
Arcade Data Types¶
Constants and Data Types:
Color
NamedPoint
Point
PointList
RGB
RGBA
Rect
RectList
Vector
Drawing Primitives¶
Functions:
draw_arc_filled
draw_arc_outline
draw_circle_filled
draw_circle_outline
draw_ellipse_filled
draw_ellipse_outline
draw_line
draw_line_strip
draw_lines
draw_lrtb_rectangle_filled
draw_lrtb_rectangle_outline
draw_lrwh_rectangle_textured
draw_parabola_filled
draw_parabola_outline
draw_point
draw_points
draw_polygon_filled
draw_polygon_outline
draw_rectangle_filled
draw_rectangle_outline
draw_scaled_texture_rectangle
draw_texture_rectangle
draw_triangle_filled
draw_triangle_outline
draw_xywh_rectangle_filled
draw_xywh_rectangle_outline
get_image
get_pixel
Window Commands¶
Functions:
close_window
create_orthogonal_projection
finish_render
get_display_size
get_projection
get_scaling_factor
get_viewport
get_window
pause
quick_run
run
schedule
set_background_color
set_viewport
set_window
start_render
unschedule
The arcade.gl
module¶
OpenGL Types¶
Classes:
AttribFormat
BufferDescription
GLTypes
TypeInfo
Constants and Data Types:
SHADER_TYPE_NAMES
pixel_formats
OpenGL Enums¶
Constants and Data Types:
BLEND_ADDITIVE
BLEND_DEFAULT
BLEND_PREMULTIPLIED_ALPHA
CLAMP_TO_BORDER
CLAMP_TO_EDGE
DST_ALPHA
DST_COLOR
FUNC_ADD
FUNC_REVERSE_SUBTRACT
FUNC_SUBTRACT
LINEAR
LINEAR_MIPMAP_LINEAR
LINEAR_MIPMAP_NEAREST
LINES
LINES_ADJACENCY
LINE_STRIP
LINE_STRIP_ADJACENCY
MAX
MIN
MIRRORED_REPEAT
NEAREST
NEAREST_MIPMAP_LINEAR
NEAREST_MIPMAP_NEAREST
ONE
ONE_MINUS_DST_ALPHA
ONE_MINUS_DST_COLOR
ONE_MINUS_SRC_ALPHA
ONE_MINUS_SRC_COLOR
PATCHES
POINTS
REPEAT
SRC_ALPHA
SRC_COLOR
TRIANGLES
TRIANGLES_ADJACENCY
TRIANGLE_FAN
TRIANGLE_STRIP
TRIANGLE_STRIP_ADJACENCY
ZERO
The arcade.gui
module¶
GUI Utilities¶
Functions:
add_margin
get_image_with_text
get_text_image
parse_value
render_text_image
Core GUI¶
Classes:
UIElement
UIEvent
UIException
Constants and Data Types:
KEY_PRESS
KEY_RELEASE
MOUSE_MOTION
MOUSE_PRESS
MOUSE_RELEASE
MOUSE_SCROLL
TEXT_INPUT
TEXT_MOTION
TEXT_MOTION_SELECTION
Arcade Package API¶
This page documents the Application Programming Interface (API) for the Python Arcade library. Soo also:
arcade.key package¶
Mapping of keyboard keys to values.
"""
Constants used to signify what keys on the keyboard were pressed.
"""
# Key modifiers
# Done in powers of two, so you can do a bit-wise 'and' to detect
# multiple modifiers.
MOD_SHIFT = 1
MOD_CTRL = 2
MOD_ALT = 4
MOD_CAPSLOCK = 8
MOD_NUMLOCK = 16
MOD_WINDOWS = 32
MOD_COMMAND = 64
MOD_OPTION = 128
MOD_SCROLLLOCK = 256
MOD_ACCEL = 2
# Keys
BACKSPACE = 65288
TAB = 65289
LINEFEED = 65290
CLEAR = 65291
RETURN = 65293
ENTER = 65293
PAUSE = 65299
SCROLLLOCK = 65300
SYSREQ = 65301
ESCAPE = 65307
HOME = 65360
LEFT = 65361
UP = 65362
RIGHT = 65363
DOWN = 65364
PAGEUP = 65365
PAGEDOWN = 65366
END = 65367
BEGIN = 65368
DELETE = 65535
SELECT = 65376
PRINT = 65377
EXECUTE = 65378
INSERT = 65379
UNDO = 65381
REDO = 65382
MENU = 65383
FIND = 65384
CANCEL = 65385
HELP = 65386
BREAK = 65387
MODESWITCH = 65406
SCRIPTSWITCH = 65406
MOTION_UP = 65362
MOTION_RIGHT = 65363
MOTION_DOWN = 65364
MOTION_LEFT = 65361
MOTION_NEXT_WORD = 1
MOTION_PREVIOUS_WORD = 2
MOTION_BEGINNING_OF_LINE = 3
MOTION_END_OF_LINE = 4
MOTION_NEXT_PAGE = 65366
MOTION_PREVIOUS_PAGE = 65365
MOTION_BEGINNING_OF_FILE = 5
MOTION_END_OF_FILE = 6
MOTION_BACKSPACE = 65288
MOTION_DELETE = 65535
NUMLOCK = 65407
NUM_SPACE = 65408
NUM_TAB = 65417
NUM_ENTER = 65421
NUM_F1 = 65425
NUM_F2 = 65426
NUM_F3 = 65427
NUM_F4 = 65428
NUM_HOME = 65429
NUM_LEFT = 65430
NUM_UP = 65431
NUM_RIGHT = 65432
NUM_DOWN = 65433
NUM_PRIOR = 65434
NUM_PAGE_UP = 65434
NUM_NEXT = 65435
NUM_PAGE_DOWN = 65435
NUM_END = 65436
NUM_BEGIN = 65437
NUM_INSERT = 65438
NUM_DELETE = 65439
NUM_EQUAL = 65469
NUM_MULTIPLY = 65450
NUM_ADD = 65451
NUM_SEPARATOR = 65452
NUM_SUBTRACT = 65453
NUM_DECIMAL = 65454
NUM_DIVIDE = 65455
# Numbers on the numberpad
NUM_0 = 65456
NUM_1 = 65457
NUM_2 = 65458
NUM_3 = 65459
NUM_4 = 65460
NUM_5 = 65461
NUM_6 = 65462
NUM_7 = 65463
NUM_8 = 65464
NUM_9 = 65465
F1 = 65470
F2 = 65471
F3 = 65472
F4 = 65473
F5 = 65474
F6 = 65475
F7 = 65476
F8 = 65477
F9 = 65478
F10 = 65479
F11 = 65480
F12 = 65481
F13 = 65482
F14 = 65483
F15 = 65484
F16 = 65485
LSHIFT = 65505
RSHIFT = 65506
LCTRL = 65507
RCTRL = 65508
CAPSLOCK = 65509
LMETA = 65511
RMETA = 65512
LALT = 65513
RALT = 65514
LWINDOWS = 65515
RWINDOWS = 65516
LCOMMAND = 65517
RCOMMAND = 65518
LOPTION = 65488
ROPTION = 65489
SPACE = 32
EXCLAMATION = 33
DOUBLEQUOTE = 34
HASH = 35
POUND = 35
DOLLAR = 36
PERCENT = 37
AMPERSAND = 38
APOSTROPHE = 39
PARENLEFT = 40
PARENRIGHT = 41
ASTERISK = 42
PLUS = 43
COMMA = 44
MINUS = 45
PERIOD = 46
SLASH = 47
# Numbers on the main keyboard
KEY_0 = 48
KEY_1 = 49
KEY_2 = 50
KEY_3 = 51
KEY_4 = 52
KEY_5 = 53
KEY_6 = 54
KEY_7 = 55
KEY_8 = 56
KEY_9 = 57
COLON = 58
SEMICOLON = 59
LESS = 60
EQUAL = 61
GREATER = 62
QUESTION = 63
AT = 64
BRACKETLEFT = 91
BACKSLASH = 92
BRACKETRIGHT = 93
ASCIICIRCUM = 94
UNDERSCORE = 95
GRAVE = 96
QUOTELEFT = 96
A = 97
B = 98
C = 99
D = 100
E = 101
F = 102
G = 103
H = 104
# noinspection PyPep8
I = 105
J = 106
K = 107
L = 108
M = 109
N = 110
# noinspection PyPep8
O = 111
P = 112
Q = 113
R = 114
S = 115
T = 116
U = 117
V = 118
W = 119
X = 120
Y = 121
Z = 122
BRACELEFT = 123
BAR = 124
BRACERIGHT = 125
ASCIITILDE = 126
arcade.csscolor package¶
These are standard CSS named colors you can use when drawing.
You can specify colors four ways:
Standard CSS color names (this package):
arcade.csscolor.RED
Nonstandard color names arcade.color package:
arcade.color.RED
Three-byte numbers:
(255, 0, 0)
Four-byte numbers (fourth byte is transparency. 0 transparent, 255 opaque):
(255, 0, 0, 255)
ALICE_BLUE | (240, 248, 255) | |
ANTIQUE_WHITE | (250, 235, 215) | |
AQUA | (0, 255, 255) | |
AQUAMARINE | (127, 255, 212) | |
AZURE | (240, 255, 255) | |
BEIGE | (245, 245, 220) | |
BISQUE | (255, 228, 196) | |
BLACK | (0, 0, 0) | |
BLANCHED_ALMOND | (255, 235, 205) | |
BLUE | (0, 0, 255) | |
BLUE_VIOLET | (138, 43, 226) | |
BROWN | (165, 42, 42) | |
BURLYWOOD | (222, 184, 135) | |
CADET_BLUE | (95, 158, 160) | |
CHARTREUSE | (127, 255, 0) | |
CHOCOLATE | (210, 105, 30) | |
CORAL | (255, 127, 80) | |
CORNFLOWER_BLUE | (100, 149, 237) | |
CORNSILK | (255, 248, 220) | |
CRIMSON | (220, 20, 60) | |
CYAN | (0, 255, 255) | |
DARK_BLUE | (0, 0, 139) | |
DARK_CYAN | (0, 139, 139) | |
DARK_GOLDENROD | (184, 134, 11) | |
DARK_GRAY | (169, 169, 169) | |
DARK_GREEN | (0, 100, 0) | |
DARK_GREY | (169, 169, 169) | |
DARK_KHAKI | (189, 183, 107) | |
DARK_MAGENTA | (139, 0, 139) | |
DARK_OLIVE_GREEN | (85, 107, 47) | |
DARK_ORANGE | (255, 140, 0) | |
DARK_ORCHID | (153, 50, 204) | |
DARK_RED | (139, 0, 0) | |
DARK_SALMON | (233, 150, 122) | |
DARK_SEA_GREEN | (143, 188, 143) | |
DARK_SLATE_BLUE | (72, 61, 139) | |
DARK_SLATE_GRAY | (47, 79, 79) | |
DARK_SLATE_GREY | (47, 79, 79) | |
DARK_TURQUOISE | (0, 206, 209) | |
DARK_VIOLET | (148, 0, 211) | |
DEEP_PINK | (255, 20, 147) | |
DEEP_SKY_BLUE | (0, 191, 255) | |
DIM_GRAY | (105, 105, 105) | |
DIM_GREY | (105, 105, 105) | |
DODGER_BLUE | (30, 144, 255) | |
FIREBRICK | (178, 34, 34) | |
FLORAL_WHITE | (255, 250, 240) | |
FOREST_GREEN | (34, 139, 34) | |
FUCHSIA | (255, 0, 255) | |
GAINSBORO | (220, 220, 220) | |
GHOST_WHITE | (248, 248, 255) | |
GOLD | (255, 215, 0) | |
GOLDENROD | (218, 165, 32) | |
GRAY | (128, 128, 128) | |
GREEN | (0, 128, 0) | |
GREENYELLOW | (173, 255, 47) | |
GREY | (128, 128, 128) | |
HONEYDEW | (240, 255, 240) | |
HOTPINK | (255, 105, 180) | |
INDIANRED | (205, 92, 92) | |
INDIGO | (75, 0, 130) | |
IVORY | (255, 255, 240) | |
KHAKI | (240, 230, 140) | |
LAVENDER | (230, 230, 250) | |
LAVENDER_BLUSH | (255, 240, 245) | |
LAWNGREEN | (124, 252, 0) | |
LEMON_CHIFFON | (255, 250, 205) | |
LIGHT_BLUE | (173, 216, 230) | |
LIGHT_CORAL | (240, 128, 128) | |
LIGHT_CYAN | (224, 255, 255) | |
LIGHT_GOLDENROD_YELLOW | (250, 250, 210) | |
LIGHT_GRAY | (211, 211, 211) | |
LIGHT_GREEN | (144, 238, 144) | |
LIGHT_GREY | (211, 211, 211) | |
LIGHT_PINK | (255, 182, 193) | |
LIGHT_SALMON | (255, 160, 122) | |
LIGHT_SEA_GREEN | (32, 178, 170) | |
LIGHT_SKY_BLUE | (135, 206, 250) | |
LIGHT_SLATE_GRAY | (119, 136, 153) | |
LIGHT_SLATE_GREY | (119, 136, 153) | |
LIGHT_STEEL_BLUE | (176, 196, 222) | |
LIGHT_YELLOW | (255, 255, 224) | |
LIME | (0, 255, 0) | |
LIME_GREEN | (50, 205, 50) | |
LINEN | (250, 240, 230) | |
MAGENTA | (255, 0, 255) | |
MAROON | (128, 0, 0) | |
MEDIUM_AQUAMARINE | (102, 205, 170) | |
MEDIUM_BLUE | (0, 0, 205) | |
MEDIUM_ORCHID | (186, 85, 211) | |
MEDIUM_PURPLE | (147, 112, 219) | |
MEDIUM_SEA_GREEN | (60, 179, 113) | |
MEDIUM_SLATE_BLUE | (123, 104, 238) | |
MEDIUM_SPRING_GREEN | (0, 250, 154) | |
MEDIUM_TURQUOISE | (72, 209, 204) | |
MEDIUM_VIOLET_RED | (199, 21, 133) | |
MIDNIGHT_BLUE | (25, 25, 112) | |
MINT_CREAM | (245, 255, 250) | |
MISTY_ROSE | (255, 228, 225) | |
MOCCASIN | (255, 228, 181) | |
NAVAJO_WHITE | (255, 222, 173) | |
NAVY | (0, 0, 128) | |
OLD_LACE | (253, 245, 230) | |
OLIVE | (128, 128, 0) | |
OLIVE_DRAB | (107, 142, 35) | |
ORANGE | (255, 165, 0) | |
ORANGE_RED | (255, 69, 0) | |
ORCHID | (218, 112, 214) | |
PALE_GOLDENROD | (238, 232, 170) | |
PALE_GREEN | (152, 251, 152) | |
PALE_TURQUOISE | (175, 238, 238) | |
PALE_VIOLET_RED | (219, 112, 147) | |
PAPAYA_WHIP | (255, 239, 213) | |
PEACH_PUFF | (255, 218, 185) | |
PERU | (205, 133, 63) | |
PINK | (255, 192, 203) | |
PLUM | (221, 160, 221) | |
POWDER_BLUE | (176, 224, 230) | |
PURPLE | (128, 0, 128) | |
RED | (255, 0, 0) | |
ROSY_BROWN | (188, 143, 143) | |
ROYAL_BLUE | (65, 105, 225) | |
SADDLE_BROWN | (139, 69, 19) | |
SALMON | (250, 128, 114) | |
SANDY_BROWN | (244, 164, 96) | |
SEA_GREEN | (46, 139, 87) | |
SEASHELL | (255, 245, 238) | |
SIENNA | (160, 82, 45) | |
SILVER | (192, 192, 192) | |
SKY_BLUE | (135, 206, 235) | |
SLATE_BLUE | (106, 90, 205) | |
SLATE_GRAY | (112, 128, 144) | |
SLATE_GREY | (112, 128, 144) | |
SNOW | (255, 250, 250) | |
SPRING_GREEN | (0, 255, 127) | |
STEEL_BLUE | (70, 130, 180) | |
TAN | (210, 180, 140) | |
TEAL | (0, 128, 128) | |
THISTLE | (216, 191, 216) | |
TOMATO | (255, 99, 71) | |
TURQUOISE | (64, 224, 208) | |
VIOLET | (238, 130, 238) | |
WHEAT | (245, 222, 179) | |
WHITE | (255, 255, 255) | |
WHITE_SMOKE | (245, 245, 245) | |
YELLOW | (255, 255, 0) | |
YELLOW_GREEN | (154, 205) |
arcade.color package¶
These are named colors you can use when drawing.
You can specify colors four ways:
Standard CSS color names arcade.csscolor package:
arcade.csscolor.RED
Nonstandard color names (this package):
arcade.color.RED
Three-byte numbers:
(255, 0, 0)
Four-byte numbers (fourth byte is transparency. 0 transparent, 255 opaque):
(255, 0, 0, 255)
AERO_BLUE | (201, 255, 229) | |
AFRICAN_VIOLET | (178, 132, 190) | |
AIR_FORCE_BLUE | (93, 138, 168) | |
AIR_SUPERIORITY_BLUE | (114, 160, 193) | |
ALABAMA_CRIMSON | (175, 0, 42) | |
ALICE_BLUE | (240, 248, 255) | |
ALIZARIN_CRIMSON | (227, 38, 54) | |
ALLOY_ORANGE | (196, 98, 16) | |
ALMOND | (239, 222, 205) | |
AMARANTH | (229, 43, 80) | |
AMARANTH_PINK | (241, 156, 187) | |
AMARANTH_PURPLE | (171, 39, 79) | |
AMAZON | (59, 122, 87) | |
AMBER | (255, 191, 0) | |
SAE | (255, 126, 0) | |
AMERICAN_ROSE | (255, 3, 62) | |
AMETHYST | (153, 102, 204) | |
ANDROID_GREEN | (164, 198, 57) | |
ANTI_FLASH_WHITE | (242, 243, 244) | |
ANTIQUE_BRASS | (205, 149, 117) | |
ANTIQUE_BRONZE | (102, 93, 30) | |
ANTIQUE_FUCHSIA | (145, 92, 131) | |
ANTIQUE_RUBY | (132, 27, 45) | |
ANTIQUE_WHITE | (250, 235, 215) | |
AO | (0, 128, 0) | |
APPLE_GREEN | (141, 182, 0) | |
APRICOT | (251, 206, 177) | |
AQUA | (0, 255, 255) | |
AQUAMARINE | (127, 255, 212) | |
ARMY_GREEN | (75, 83, 32) | |
ARSENIC | (59, 68, 75) | |
ARTICHOKE | (143, 151, 121) | |
ARYLIDE_YELLOW | (233, 214, 107) | |
ASH_GREY | (178, 190, 181) | |
ASPARAGUS | (135, 169, 107) | |
ATOMIC_TANGERINE | (255, 153, 102) | |
AUBURN | (165, 42, 42) | |
AUREOLIN | (253, 238, 0) | |
AUROMETALSAURUS | (110, 127, 128) | |
AVOCADO | (86, 130, 3) | |
AZURE | (0, 127, 255) | |
AZURE_MIST | (240, 255, 255) | |
BABY_BLUE | (137, 207, 240) | |
BABY_BLUE_EYES | (161, 202, 241) | |
BABY_PINK | (244, 194, 194) | |
BABY_POWDER | (254, 254, 250) | |
BAKER_MILLER_PINK | (255, 145, 175) | |
BALL_BLUE | (33, 171, 205) | |
BANANA_MANIA | (250, 231, 181) | |
BANANA_YELLOW | (255, 225, 53) | |
BANGLADESH_GREEN | (0, 106, 78) | |
BARBIE_PINK | (224, 33, 138) | |
BARN_RED | (124, 10, 2) | |
BATTLESHIP_GREY | (132, 132, 130) | |
BAZAAR | (152, 119, 123) | |
BEAU_BLUE | (188, 212, 230) | |
BRIGHT_LILAC | (216, 145, 239) | |
BEAVER | (159, 129, 112) | |
BEIGE | (245, 245, 220) | |
BISQUE | (255, 228, 196) | |
BISTRE | (61, 43, 31) | |
BISTRE_BROWN | (150, 113, 23) | |
BITTER_LEMON | (202, 224, 13) | |
BITTER_LIME | (100, 140, 17) | |
BITTERSWEET | (254, 111, 94) | |
BITTERSWEET_SHIMMER | (191, 79, 81) | |
BLACK | (0, 0, 0) | |
BLACK_BEAN | (61, 12, 2) | |
BLACK_LEATHER_JACKET | (37, 53, 41) | |
BLACK_OLIVE | (59, 60, 54) | |
BLANCHED_ALMOND | (255, 235, 205) | |
BLAST_OFF_BRONZE | (165, 113, 100) | |
BLEU_DE_FRANCE | (49, 140, 231) | |
BLIZZARD_BLUE | (172, 229, 238) | |
BLOND | (250, 240, 190) | |
BLUE | (0, 0, 255) | |
BLUE_BELL | (162, 162, 208) | |
BLUE_GRAY | (102, 153, 204) | |
BLUE_GREEN | (13, 152, 186) | |
BLUE_SAPPHIRE | (18, 97, 128) | |
BLUE_VIOLET | (138, 43, 226) | |
BLUE_YONDER | (80, 114, 167) | |
BLUEBERRY | (79, 134, 247) | |
BLUEBONNET | (28, 28, 240) | |
BLUSH | (222, 93, 131) | |
BOLE | (121, 68, 59) | |
BONDI_BLUE | (0, 149, 182) | |
BONE | (227, 218, 201) | |
BOSTON_UNIVERSITY_RED | (204, 0, 0) | |
BOTTLE_GREEN | (0, 106, 78) | |
BOYSENBERRY | (135, 50, 96) | |
BRANDEIS_BLUE | (0, 112, 255) | |
BRASS | (181, 166, 66) | |
BRICK_RED | (203, 65, 84) | |
BRIGHT_CERULEAN | (29, 172, 214) | |
BRIGHT_GREEN | (102, 255, 0) | |
BRIGHT_LAVENDER | (191, 148, 228) | |
BRIGHT_MAROON | (195, 33, 72) | |
BRIGHT_NAVY_BLUE | (25, 116, 210) | |
BRIGHT_PINK | (255, 0, 127) | |
BRIGHT_TURQUOISE | (8, 232, 222) | |
BRIGHT_UBE | (209, 159, 232) | |
BRILLIANT_LAVENDER | (244, 187, 255) | |
BRILLIANT_ROSE | (255, 85, 163) | |
BRINK_PINK | (251, 96, 127) | |
BRITISH_RACING_GREEN | (0, 66, 37) | |
BRONZE | (205, 127, 50) | |
BRONZE_YELLOW | (115, 112, 0) | |
BROWN | (165, 42, 42) | |
BROWN_NOSE | (107, 68, 35) | |
BRUNSWICK_GREEN | (27, 77, 62) | |
BUBBLE_GUM | (255, 193, 204) | |
BUBBLES | (231, 254, 255) | |
BUD_GREEN | (123, 182, 97) | |
BUFF | (240, 220, 130) | |
BULGARIAN_ROSE | (72, 6, 7) | |
BURGUNDY | (128, 0, 32) | |
BURLYWOOD | (222, 184, 135) | |
BURNT_ORANGE | (204, 85, 0) | |
BURNT_SIENNA | (233, 116, 81) | |
BURNT_UMBER | (138, 51, 36) | |
BYZANTINE | (189, 51, 164) | |
BYZANTIUM | (112, 41, 99) | |
CADET | (83, 104, 114) | |
CADET_BLUE | (95, 158, 160) | |
CADET_GREY | (145, 163, 176) | |
CADMIUM_GREEN | (0, 107, 60) | |
CADMIUM_ORANGE | (237, 135, 45) | |
CADMIUM_RED | (227, 0, 34) | |
CADMIUM_YELLOW | (255, 246, 0) | |
CAL_POLY_GREEN | (30, 77, 43) | |
CAMBRIDGE_BLUE | (163, 193, 173) | |
CAMEL | (193, 154, 107) | |
CAMEO_PINK | (239, 187, 204) | |
CAMOUFLAGE_GREEN | (120, 134, 107) | |
CANARY_YELLOW | (255, 239, 0) | |
CANDY_APPLE_RED | (255, 8, 0) | |
CANDY_PINK | (228, 113, 122) | |
CAPRI | (0, 191, 255) | |
CAPUT_MORTUUM | (89, 39, 32) | |
CARDINAL | (196, 30, 58) | |
CARIBBEAN_GREEN | (0, 204, 153) | |
CARMINE | (150, 0, 24) | |
CARMINE_PINK | (235, 76, 66) | |
CARMINE_RED | (255, 0, 56) | |
CARNATION_PINK | (255, 166, 201) | |
CARNELIAN | (179, 27, 27) | |
CAROLINA_BLUE | (153, 186, 221) | |
CARROT_ORANGE | (237, 145, 33) | |
CASTLETON_GREEN | (0, 86, 63) | |
CATALINA_BLUE | (6, 42, 120) | |
CATAWBA | (112, 54, 66) | |
CEDAR_CHEST | (201, 90, 73) | |
CEIL | (146, 161, 207) | |
CELADON | (172, 225, 175) | |
CELADON_BLUE | (0, 123, 167) | |
CELADON_GREEN | (47, 132, 124) | |
CELESTE | (178, 255, 255) | |
CELESTIAL_BLUE | (73, 151, 208) | |
CERISE | (222, 49, 99) | |
CERISE_PINK | (236, 59, 131) | |
CERULEAN | (0, 123, 167) | |
CERULEAN_BLUE | (42, 82, 190) | |
CERULEAN_FROST | (109, 155, 195) | |
CG_BLUE | (0, 122, 165) | |
CG_RED | (224, 60, 49) | |
CHAMOISEE | (160, 120, 90) | |
CHAMPAGNE | (247, 231, 206) | |
CHARCOAL | (54, 69, 79) | |
CHARLESTON_GREEN | (35, 43, 43) | |
CHARM_PINK | (230, 143, 172) | |
CHARTREUSE | (127, 255, 0) | |
CHERRY | (222, 49, 99) | |
CHERRY_BLOSSOM_PINK | (255, 183, 197) | |
CHESTNUT | (149, 69, 53) | |
CHINA_PINK | (222, 111, 161) | |
CHINA_ROSE | (168, 81, 110) | |
CHINESE_RED | (170, 56, 30) | |
CHINESE_VIOLET | (133, 96, 136) | |
CHOCOLATE | (210, 105, 30) | |
CHROME_YELLOW | (255, 167, 0) | |
CINEREOUS | (152, 129, 123) | |
CINNABAR | (227, 66, 52) | |
CINNAMON | (210, 105, 30) | |
CITRINE | (228, 208, 10) | |
CITRON | (159, 169, 31) | |
CLARET | (127, 23, 52) | |
CLASSIC_ROSE | (251, 204, 231) | |
COAL | (124, 185, 232) | |
COBALT | (0, 71, 171) | |
COCOA_BROWN | (210, 105, 30) | |
COCONUT | (150, 90, 62) | |
COFFEE | (111, 78, 55) | |
COLUMBIA_BLUE | (155, 221, 255) | |
CONGO_PINK | (248, 131, 121) | |
COOL_BLACK | (0, 46, 99) | |
COOL_GREY | (140, 146, 172) | |
COPPER | (184, 115, 51) | |
COPPER_PENNY | (173, 111, 105) | |
COPPER_RED | (203, 109, 81) | |
COPPER_ROSE | (153, 102, 102) | |
COQUELICOT | (255, 56, 0) | |
CORAL | (255, 127, 80) | |
CORAL_PINK | (248, 131, 121) | |
CORAL_RED | (255, 64, 64) | |
CORDOVAN | (137, 63, 69) | |
CORN | (251, 236, 93) | |
CORNELL_RED | (179, 27, 27) | |
CORNFLOWER_BLUE | (100, 149, 237) | |
CORNSILK | (255, 248, 220) | |
COSMIC_LATTE | (255, 248, 231) | |
COTTON_CANDY | (255, 188, 217) | |
CREAM | (255, 253, 208) | |
CRIMSON | (220, 20, 60) | |
CRIMSON_GLORY | (190, 0, 50) | |
CYAN | (0, 255, 255) | |
CYBER_GRAPE | (88, 66, 124) | |
CYBER_YELLOW | (255, 211, 0) | |
DAFFODIL | (255, 255, 49) | |
DANDELION | (240, 225, 48) | |
DARK_BLUE | (0, 0, 139) | |
DARK_BLUE_GRAY | (102, 102, 153) | |
DARK_BROWN | (101, 67, 33) | |
DARK_BYZANTIUM | (93, 57, 84) | |
DARK_CANDY_APPLE_RED | (164, 0, 0) | |
DARK_CERULEAN | (8, 69, 126) | |
DARK_CHESTNUT | (152, 105, 96) | |
DARK_CORAL | (205, 91, 69) | |
DARK_CYAN | (0, 139, 139) | |
DARK_ELECTRIC_BLUE | (83, 104, 120) | |
DARK_GOLDENROD | (184, 134, 11) | |
DARK_GRAY | (169, 169, 169) | |
DARK_GREEN | (1, 50, 32) | |
DARK_IMPERIAL_BLUE | (0, 65, 106) | |
DARK_JUNGLE_GREEN | (26, 36, 33) | |
DARK_KHAKI | (189, 183, 107) | |
DARK_LAVA | (72, 60, 50) | |
DARK_LAVENDER | (115, 79, 150) | |
DARK_LIVER | (83, 75, 79) | |
DARK_MAGENTA | (139, 0, 139) | |
DARK_MIDNIGHT_BLUE | (0, 51, 102) | |
DARK_MOSS_GREEN | (74, 93, 35) | |
DARK_OLIVE_GREEN | (85, 107, 47) | |
DARK_ORANGE | (255, 140, 0) | |
DARK_ORCHID | (153, 50, 204) | |
DARK_PASTEL_BLUE | (119, 158, 203) | |
DARK_PASTEL_GREEN | (3, 192, 60) | |
DARK_PASTEL_PURPLE | (150, 111, 214) | |
DARK_PASTEL_RED | (194, 59, 34) | |
DARK_PINK | (231, 84, 128) | |
DARK_POWDER_BLUE | (0, 51, 153) | |
DARK_PUCE | (79, 58, 60) | |
DARK_RASPBERRY | (135, 38, 87) | |
DARK_RED | (139, 0, 0) | |
DARK_SALMON | (233, 150, 122) | |
DARK_SCARLET | (86, 3, 25) | |
DARK_SEA_GREEN | (143, 188, 143) | |
DARK_SIENNA | (60, 20, 20) | |
DARK_SKY_BLUE | (140, 190, 214) | |
DARK_SLATE_BLUE | (72, 61, 139) | |
DARK_SLATE_GRAY | (47, 79, 79) | |
DARK_SPRING_GREEN | (23, 114, 69) | |
DARK_TAN | (145, 129, 81) | |
DARK_TANGERINE | (255, 168, 18) | |
DARK_TAUPE | (72, 60, 50) | |
DARK_TERRA_COTTA | (204, 78, 92) | |
DARK_TURQUOISE | (0, 206, 209) | |
DARK_VANILLA | (209, 190, 168) | |
DARK_VIOLET | (148, 0, 211) | |
DARK_YELLOW | (155, 135, 12) | |
DARTMOUTH_GREEN | (0, 112, 60) | |
DAVY_GREY | (85, 85, 85) | |
DEBIAN_RED | (215, 10, 83) | |
DEEP_CARMINE | (169, 32, 62) | |
DEEP_CARMINE_PINK | (239, 48, 56) | |
DEEP_CARROT_ORANGE | (233, 105, 44) | |
DEEP_CERISE | (218, 50, 135) | |
DEEP_CHAMPAGNE | (250, 214, 165) | |
DEEP_CHESTNUT | (185, 78, 72) | |
DEEP_COFFEE | (112, 66, 65) | |
DEEP_FUCHSIA | (193, 84, 193) | |
DEEP_JUNGLE_GREEN | (0, 75, 73) | |
DEEP_LEMON | (245, 199, 26) | |
DEEP_LILAC | (153, 85, 187) | |
DEEP_MAGENTA | (204, 0, 204) | |
DEEP_MAUVE | (212, 115, 212) | |
DEEP_MOSS_GREEN | (53, 94, 59) | |
DEEP_PEACH | (255, 203, 164) | |
DEEP_PINK | (255, 20, 147) | |
DEEP_PUCE | (169, 92, 104) | |
DEEP_RUBY | (132, 63, 91) | |
DEEP_SAFFRON | (255, 153, 51) | |
DEEP_SKY_BLUE | (0, 191, 255) | |
DEEP_SPACE_SPARKLE | (74, 100, 108) | |
DEEP_TAUPE | (126, 94, 96) | |
DEEP_TUSCAN_RED | (102, 66, 77) | |
DEER | (186, 135, 89) | |
DENIM | (21, 96, 189) | |
DESERT | (193, 154, 107) | |
DESERT_SAND | (237, 201, 175) | |
DESIRE | (234, 60, 83) | |
DIAMOND | (185, 242, 255) | |
DIM_GRAY | (105, 105, 105) | |
DIRT | (155, 118, 83) | |
DODGER_BLUE | (30, 144, 255) | |
DOGWOOD_ROSE | (215, 24, 104) | |
DOLLAR_BILL | (133, 187, 101) | |
DONKEY_BROWN | (102, 76, 40) | |
DRAB | (150, 113, 23) | |
DUKE_BLUE | (0, 0, 156) | |
DUST_STORM | (229, 204, 201) | |
DUTCH_WHITE | (239, 223, 187) | |
EARTH_YELLOW | (225, 169, 95) | |
EBONY | (85, 93, 80) | |
ECRU | (194, 178, 128) | |
EERIE_BLACK | (27, 27, 27) | |
EGGPLANT | (97, 64, 81) | |
EGGSHELL | (240, 234, 214) | |
EGYPTIAN_BLUE | (16, 52, 166) | |
ELECTRIC_BLUE | (125, 249, 255) | |
ELECTRIC_CRIMSON | (255, 0, 63) | |
ELECTRIC_CYAN | (0, 255, 255) | |
ELECTRIC_GREEN | (0, 255, 0) | |
ELECTRIC_INDIGO | (111, 0, 255) | |
ELECTRIC_LAVENDER | (244, 187, 255) | |
ELECTRIC_LIME | (204, 255, 0) | |
ELECTRIC_PURPLE | (191, 0, 255) | |
ELECTRIC_ULTRAMARINE | (63, 0, 255) | |
ELECTRIC_VIOLET | (143, 0, 255) | |
ELECTRIC_YELLOW | (255, 255, 0) | |
EMERALD | (80, 200, 120) | |
EMINENCE | (108, 48, 130) | |
ENGLISH_GREEN | (27, 77, 62) | |
ENGLISH_LAVENDER | (180, 131, 149) | |
ENGLISH_RED | (171, 75, 82) | |
ENGLISH_VIOLET | (86, 60, 92) | |
ETON_BLUE | (150, 200, 162) | |
EUCALYPTUS | (68, 215, 168) | |
FALLOW | (193, 154, 107) | |
FALU_RED | (128, 24, 24) | |
FANDANGO | (181, 51, 137) | |
FANDANGO_PINK | (222, 82, 133) | |
FASHION_FUCHSIA | (244, 0, 161) | |
FAWN | (229, 170, 112) | |
FELDGRAU | (77, 93, 83) | |
FELDSPAR | (253, 213, 177) | |
FERN_GREEN | (79, 121, 66) | |
FERRARI_RED | (255, 40, 0) | |
FIELD_DRAB | (108, 84, 30) | |
FIREBRICK | (178, 34, 34) | |
FIRE_ENGINE_RED | (206, 32, 41) | |
FLAME | (226, 88, 34) | |
FLAMINGO_PINK | (252, 142, 172) | |
FLATTERY | (107, 68, 35) | |
FLAVESCENT | (247, 233, 142) | |
FLAX | (238, 220, 130) | |
FLIRT | (162, 0, 109) | |
FLORAL_WHITE | (255, 250, 240) | |
FLUORESCENT_ORANGE | (255, 191, 0) | |
FLUORESCENT_PINK | (255, 20, 147) | |
FLUORESCENT_YELLOW | (204, 255, 0) | |
FOLLY | (255, 0, 79) | |
FOREST_GREEN | (34, 139, 34) | |
FRENCH_BEIGE | (166, 123, 91) | |
FRENCH_BISTRE | (133, 109, 77) | |
FRENCH_BLUE | (0, 114, 187) | |
FRENCH_FUCHSIA | (253, 63, 146) | |
FRENCH_LILAC | (134, 96, 142) | |
FRENCH_LIME | (158, 253, 56) | |
FRENCH_MAUVE | (212, 115, 212) | |
FRENCH_PINK | (253, 108, 158) | |
FRENCH_PUCE | (78, 22, 9) | |
FRENCH_RASPBERRY | (199, 44, 72) | |
FRENCH_ROSE | (246, 74, 138) | |
FRENCH_SKY_BLUE | (119, 181, 254) | |
FRENCH_WINE | (172, 30, 68) | |
FRESH_AIR | (166, 231, 255) | |
FUCHSIA | (255, 0, 255) | |
FUCHSIA_PINK | (255, 119, 255) | |
FUCHSIA_PURPLE | (204, 57, 123) | |
FUCHSIA_ROSE | (199, 67, 117) | |
FULVOUS | (228, 132, 0) | |
FUZZY_WUZZY | (204, 102, 102) | |
GAINSBORO | (220, 220, 220) | |
GAMBOGE | (228, 155, 15) | |
GENERIC_VIRIDIAN | (0, 127, 102) | |
GHOST_WHITE | (248, 248, 255) | |
GIANTS_ORANGE | (254, 90, 29) | |
GINGER | (176, 101, 0) | |
GLAUCOUS | (96, 130, 182) | |
GLITTER | (230, 232, 250) | |
GO_GREEN | (0, 171, 102) | |
GOLD | (255, 215, 0) | |
GOLD_FUSION | (133, 117, 78) | |
GOLDEN_BROWN | (153, 101, 21) | |
GOLDEN_POPPY | (252, 194, 0) | |
GOLDEN_YELLOW | (255, 223, 0) | |
GOLDENROD | (218, 165, 32) | |
GRANNY_SMITH_APPLE | (168, 228, 160) | |
GRAPE | (111, 45, 168) | |
GRAY | (128, 128, 128) | |
GRAY_ASPARAGUS | (70, 89, 69) | |
GRAY_BLUE | (140, 146, 172) | |
GREEN | (0, 255, 0) | |
GREEN_YELLOW | (173, 255, 47) | |
GRULLO | (169, 154, 134) | |
GUPPIE_GREEN | (0, 255, 127) | |
HAN_BLUE | (68, 108, 207) | |
HAN_PURPLE | (82, 24, 250) | |
HANSA_YELLOW | (233, 214, 107) | |
HARLEQUIN | (63, 255, 0) | |
HARVARD_CRIMSON | (201, 0, 22) | |
HARVEST_GOLD | (218, 145, 0) | |
HEART_GOLD | (128, 128, 0) | |
HELIOTROPE | (223, 115, 255) | |
HELIOTROPE_GRAY | (170, 152, 169) | |
HOLLYWOOD_CERISE | (244, 0, 161) | |
HONEYDEW | (240, 255, 240) | |
HONOLULU_BLUE | (0, 109, 176) | |
HOOKER_GREEN | (73, 121, 107) | |
HOT_MAGENTA | (255, 29, 206) | |
HOT_PINK | (255, 105, 180) | |
HUNTER_GREEN | (53, 94, 59) | |
ICEBERG | (113, 166, 210) | |
ICTERINE | (252, 247, 94) | |
ILLUMINATING_EMERALD | (49, 145, 119) | |
IMPERIAL | (96, 47, 107) | |
IMPERIAL_BLUE | (0, 35, 149) | |
IMPERIAL_PURPLE | (102, 2, 60) | |
IMPERIAL_RED | (237, 41, 57) | |
INCHWORM | (178, 236, 93) | |
INDEPENDENCE | (76, 81, 109) | |
INDIA_GREEN | (19, 136, 8) | |
INDIAN_RED | (205, 92, 92) | |
INDIAN_YELLOW | (227, 168, 87) | |
INDIGO | (75, 0, 130) | |
INTERNATIONAL_KLEIN_BLUE | (0, 47, 167) | |
INTERNATIONAL_ORANGE | (255, 79, 0) | |
IRIS | (90, 79, 207) | |
IRRESISTIBLE | (179, 68, 108) | |
ISABELLINE | (244, 240, 236) | |
ISLAMIC_GREEN | (0, 144, 0) | |
ITALIAN_SKY_BLUE | (178, 255, 255) | |
IVORY | (255, 255, 240) | |
JADE | (0, 168, 107) | |
JAPANESE_CARMINE | (157, 41, 51) | |
JAPANESE_INDIGO | (38, 67, 72) | |
JAPANESE_VIOLET | (91, 50, 86) | |
JASMINE | (248, 222, 126) | |
JASPER | (215, 59, 62) | |
JAZZBERRY_JAM | (165, 11, 94) | |
JELLY_BEAN | (218, 97, 78) | |
JET | (52, 52, 52) | |
JONQUIL | (244, 202, 22) | |
JORDY_BLUE | (138, 185, 241) | |
JUNE_BUD | (189, 218, 87) | |
JUNGLE_GREEN | (41, 171, 135) | |
KELLY_GREEN | (76, 187, 23) | |
KENYAN_COPPER | (124, 28, 5) | |
KEPPEL | (58, 176, 158) | |
KHAKI | (195, 176, 145) | |
KOBE | (136, 45, 23) | |
KOBI | (231, 159, 196) | |
KOMBU_GREEN | (53, 66, 48) | |
KU_CRIMSON | (232, 0, 13) | |
LA_SALLE_GREEN | (8, 120, 48) | |
LANGUID_LAVENDER | (214, 202, 221) | |
LAPIS_LAZULI | (38, 97, 156) | |
LASER_LEMON | (255, 255, 102) | |
LAUREL_GREEN | (169, 186, 157) | |
LAVA | (207, 16, 32) | |
LAVENDER | (230, 230, 250) | |
LAVENDER_BLUE | (204, 204, 255) | |
LAVENDER_BLUSH | (255, 240, 245) | |
LAVENDER_GRAY | (196, 195, 208) | |
LAVENDER_INDIGO | (148, 87, 235) | |
LAVENDER_MAGENTA | (238, 130, 238) | |
LAVENDER_MIST | (230, 230, 250) | |
LAVENDER_PINK | (251, 174, 210) | |
LAVENDER_PURPLE | (150, 123, 182) | |
LAVENDER_ROSE | (251, 160, 227) | |
LAWN_GREEN | (124, 252, 0) | |
LEMON | (255, 247, 0) | |
LEMON_CHIFFON | (255, 250, 205) | |
LEMON_CURRY | (204, 160, 29) | |
LEMON_GLACIER | (253, 255, 0) | |
LEMON_LIME | (227, 255, 0) | |
LEMON_MERINGUE | (246, 234, 190) | |
LEMON_YELLOW | (255, 244, 79) | |
LIBERTY | (84, 90, 167) | |
LICORICE | (26, 17, 16) | |
LIGHT_APRICOT | (253, 213, 177) | |
LIGHT_BLUE | (173, 216, 230) | |
LIGHT_BROWN | (181, 101, 29) | |
LIGHT_CARMINE_PINK | (230, 103, 113) | |
LIGHT_CORAL | (240, 128, 128) | |
LIGHT_CORNFLOWER_BLUE | (147, 204, 234) | |
LIGHT_CRIMSON | (245, 105, 145) | |
LIGHT_CYAN | (224, 255, 255) | |
LIGHT_DEEP_PINK | (255, 92, 205) | |
LIGHT_FUCHSIA_PINK | (249, 132, 239) | |
LIGHT_GOLDENROD_YELLOW | (250, 250, 210) | |
LIGHT_GRAY | (211, 211, 211) | |
LIGHT_GREEN | (144, 238, 144) | |
LIGHT_HOT_PINK | (255, 179, 222) | |
LIGHT_KHAKI | (240, 230, 140) | |
LIGHT_MEDIUM_ORCHID | (211, 155, 203) | |
LIGHT_MOSS_GREEN | (173, 223, 173) | |
LIGHT_ORCHID | (230, 168, 215) | |
LIGHT_PASTEL_PURPLE | (177, 156, 217) | |
LIGHT_PINK | (255, 182, 193) | |
LIGHT_RED_OCHRE | (233, 116, 81) | |
LIGHT_SALMON | (255, 160, 122) | |
LIGHT_SALMON_PINK | (255, 153, 153) | |
LIGHT_SEA_GREEN | (32, 178, 170) | |
LIGHT_SKY_BLUE | (135, 206, 250) | |
LIGHT_SLATE_GRAY | (119, 136, 153) | |
LIGHT_STEEL_BLUE | (176, 196, 222) | |
LIGHT_TAUPE | (179, 139, 109) | |
LIGHT_THULIAN_PINK | (230, 143, 172) | |
LIGHT_YELLOW | (255, 255, 224) | |
LILAC | (200, 162, 200) | |
LIME | (191, 255, 0) | |
LIME_GREEN | (50, 205, 50) | |
LIMERICK | (157, 194, 9) | |
LINCOLN_GREEN | (25, 89, 5) | |
LINEN | (250, 240, 230) | |
LION | (193, 154, 107) | |
LISERAN_PURPLE | (222, 111, 161) | |
LITTLE_BOY_BLUE | (108, 160, 220) | |
LIVER | (103, 76, 71) | |
LIVER_CHESTNUT | (152, 116, 86) | |
LIVID | (102, 153, 204) | |
LUMBER | (255, 228, 205) | |
LUST | (230, 32, 32) | |
MAGENTA | (255, 0, 255) | |
MAGENTA_HAZE | (159, 69, 118) | |
MAGIC_MINT | (170, 240, 209) | |
MAGNOLIA | (248, 244, 255) | |
MAHOGANY | (192, 64, 0) | |
MAIZE | (251, 236, 93) | |
MAJORELLE_BLUE | (96, 80, 220) | |
MALACHITE | (11, 218, 81) | |
MANATEE | (151, 154, 170) | |
MANGO_TANGO | (255, 130, 67) | |
MANTIS | (116, 195, 101) | |
MARDI_GRAS | (136, 0, 133) | |
MAROON | (128, 0, 0) | |
MAUVE | (224, 176, 255) | |
MAUVE_TAUPE | (145, 95, 109) | |
MAUVELOUS | (239, 152, 170) | |
MAYA_BLUE | (115, 194, 251) | |
MEAT_BROWN | (229, 183, 59) | |
MEDIUM_AQUAMARINE | (102, 221, 170) | |
MEDIUM_BLUE | (0, 0, 205) | |
MEDIUM_CANDY_APPLE_RED | (226, 6, 44) | |
MEDIUM_CARMINE | (175, 64, 53) | |
MEDIUM_CHAMPAGNE | (243, 229, 171) | |
MEDIUM_ELECTRIC_BLUE | (3, 80, 150) | |
MEDIUM_JUNGLE_GREEN | (28, 53, 45) | |
MEDIUM_LAVENDER_MAGENTA | (221, 160, 221) | |
MEDIUM_ORCHID | (186, 85, 211) | |
MEDIUM_PERSIAN_BLUE | (0, 103, 165) | |
MEDIUM_PURPLE | (147, 112, 219) | |
MEDIUM_RED_VIOLET | (187, 51, 133) | |
MEDIUM_RUBY | (170, 64, 105) | |
MEDIUM_SEA_GREEN | (60, 179, 113) | |
MEDIUM_SLATE_BLUE | (123, 104, 238) | |
MEDIUM_SPRING_BUD | (201, 220, 135) | |
MEDIUM_SPRING_GREEN | (0, 250, 154) | |
MEDIUM_SKY_BLUE | (128, 218, 235) | |
MEDIUM_TAUPE | (103, 76, 71) | |
MEDIUM_TURQUOISE | (72, 209, 204) | |
MEDIUM_TUSCAN_RED | (121, 68, 59) | |
MEDIUM_VERMILION | (217, 96, 59) | |
MEDIUM_VIOLET_RED | (199, 21, 133) | |
MELLOW_APRICOT | (248, 184, 120) | |
MELLOW_YELLOW | (248, 222, 126) | |
MELON | (253, 188, 180) | |
METALLIC_SEAWEED | (10, 126, 140) | |
METALLIC_SUNBURST | (156, 124, 56) | |
MEXICAN_PINK | (228, 0, 124) | |
MIDNIGHT_BLUE | (25, 25, 112) | |
MIDNIGHT_GREEN | (0, 73, 83) | |
MIKADO_YELLOW | (255, 196, 12) | |
MINDARO | (227, 249, 136) | |
MINT | (62, 180, 137) | |
MINT_CREAM | (245, 255, 250) | |
MINT_GREEN | (152, 255, 152) | |
MISTY_ROSE | (255, 228, 225) | |
MOCCASIN | (250, 235, 215) | |
MODE_BEIGE | (150, 113, 23) | |
MOONSTONE_BLUE | (115, 169, 194) | |
MOSS_GREEN | (138, 154, 91) | |
MOUNTAIN_MEADOW | (48, 186, 143) | |
MOUNTBATTEN_PINK | (153, 122, 141) | |
MSU_GREEN | (24, 69, 59) | |
MUGHAL_GREEN | (48, 96, 48) | |
MULBERRY | (197, 75, 140) | |
MUSTARD | (255, 219, 88) | |
MYRTLE_GREEN | (49, 120, 115) | |
NADESHIKO_PINK | (246, 173, 198) | |
NAPIER_GREEN | (42, 128, 0) | |
NAPLES_YELLOW | (250, 218, 94) | |
NAVAJO_WHITE | (255, 222, 173) | |
NAVY_BLUE | (0, 0, 128) | |
NAVY_PURPLE | (148, 87, 235) | |
NEON_CARROT | (255, 163, 67) | |
NEON_FUCHSIA | (254, 65, 100) | |
NEON_GREEN | (57, 255, 20) | |
NEW_CAR | (33, 79, 198) | |
NEW_YORK_PINK | (215, 131, 127) | |
NON_PHOTO_BLUE | (164, 221, 237) | |
NYANZA | (233, 255, 219) | |
OCEAN_BOAT_BLUE | (0, 119, 190) | |
OCHRE | (204, 119, 34) | |
OFFICE_GREEN | (0, 128, 0) | |
OLD_BURGUNDY | (67, 48, 46) | |
OLD_GOLD | (207, 181, 59) | |
OLD_HELIOTROPE | (86, 60, 92) | |
OLD_LACE | (253, 245, 230) | |
OLD_LAVENDER | (121, 104, 120) | |
OLD_MAUVE | (103, 49, 71) | |
OLD_MOSS_GREEN | (134, 126, 54) | |
OLD_ROSE | (192, 128, 129) | |
OLD_SILVER | (132, 132, 130) | |
OLIVE | (128, 128, 0) | |
OLIVE_DRAB | (107, 142, 35) | |
OLIVINE | (154, 185, 115) | |
ONYX | (53, 56, 57) | |
OPERA_MAUVE | (183, 132, 167) | |
ORANGE | (255, 165, 0) | |
ORANGE_PEEL | (255, 159, 0) | |
ORANGE_RED | (255, 69, 0) | |
ORCHID | (218, 112, 214) | |
ORCHID_PINK | (242, 141, 205) | |
ORIOLES_ORANGE | (251, 79, 20) | |
OTTER_BROWN | (101, 67, 33) | |
OUTER_SPACE | (65, 74, 76) | |
OUTRAGEOUS_ORANGE | (255, 110, 74) | |
OXFORD_BLUE | (0, 33, 71) | |
OU_CRIMSON_RED | (153, 0, 0) | |
PAKISTAN_GREEN | (0, 102, 0) | |
PALATINATE_BLUE | (39, 59, 226) | |
PALATINATE_PURPLE | (104, 40, 96) | |
PALE_AQUA | (188, 212, 230) | |
PALE_BLUE | (175, 238, 238) | |
PALE_BROWN | (152, 118, 84) | |
PALE_CARMINE | (175, 64, 53) | |
PALE_CERULEAN | (155, 196, 226) | |
PALE_CHESTNUT | (221, 173, 175) | |
PALE_COPPER | (218, 138, 103) | |
PALE_CORNFLOWER_BLUE | (171, 205, 239) | |
PALE_GOLD | (230, 190, 138) | |
PALE_GOLDENROD | (238, 232, 170) | |
PALE_GREEN | (152, 251, 152) | |
PALE_LAVENDER | (220, 208, 255) | |
PALE_MAGENTA | (249, 132, 229) | |
PALE_PINK | (250, 218, 221) | |
PALE_PLUM | (221, 160, 221) | |
PALE_RED_VIOLET | (219, 112, 147) | |
PALE_ROBIN_EGG_BLUE | (150, 222, 209) | |
PALE_SILVER | (201, 192, 187) | |
PALE_SPRING_BUD | (236, 235, 189) | |
PALE_TAUPE | (188, 152, 126) | |
PALE_TURQUOISE | (175, 238, 238) | |
PALE_VIOLET_RED | (219, 112, 147) | |
PANSY_PURPLE | (120, 24, 74) | |
PAOLO_VERONESE_GREEN | (0, 155, 125) | |
PAPAYA_WHIP | (255, 239, 213) | |
PARADISE_PINK | (230, 62, 98) | |
PARIS_GREEN | (80, 200, 120) | |
PASTEL_BLUE | (174, 198, 207) | |
PASTEL_BROWN | (131, 105, 83) | |
PASTEL_GRAY | (207, 207, 196) | |
PASTEL_GREEN | (119, 221, 119) | |
PASTEL_MAGENTA | (244, 154, 194) | |
PASTEL_ORANGE | (255, 179, 71) | |
PASTEL_PINK | (222, 165, 164) | |
PASTEL_PURPLE | (179, 158, 181) | |
PASTEL_RED | (255, 105, 97) | |
PASTEL_VIOLET | (203, 153, 201) | |
PASTEL_YELLOW | (253, 253, 150) | |
PATRIARCH | (128, 0, 128) | |
PAYNE_GREY | (83, 104, 120) | |
PEACH | (255, 229, 180) | |
PEACH_ORANGE | (255, 204, 153) | |
PEACH_PUFF | (255, 218, 185) | |
PEACH_YELLOW | (250, 223, 173) | |
PEAR | (209, 226, 49) | |
PEARL | (234, 224, 200) | |
PEARL_AQUA | (136, 216, 192) | |
PEARLY_PURPLE | (183, 104, 162) | |
PERIDOT | (230, 226, 0) | |
PERIWINKLE | (204, 204, 255) | |
PERSIAN_BLUE | (28, 57, 187) | |
PERSIAN_GREEN | (0, 166, 147) | |
PERSIAN_INDIGO | (50, 18, 122) | |
PERSIAN_ORANGE | (217, 144, 88) | |
PERSIAN_PINK | (247, 127, 190) | |
PERSIAN_PLUM | (112, 28, 28) | |
PERSIAN_RED | (204, 51, 51) | |
PERSIAN_ROSE | (254, 40, 162) | |
PERSIMMON | (236, 88, 0) | |
PERU | (205, 133, 63) | |
PHLOX | (223, 0, 255) | |
PHTHALO_BLUE | (0, 15, 137) | |
PHTHALO_GREEN | (18, 53, 36) | |
PICTON_BLUE | (69, 177, 232) | |
PICTORIAL_CARMINE | (195, 11, 78) | |
PIGGY_PINK | (253, 221, 230) | |
PINE_GREEN | (1, 121, 111) | |
PINK | (255, 192, 203) | |
PINK_LACE | (255, 221, 244) | |
PINK_LAVENDER | (216, 178, 209) | |
PINK_PEARL | (231, 172, 207) | |
PINK_SHERBET | (247, 143, 167) | |
PISTACHIO | (147, 197, 114) | |
PLATINUM | (229, 228, 226) | |
PLUM | (221, 160, 221) | |
POMP_AND_POWER | (134, 96, 142) | |
POPSTAR | (190, 79, 98) | |
PORTLAND_ORANGE | (255, 90, 54) | |
POWDER_BLUE | (176, 224, 230) | |
PRINCETON_ORANGE | (255, 143, 0) | |
PRUNE | (112, 28, 28) | |
PRUSSIAN_BLUE | (0, 49, 83) | |
PSYCHEDELIC_PURPLE | (223, 0, 255) | |
PUCE | (204, 136, 153) | |
PUCE_RED | (114, 47, 55) | |
PULLMAN_BROWN | (100, 65, 23) | |
PUMPKIN | (255, 117, 24) | |
PURPLE | (128, 0, 128) | |
PURPLE_HEART | (105, 53, 156) | |
PURPLE_MOUNTAIN_MAJESTY | (150, 120, 182) | |
PURPLE_NAVY | (78, 81, 128) | |
PURPLE_PIZZAZZ | (254, 78, 218) | |
PURPLE_TAUPE | (80, 64, 77) | |
PURPUREUS | (154, 78, 174) | |
QUARTZ | (81, 72, 79) | |
QUEEN_BLUE | (67, 107, 149) | |
QUEEN_PINK | (232, 204, 215) | |
QUINACRIDONE_MAGENTA | (142, 58, 89) | |
RACKLEY | (93, 138, 168) | |
RADICAL_RED | (255, 53, 94) | |
RAJAH | (251, 171, 96) | |
RASPBERRY | (227, 11, 93) | |
RASPBERRY_GLACE | (145, 95, 109) | |
RASPBERRY_PINK | (226, 80, 152) | |
RASPBERRY_ROSE | (179, 68, 108) | |
RAW_UMBER | (130, 102, 68) | |
RAZZLE_DAZZLE_ROSE | (255, 51, 204) | |
RAZZMATAZZ | (227, 37, 107) | |
RAZZMIC_BERRY | (141, 78, 133) | |
RED | (255, 0, 0) | |
RED_BROWN | (165, 42, 42) | |
RED_DEVIL | (134, 1, 17) | |
RED_ORANGE | (255, 83, 73) | |
RED_PURPLE | (228, 0, 120) | |
RED_VIOLET | (199, 21, 133) | |
REDWOOD | (164, 90, 82) | |
REGALIA | (82, 45, 128) | |
RESOLUTION_BLUE | (0, 35, 135) | |
RHYTHM | (119, 118, 150) | |
RICH_BLACK | (0, 64, 64) | |
RICH_BRILLIANT_LAVENDER | (241, 167, 254) | |
RICH_CARMINE | (215, 0, 64) | |
RICH_ELECTRIC_BLUE | (8, 146, 208) | |
RICH_LAVENDER | (167, 107, 207) | |
RICH_LILAC | (182, 102, 210) | |
RICH_MAROON | (176, 48, 96) | |
RIFLE_GREEN | (68, 76, 56) | |
ROAST_COFFEE | (112, 66, 65) | |
ROBIN_EGG_BLUE | (0, 204, 204) | |
ROCKET_METALLIC | (138, 127, 128) | |
ROMAN_SILVER | (131, 137, 150) | |
ROSE | (255, 0, 127) | |
ROSE_BONBON | (249, 66, 158) | |
ROSE_EBONY | (103, 72, 70) | |
ROSE_GOLD | (183, 110, 121) | |
ROSE_MADDER | (227, 38, 54) | |
ROSE_PINK | (255, 102, 204) | |
ROSE_QUARTZ | (170, 152, 169) | |
ROSE_RED | (194, 30, 86) | |
ROSE_TAUPE | (144, 93, 93) | |
ROSE_VALE | (171, 78, 82) | |
ROSEWOOD | (101, 0, 11) | |
ROSSO_CORSA | (212, 0, 0) | |
ROSY_BROWN | (188, 143, 143) | |
ROYAL_AZURE | (0, 56, 168) | |
ROYAL_BLUE | (65, 105, 225) | |
ROYAL_FUCHSIA | (202, 44, 146) | |
ROYAL_PURPLE | (120, 81, 169) | |
ROYAL_YELLOW | (250, 218, 94) | |
RUBER | (206, 70, 118) | |
RUBINE_RED | (209, 0, 86) | |
RUBY | (224, 17, 95) | |
RUBY_RED | (155, 17, 30) | |
RUDDY | (255, 0, 40) | |
RUDDY_BROWN | (187, 101, 40) | |
RUDDY_PINK | (225, 142, 150) | |
RUFOUS | (168, 28, 7) | |
RUSSET | (128, 70, 27) | |
RUSSIAN_GREEN | (103, 146, 103) | |
RUSSIAN_VIOLET | (50, 23, 77) | |
RUST | (183, 65, 14) | |
RUSTY_RED | (218, 44, 67) | |
SACRAMENTO_STATE_GREEN | (0, 86, 63) | |
SADDLE_BROWN | (139, 69, 19) | |
SAFETY_ORANGE | (255, 103, 0) | |
SAFETY_YELLOW | (238, 210, 2) | |
SAFFRON | (244, 196, 48) | |
SAGE | (188, 184, 138) | |
ST_PATRICK_BLUE | (35, 41, 122) | |
SALMON | (250, 128, 114) | |
SALMON_PINK | (255, 145, 164) | |
SAND | (194, 178, 128) | |
SAND_DUNE | (150, 113, 23) | |
SANDSTORM | (236, 213, 64) | |
SANDY_BROWN | (244, 164, 96) | |
SANDY_TAUPE | (150, 113, 23) | |
SANGRIA | (146, 0, 10) | |
SAP_GREEN | (80, 125, 42) | |
SAPPHIRE | (15, 82, 186) | |
SAPPHIRE_BLUE | (0, 103, 165) | |
SATIN_SHEEN_GOLD | (203, 161, 53) | |
SCARLET | (255, 36, 0) | |
SCHAUSS_PINK | (255, 145, 175) | |
SCHOOL_BUS_YELLOW | (255, 216, 0) | |
SCREAMIN_GREEN | (118, 255, 122) | |
SEA_BLUE | (0, 105, 148) | |
SEA_GREEN | (46, 255, 139) | |
SEAL_BROWN | (50, 20, 20) | |
SEASHELL | (255, 245, 238) | |
SELECTIVE_YELLOW | (255, 186, 0) | |
SEPIA | (112, 66, 20) | |
SHADOW | (138, 121, 93) | |
SHADOW_BLUE | (119, 139, 165) | |
SHAMPOO | (255, 207, 241) | |
SHAMROCK_GREEN | (0, 158, 96) | |
SHEEN_GREEN | (143, 212, 0) | |
SHIMMERING_BLUSH | (217, 134, 149) | |
SHOCKING_PINK | (252, 15, 192) | |
SIENNA | (136, 45, 23) | |
SILVER | (192, 192, 192) | |
SILVER_CHALICE | (172, 172, 172) | |
SILVER_LAKE_BLUE | (93, 137, 186) | |
SILVER_PINK | (196, 174, 173) | |
SILVER_SAND | (191, 193, 194) | |
SINOPIA | (203, 65, 11) | |
SKOBELOFF | (0, 116, 116) | |
SKY_BLUE | (135, 206, 235) | |
SKY_MAGENTA | (207, 113, 175) | |
SLATE_BLUE | (106, 90, 205) | |
SLATE_GRAY | (112, 128, 144) | |
SMALT | (0, 51, 153) | |
SMITTEN | (200, 65, 134) | |
SMOKE | (115, 130, 118) | |
SMOKEY_TOPAZ | (147, 61, 65) | |
SMOKY_BLACK | (16, 12, 8) | |
SNOW | (255, 250, 250) | |
SOAP | (206, 200, 239) | |
SONIC_SILVER | (117, 117, 117) | |
SPACE_CADET | (29, 41, 81) | |
SPANISH_BISTRE | (128, 117, 90) | |
SPANISH_CARMINE | (209, 0, 71) | |
SPANISH_CRIMSON | (229, 26, 76) | |
SPANISH_BLUE | (0, 112, 184) | |
SPANISH_GRAY | (152, 152, 152) | |
SPANISH_GREEN | (0, 145, 80) | |
SPANISH_ORANGE | (232, 97, 0) | |
SPANISH_PINK | (247, 191, 190) | |
SPANISH_RED | (230, 0, 38) | |
SPANISH_SKY_BLUE | (0, 170, 228) | |
SPANISH_VIOLET | (76, 40, 130) | |
SPANISH_VIRIDIAN | (0, 127, 92) | |
SPIRO_DISCO_BALL | (15, 192, 252) | |
SPRING_BUD | (167, 252, 0) | |
SPRING_GREEN | (0, 255, 127) | |
STAR_COMMAND_BLUE | (0, 123, 184) | |
STEEL_BLUE | (70, 130, 180) | |
STEEL_PINK | (204, 51, 102) | |
STIL_DE_GRAIN_YELLOW | (250, 218, 94) | |
STIZZA | (153, 0, 0) | |
STORMCLOUD | (79, 102, 106) | |
STRAW | (228, 217, 111) | |
STRAWBERRY | (252, 90, 141) | |
SUNGLOW | (255, 204, 51) | |
SUNRAY | (227, 171, 87) | |
SUNSET | (250, 214, 165) | |
SUNSET_ORANGE | (253, 94, 83) | |
SUPER_PINK | (207, 107, 169) | |
TAN | (210, 180, 140) | |
TANGELO | (249, 77, 0) | |
TANGERINE | (242, 133, 0) | |
TANGERINE_YELLOW | (255, 204, 0) | |
TANGO_PINK | (228, 113, 122) | |
TAUPE | (72, 60, 50) | |
TAUPE_GRAY | (139, 133, 137) | |
TEA_GREEN | (208, 240, 192) | |
TEA_ROSE | (244, 194, 194) | |
TEAL | (0, 128, 128) | |
TEAL_BLUE | (54, 117, 136) | |
TEAL_DEER | (153, 230, 179) | |
TEAL_GREEN | (0, 130, 127) | |
TELEMAGENTA | (207, 52, 118) | |
TERRA_COTTA | (226, 114, 91) | |
THISTLE | (216, 191, 216) | |
THULIAN_PINK | (222, 111, 161) | |
TICKLE_ME_PINK | (252, 137, 172) | |
TIFFANY_BLUE | (10, 186, 181) | |
TIGERS_EYE | (224, 141, 60) | |
TIMBERWOLF | (219, 215, 210) | |
TITANIUM_YELLOW | (238, 230, 0) | |
TOMATO | (255, 99, 71) | |
TOOLBOX | (116, 108, 192) | |
TOPAZ | (255, 200, 124) | |
TRACTOR_RED | (253, 14, 53) | |
TROLLEY_GREY | (128, 128, 128) | |
TROPICAL_RAIN_FOREST | (0, 117, 94) | |
TRUE_BLUE | (0, 115, 207) | |
TUFTS_BLUE | (65, 125, 193) | |
TULIP | (255, 135, 141) | |
TUMBLEWEED | (222, 170, 136) | |
TURKISH_ROSE | (181, 114, 129) | |
TURQUOISE | (64, 224, 208) | |
TURQUOISE_BLUE | (0, 255, 239) | |
TURQUOISE_GREEN | (160, 214, 180) | |
TUSCAN | (250, 214, 165) | |
TUSCAN_BROWN | (111, 78, 55) | |
TUSCAN_RED | (124, 72, 72) | |
TUSCAN_TAN | (166, 123, 91) | |
TUSCANY | (192, 153, 153) | |
TWILIGHT_LAVENDER | (138, 73, 107) | |
TYRIAN_PURPLE | (102, 2, 60) | |
UA_BLUE | (0, 51, 170) | |
UA_RED | (217, 0, 76) | |
UBE | (136, 120, 195) | |
UCLA_BLUE | (83, 104, 149) | |
UCLA_GOLD | (255, 179, 0) | |
UFO_GREEN | (60, 208, 112) | |
ULTRAMARINE | (18, 10, 143) | |
ULTRAMARINE_BLUE | (65, 102, 245) | |
ULTRA_PINK | (255, 111, 255) | |
UMBER | (99, 81, 71) | |
UNBLEACHED_SILK | (255, 221, 202) | |
UNITED_NATIONS_BLUE | (91, 146, 229) | |
UNIVERSITY_OF_CALIFORNIA_GOLD | (183, 135, 39) | |
UNMELLOW_YELLOW | (255, 255, 102) | |
UP_FOREST_GREEN | (1, 68, 33) | |
UP_MAROON | (123, 17, 19) | |
UPSDELL_RED | (174, 32, 41) | |
UROBILIN | (225, 173, 33) | |
USAFA_BLUE | (0, 79, 152) | |
USC_CARDINAL | (153, 0, 0) | |
USC_GOLD | (255, 204, 0) | |
UNIVERSITY_OF_TENNESSEE_ORANGE | (247, 127, 0) | |
UTAH_CRIMSON | (211, 0, 63) | |
VANILLA | (243, 229, 171) | |
VANILLA_ICE | (243, 143, 169) | |
VEGAS_GOLD | (197, 179, 88) | |
VENETIAN_RED | (200, 8, 21) | |
VERDIGRIS | (67, 179, 174) | |
VERMILION | (227, 66, 52) | |
VERONICA | (160, 32, 240) | |
VIOLET | (143, 0, 255) | |
VIOLET_BLUE | (50, 74, 178) | |
VIOLET_RED | (247, 83, 148) | |
VIRIDIAN | (64, 130, 109) | |
VIRIDIAN_GREEN | (0, 150, 152) | |
VIVID_AUBURN | (146, 39, 36) | |
VIVID_BURGUNDY | (159, 29, 53) | |
VIVID_CERISE | (218, 29, 129) | |
VIVID_ORCHID | (204, 0, 255) | |
VIVID_SKY_BLUE | (0, 204, 255) | |
VIVID_TANGERINE | (255, 160, 137) | |
VIVID_VIOLET | (159, 0, 255) | |
WARM_BLACK | (0, 66, 66) | |
WATERSPOUT | (164, 244, 249) | |
WENGE | (100, 84, 82) | |
WHEAT | (245, 222, 179) | |
WHITE | (255, 255, 255) | |
WHITE_SMOKE | (245, 245, 245) | |
WILD_BLUE_YONDER | (162, 173, 208) | |
WILD_ORCHID | (212, 112, 162) | |
WILD_STRAWBERRY | (255, 67, 164) | |
WILD_WATERMELON | (252, 108, 133) | |
WILLPOWER_ORANGE | (253, 88, 0) | |
WINDSOR_TAN | (167, 85, 2) | |
WINE | (114, 47, 55) | |
WINE_DREGS | (103, 49, 71) | |
WISTERIA | (201, 160, 220) | |
WOOD_BROWN | (193, 154, 107) | |
XANADU | (115, 134, 120) | |
YALE_BLUE | (15, 77, 146) | |
YANKEES_BLUE | (28, 40, 65) | |
YELLOW | (255, 255, 0) | |
YELLOW_GREEN | (154, 205, 50) | |
YELLOW_ORANGE | (255, 174, 66) | |
YELLOW_ROSE | (255, 240, 0) | |
ZAFFRE | (0, 20, 168) | |
ZINNWALDITE_BROWN | (44, 22, 8) |
arcade.gl package¶
Buffer¶
BufferDescription¶
Context¶
Framebuffer¶
Geometry¶
Program¶
Query¶
Texture¶
VertexArray¶
ShaderException¶
geometry¶
arcade.gui package¶
Resources¶
Resource files are images and sounds built into Arcade that can be used to quickly build and test simple code without having to worry about copying files into the project.
Any file loaded that starts with :resources:
will attempt to load that file from the library resources instead of the project directory.
Many of the resources come from Kenney.nl and are licensed under CC0 (Creative Commons Zero). Be sure to check out his web page for a much wider selection of assets.
:resources:images/¶
:resources:images/isometric_dungeon/¶
:resources:images/topdown_tanks/¶
:resources:images/animated_characters/male_person/¶
:resources:images/animated_characters/female_person/¶
:resources:images/animated_characters/zombie/¶
:resources:images/animated_characters/female_adventurer/¶
:resources:images/animated_characters/male_adventurer/¶
:resources:images/animated_characters/robot/¶
:resources:images/cards/¶
:resources:images/backgrounds/¶
![]() abstract_2.jpg |
![]() instructions_0.png |
![]() instructions_1.png |
![]() abstract_1.jpg |
![]() stars.png |
:resources:images/pinball/¶
![]() pool_cue_ball.png |
![]() bumper.png |
:resources:images/space_shooter/¶
:resources:images/enemies/¶
:resources:images/items/¶
:resources:images/alien/¶
![]() alienBlue_jump.png |
![]() alienBlue_climb1.png |
![]() alienBlue_walk2.png |
![]() alienBlue_climb2.png |
![]() alienBlue_walk1.png |
![]() alienBlue_front.png |
:resources:images/test_textures/¶
![]() xy_square.png |
![]() test_texture.png |
:resources:images/tiles/¶
:resources:images/spritesheets/¶
![]() number_sheet.png |
![]() tiles.png |
![]() codepage_437.png |
![]() explosion.png |
:resources:music/¶
1918.mp3 |
funkyrobot.mp3 |
:resources:style/¶
default.yml |
:resources:tmx_maps/¶
level_2.tmx | level_1.tmx | test_objects.tmx |
items.tsx | map_with_custom_hitboxes.tmx | more_tiles.tsx |
standard_tileset.tsx | map2_level_2.tmx | dirt.tsx |
map_with_external_tileset.tmx | test_map_6.tmx | spritesheet.tsx |
test_map_3.tmx | map_with_ladders.tmx | isometric_dungeon.tmx |
test_map_1.tmx | map2_level_1.tmx | test_map_5.tmx |
map.tmx | grass.tsx | test_map_4.tmx |
test_map_2.tmx | test_map_7.tmx |
:resources:shaders/¶
shape_element_list_fs.glsl | texture_default_projection_vs.glsl | shape_element_list_vs.glsl |
texture_fs.glsl | __init__.py |
:resources:shaders/shapes/¶
README.md |
:resources:shaders/shapes/line/¶
unbuffered_fs.glsl | line_generic_with_colors_vs.glsl | line_vertex_shader_fs.glsl |
unbuffered_geo.glsl | buffered_geo.glsl | unbuffered_vs.glsl |
line_generic_with_colors_fs.glsl | line_vertex_shader_vs.glsl | buffered_fs.glsl |
buffered_vs.glsl |
:resources:shaders/shapes/rectangle/¶
filled_unbuffered_vs.glsl | filled_unbuffered_fs.glsl | filled_unbuffered_geo.glsl |
:resources:shaders/shapes/ellipse/¶
outline_unbuffered_fs.glsl | filled_unbuffered_vs.glsl | filled_unbuffered_fs.glsl |
filled_unbuffered_geo.glsl | outline_unbuffered_geo.glsl | outline_unbuffered_vs.glsl |
:resources:shaders/lights/¶
combine_vs.glsl | point_lights_vs.glsl | point_lights_fs.glsl |
combine_fs.glsl | point_lights_geo.glsl |
:resources:shaders/postprocessing/¶
gaussian_blur_y_fs.glsl | gaussian_combine_fs.glsl | glow_filter_vs.glsl |
glow_filter_fs.glsl | gaussian_blur_x_fs.glsl |
:resources:shaders/sprites/¶
sprite_list_instanced_fs.glsl | sprite_list_geometry_cull_geo.glsl | sprite_list_instanced_vs.glsl |
sprite_list_geometry_no_cull_geo.glsl | sprite_list_geometry_fs.glsl | sprite_list_geometry_vs.glsl |
:resources:sounds/¶
explosion2.wav |
hurt5.wav |
error4.wav |
fall2.wav |
hurt2.wav |
laser4.wav |
gameover3.wav |
upgrade4.wav |
coin5.wav |
jump2.wav |
rockHit2.ogg |
laser5.wav |
error1.wav |
jump3.wav |
laser1.wav |
hurt4.wav |
error2.wav |
upgrade1.wav |
secret4.wav |
gameover4.wav |
upgrade5.wav |
jump4.wav |
hurt3.wav |
lose4.wav |
hit3.wav |
lose3.wav |
laser1.mp3 |
hit4.wav |
gameover5.wav |
fall1.wav |
phaseJump1.wav |
fall3.wav |
hurt1.wav |
fall4.wav |
error5.wav |
rockHit2.wav |
gameover1.wav |
hit1.wav |
error3.wav |
coin4.wav |
jump5.wav |
jump1.wav |
lose5.wav |
laser3.wav |
coin1.wav |
coin3.wav |
lose1.wav |
upgrade3.wav |
hit5.wav |
upgrade2.wav |
gameover2.wav |
coin2.wav |
explosion1.wav |
hit2.wav |
phaseJump1.ogg |
secret2.wav |
laser1.ogg |
lose2.wav |
laser2.wav |
:resources:gui_basic_assets/¶
![]() red_button_normal.png |
![]() red_button_press.png |
![]() red_button_hover.png |
:resources:gui_basic_assets/icons/¶
![]() larger.png |
![]() smaller.png |
Main Arcade Module Package API¶
Working with the Keyboard¶
Modifiers¶
The modifiers that are held down when the event is generated are combined in a bitwise fashion and provided in the modifiers parameter. The modifier constants defined in arcade.key are:
MOD_SHIFT
MOD_CTRL
MOD_ALT Not available on Mac OS X
MOD_WINDOWS Available on Windows only
MOD_COMMAND Available on Mac OS X only
MOD_OPTION Available on Mac OS X only
MOD_CAPSLOCK
MOD_NUMLOCK
MOD_SCROLLLOCK
MOD_ACCEL Equivalent to MOD_CTRL, or MOD_COMMAND on Mac OS X.
For example, to test if the shift key is held down:
if modifiers & MOD_SHIFT:
pass
Unlike the corresponding key symbols, it is not possible to determine whether the left or right modifier is held down (though you could emulate this behavior by keeping track of the key states yourself).
Simple Platformer¶

Use Python and the Arcade library to create a 2D platformer game. Learn to work with Sprites and the Tiled Map Editor to create your own games. Add coins, ramps, moving platforms, enemies, and more.
The tutorial is divided into these parts:
Step 1 - Install and Open a Window¶
Our first step is to make sure everything is installed, and that we can at least get a window open.
Installation¶
Make sure Python is installed. Download Python here if you don’t already have it.
Download this bundle with code, images, and sounds. (Images are from kenney.nl.) Your file structure should look like:

Make sure the Arcade library is installed.
Install Arcade with
pip install arcade
on Windows orpip3 install arcade
on Mac/Linux. Or install by using a venv.Here are the longer, official Installation Instructions.
I highly recommend using the free community edition of PyCharm as an editor. If you do, see Install Arcade with PyCharm and a Virtual Environment.
Open a Window¶
The example below opens up a blank window. Set up a project and get the code
below working. (It is also in the zip file as
01_open_window.py
.)
Note
This is a fixed-size window. It is possible to have a Resizable Window or a Full Screen Example, but there are more interesting things we can do first. Therefore we’ll stick with a fixed-size window for this tutorial.
1"""
2Platformer Game
3"""
4import arcade
5
6# Constants
7SCREEN_WIDTH = 1000
8SCREEN_HEIGHT = 650
9SCREEN_TITLE = "Platformer"
10
11
12class MyGame(arcade.Window):
13 """
14 Main application class.
15 """
16
17 def __init__(self):
18
19 # Call the parent class and set up the window
20 super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
21
22 arcade.set_background_color(arcade.csscolor.CORNFLOWER_BLUE)
23
24 def setup(self):
25 """ Set up the game here. Call this function to restart the game. """
26 pass
27
28 def on_draw(self):
29 """ Render the screen. """
30
31 arcade.start_render()
32 # Code to draw the screen goes here
33
34
35def main():
36 """ Main method """
37 window = MyGame()
38 window.setup()
39 arcade.run()
40
41
42if __name__ == "__main__":
43 main()
Once you get the code working, figure out how to:
Change the screen size
Change the title
Change the background color
See the documentation for arcade.color package
See the documentation for arcade.csscolor package
Look through the documentation for the Window class to get an idea of everything it can do.
Step 2 - Add Sprites¶
Our next step is to add some sprites, which are graphics we can see and interact with on the screen.

Setup vs. Init¶
In the next code example, 02_draw_sprites
,
we’ll have both an __init__
method and a
setup
.
The __init__
creates the variables. The variables are set to values such as
0 or None
. The setup
actually creates the object instances, such as
graphical sprites.
I often get the very reasonable question, “Why have two methods? Why not just
put everything into __init__
? Seems like we are doing twice the work.”
Here’s why.
With a setup
method split out, later on we can easily add
“restart/play again” functionality to the game.
A simple call to setup
will reset everything.
Later, we can expand our game with different levels, and have functions such as
setup_level_1
and setup_level_2
.
Sprite Lists¶
Sprites are managed in lists. The SpriteList
class optimizes drawing, movement,
and collision detection.
We are using three logical groups in our game. A player_list
for the player.
A wall_list
for walls we can’t move through. And finally a
coin_list
for coins we can pick up.
self.player_list = arcade.SpriteList()
self.wall_list = arcade.SpriteList(use_spatial_hash=True)
self.coin_list = arcade.SpriteList(use_spatial_hash=True)
Sprite lists have an option to use something called “spatial hashing.” Spatial hashing speeds the time it takes to find collisions, but increases the time it takes to move a sprite. Since I don’t expect most of my walls or coins to move, I’ll turn on spatial hashing for these lists. My player moves around a lot, so I’ll leave it off for her.
Add Sprites to the Game¶
To create sprites we’ll use the arcade.Sprite
class.
We can create an instance of the sprite class with code like this:
self.player_sprite = arcade.Sprite("images/player_1/player_stand.png", CHARACTER_SCALING)
The first parameter is a string or path to the image you want it to load.
An optional second parameter will scale the sprite up or down.
If the second parameter (in this case a constant CHARACTER_SCALING
) is set to
0.5, and the the sprite is 128x128, then both width and height will be scaled
down 50% for a 64x64 sprite.
Next, we need to tell where the sprite goes. You can use the attributes
center_x
and center_y
to position the sprite. You can also use top
,
bottom
, left
, and right
to get or set the sprites location by an
edge instead of the center. You can also use position
attribute to set both the
x and y at the same time.
self.player_sprite.center_x = 64
self.player_sprite.center_y = 120
Finally, all instances of the Sprite
class need to go in a SpriteList
class.
self.player_list.append(self.player_sprite)
We manage groups of sprites by the list that they are in.
In the example below there’s a wall_list
that will hold everything that the
player character can’t walk through, and
a coin_list
for sprites we can pick up to get points. There’s also a player_list
which holds only the player.
Documentation for the Sprite class
Documentation for the SpriteList class
Notice that the code creates Sprites
three ways:
Creating a
Sprite
class, positioning it, adding it to the listCreate a series of sprites in a loop
Create a series of sprites using coordinates
1"""
2Platformer Game
3"""
4import arcade
5
6# Constants
7SCREEN_WIDTH = 1000
8SCREEN_HEIGHT = 650
9SCREEN_TITLE = "Platformer"
10
11# Constants used to scale our sprites from their original size
12CHARACTER_SCALING = 1
13TILE_SCALING = 0.5
14COIN_SCALING = 0.5
15
16
17class MyGame(arcade.Window):
18 """
19 Main application class.
20 """
21
22 def __init__(self):
23
24 # Call the parent class and set up the window
25 super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
26
27 # These are 'lists' that keep track of our sprites. Each sprite should
28 # go into a list.
29 self.coin_list = None
30 self.wall_list = None
31 self.player_list = None
32
33 # Separate variable that holds the player sprite
34 self.player_sprite = None
35
36 arcade.set_background_color(arcade.csscolor.CORNFLOWER_BLUE)
37
38 def setup(self):
39 """ Set up the game here. Call this function to restart the game. """
40 # Create the Sprite lists
41 self.player_list = arcade.SpriteList()
42 self.wall_list = arcade.SpriteList(use_spatial_hash=True)
43 self.coin_list = arcade.SpriteList(use_spatial_hash=True)
44
45 # Set up the player, specifically placing it at these coordinates.
46 image_source = ":resources:images/animated_characters/female_adventurer/femaleAdventurer_idle.png"
47 self.player_sprite = arcade.Sprite(image_source, CHARACTER_SCALING)
48 self.player_sprite.center_x = 64
49 self.player_sprite.center_y = 128
50 self.player_list.append(self.player_sprite)
51
52 # Create the ground
53 # This shows using a loop to place multiple sprites horizontally
54 for x in range(0, 1250, 64):
55 wall = arcade.Sprite(":resources:images/tiles/grassMid.png", TILE_SCALING)
56 wall.center_x = x
57 wall.center_y = 32
58 self.wall_list.append(wall)
59
60 # Put some crates on the ground
61 # This shows using a coordinate list to place sprites
62 coordinate_list = [[512, 96],
63 [256, 96],
64 [768, 96]]
65
66 for coordinate in coordinate_list:
67 # Add a crate on the ground
68 wall = arcade.Sprite(":resources:images/tiles/boxCrate_double.png", TILE_SCALING)
69 wall.position = coordinate
70 self.wall_list.append(wall)
71
72 def on_draw(self):
73 """ Render the screen. """
74
75 # Clear the screen to the background color
76 arcade.start_render()
77
78 # Draw our sprites
79 self.wall_list.draw()
80 self.coin_list.draw()
81 self.player_list.draw()
82
83
84def main():
85 """ Main method """
86 window = MyGame()
87 window.setup()
88 arcade.run()
89
90
91if __name__ == "__main__":
92 main()
Note
Once the code example is up and working:
Adjust the code and try putting sprites in new positions.
Use different images for sprites (see the images folder).
Practice placing individually, via a loop, and by coordinates in a list.
Step 3 - Add User Control¶
Now we need to be able to get the user to move around.
First, at the top of the program add a constant that controls how many pixels per update our character travels:
# Movement speed of player, in pixels per frame
PLAYER_MOVEMENT_SPEED = 5
Next, at the end of our setup
method, we are need to create a physics engine that will
move our player and keep her from running through walls. The PhysicsEngineSimple
class takes two parameters: The moving
sprite, and a list of sprites the moving sprite can’t move through.
For more information about the physics engine we are using here, see PhysicsEngineSimple class
Note
It is possible to have multiple physics engines, one per moving sprite. These are very simple, but easy physics engines. See Pymunk Platformer for a more advanced physics engine.
# Movement speed of player, in pixels per frame
PLAYER_MOVEMENT_SPEED = 5
Each sprite has center_x
and center_y
attributes. Changing these will
change the location of the sprite. (There are also attributes for top, bottom,
left, right, and angle that will move the sprite.)
Each sprite has change_x
and change_y
variables. These can be used to
hold the velocity that the sprite is moving with. We will adjust these
based on what key the user hits. If the user hits the right arrow key
we want a positive value for change_x
. If the value is 5, it will move
5 pixels per frame.
In this case, when the user presses a key we’ll change the sprites change x and y. The physics engine will look at that, and move the player unless she’ll hit a wall.
1 def on_key_press(self, key, modifiers):
2 """Called whenever a key is pressed. """
3
4 if key == arcade.key.UP or key == arcade.key.W:
5 self.player_sprite.change_y = PLAYER_MOVEMENT_SPEED
6 elif key == arcade.key.DOWN or key == arcade.key.S:
7 self.player_sprite.change_y = -PLAYER_MOVEMENT_SPEED
8 elif key == arcade.key.LEFT or key == arcade.key.A:
9 self.player_sprite.change_x = -PLAYER_MOVEMENT_SPEED
10 elif key == arcade.key.RIGHT or key == arcade.key.D:
11 self.player_sprite.change_x = PLAYER_MOVEMENT_SPEED
On releasing the key, we’ll put our speed back to zero.
1 def on_key_release(self, key, modifiers):
2 """Called when the user releases a key. """
3
4 if key == arcade.key.UP or key == arcade.key.W:
5 self.player_sprite.change_y = 0
6 elif key == arcade.key.DOWN or key == arcade.key.S:
7 self.player_sprite.change_y = 0
8 elif key == arcade.key.LEFT or key == arcade.key.A:
9 self.player_sprite.change_x = 0
10 elif key == arcade.key.RIGHT or key == arcade.key.D:
11 self.player_sprite.change_x = 0
Note
This method of tracking the speed to the key the player presses is simple, but isn’t perfect. If the player hits both left and right keys at the same time, then lets off the left one, we expect the player to move right. This method won’t support that. If you want a slightly more complex method that does, see Better Move By Keyboard.
Our on_update
method is called about 60 times per second. We’ll ask the physics
engine to move our player based on her change_x
and change_y
.
1 def on_update(self, delta_time):
2 """ Movement and game logic """
3
4 # Move the player with the physics engine
5 self.physics_engine.update()
Step 4 - Add Gravity¶
The example above works great for top-down, but what if it is a side view with jumping? We need to add gravity. First, let’s define a constant to represent the acceleration for gravity, and one for a jump speed.
GRAVITY = 1
PLAYER_JUMP_SPEED = 20
At the end of the setup
method, change the physics engine to
PhysicsEnginePlatformer
and include gravity as a parameter.
# Create the 'physics engine'
self.physics_engine = arcade.PhysicsEnginePlatformer(self.player_sprite,
self.wall_list,
GRAVITY)
Then, modify the key down and key up event handlers. We’ll remove the up/down statements we had before, and make ‘UP’ jump when pressed.
1 def on_key_press(self, key, modifiers):
2 """Called whenever a key is pressed. """
3
4 if key == arcade.key.UP or key == arcade.key.W:
5 if self.physics_engine.can_jump():
6 self.player_sprite.change_y = PLAYER_JUMP_SPEED
7 elif key == arcade.key.LEFT or key == arcade.key.A:
8 self.player_sprite.change_x = -PLAYER_MOVEMENT_SPEED
9 elif key == arcade.key.RIGHT or key == arcade.key.D:
10 self.player_sprite.change_x = PLAYER_MOVEMENT_SPEED
11
12 def on_key_release(self, key, modifiers):
13 """Called when the user releases a key. """
14
15 if key == arcade.key.LEFT or key == arcade.key.A:
16 self.player_sprite.change_x = 0
17 elif key == arcade.key.RIGHT or key == arcade.key.D:
18 self.player_sprite.change_x = 0
Note
You can change how the user jumps by changing the gravity and jump constants. Lower values for both will make for a more “floaty” character. Higher values make for a faster-paced game.
Step 5 - Add Scrolling¶
We can have our window be a small viewport into a much larger world by adding scrolling.
The viewport margins control how close you can get to the edge of the screen before the camera starts scrolling.
1"""
2Platformer Game
3"""
4import arcade
5
6# Constants
7SCREEN_WIDTH = 1000
8SCREEN_HEIGHT = 650
9SCREEN_TITLE = "Platformer"
10
11# Constants used to scale our sprites from their original size
12CHARACTER_SCALING = 1
13TILE_SCALING = 0.5
14COIN_SCALING = 0.5
15
16# Movement speed of player, in pixels per frame
17PLAYER_MOVEMENT_SPEED = 5
18GRAVITY = 1
19PLAYER_JUMP_SPEED = 20
20
21# How many pixels to keep as a minimum margin between the character
22# and the edge of the screen.
23LEFT_VIEWPORT_MARGIN = 250
24RIGHT_VIEWPORT_MARGIN = 250
25BOTTOM_VIEWPORT_MARGIN = 50
26TOP_VIEWPORT_MARGIN = 100
27
28
29class MyGame(arcade.Window):
30 """
31 Main application class.
32 """
33
34 def __init__(self):
35
36 # Call the parent class and set up the window
37 super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
38
39 # These are 'lists' that keep track of our sprites. Each sprite should
40 # go into a list.
41 self.coin_list = None
42 self.wall_list = None
43 self.player_list = None
44
45 # Separate variable that holds the player sprite
46 self.player_sprite = None
47
48 # Our physics engine
49 self.physics_engine = None
50
51 # Used to keep track of our scrolling
52 self.view_bottom = 0
53 self.view_left = 0
54
55 arcade.set_background_color(arcade.csscolor.CORNFLOWER_BLUE)
56
57 def setup(self):
58 """ Set up the game here. Call this function to restart the game. """
59
60 # Used to keep track of our scrolling
61 self.view_bottom = 0
62 self.view_left = 0
63
64 # Create the Sprite lists
65 self.player_list = arcade.SpriteList()
66 self.wall_list = arcade.SpriteList()
67 self.coin_list = arcade.SpriteList()
68
69 # Set up the player, specifically placing it at these coordinates.
70 image_source = ":resources:images/animated_characters/female_adventurer/femaleAdventurer_idle.png"
71 self.player_sprite = arcade.Sprite(image_source, CHARACTER_SCALING)
72 self.player_sprite.center_x = 64
73 self.player_sprite.center_y = 96
74 self.player_list.append(self.player_sprite)
75
76 # Create the ground
77 # This shows using a loop to place multiple sprites horizontally
78 for x in range(0, 1250, 64):
79 wall = arcade.Sprite(":resources:images/tiles/grassMid.png", TILE_SCALING)
80 wall.center_x = x
81 wall.center_y = 32
82 self.wall_list.append(wall)
83
84 # Put some crates on the ground
85 # This shows using a coordinate list to place sprites
86 coordinate_list = [[512, 96],
87 [256, 96],
88 [768, 96]]
89
90 for coordinate in coordinate_list:
91 # Add a crate on the ground
92 wall = arcade.Sprite(":resources:images/tiles/boxCrate_double.png", TILE_SCALING)
93 wall.position = coordinate
94 self.wall_list.append(wall)
95
96 # Create the 'physics engine'
97 self.physics_engine = arcade.PhysicsEnginePlatformer(self.player_sprite,
98 self.wall_list,
99 GRAVITY)
100
101 def on_draw(self):
102 """ Render the screen. """
103
104 # Clear the screen to the background color
105 arcade.start_render()
106
107 # Draw our sprites
108 self.wall_list.draw()
109 self.coin_list.draw()
110 self.player_list.draw()
111
112 def on_key_press(self, key, modifiers):
113 """Called whenever a key is pressed. """
114
115 if key == arcade.key.UP or key == arcade.key.W:
116 if self.physics_engine.can_jump():
117 self.player_sprite.change_y = PLAYER_JUMP_SPEED
118 elif key == arcade.key.LEFT or key == arcade.key.A:
119 self.player_sprite.change_x = -PLAYER_MOVEMENT_SPEED
120 elif key == arcade.key.RIGHT or key == arcade.key.D:
121 self.player_sprite.change_x = PLAYER_MOVEMENT_SPEED
122
123 def on_key_release(self, key, modifiers):
124 """Called when the user releases a key. """
125
126 if key == arcade.key.LEFT or key == arcade.key.A:
127 self.player_sprite.change_x = 0
128 elif key == arcade.key.RIGHT or key == arcade.key.D:
129 self.player_sprite.change_x = 0
130
131 def on_update(self, delta_time):
132 """ Movement and game logic """
133
134 # Move the player with the physics engine
135 self.physics_engine.update()
136
137 # --- Manage Scrolling ---
138
139 # Track if we need to change the viewport
140
141 changed = False
142
143 # Scroll left
144 left_boundary = self.view_left + LEFT_VIEWPORT_MARGIN
145 if self.player_sprite.left < left_boundary:
146 self.view_left -= left_boundary - self.player_sprite.left
147 changed = True
148
149 # Scroll right
150 right_boundary = self.view_left + SCREEN_WIDTH - RIGHT_VIEWPORT_MARGIN
151 if self.player_sprite.right > right_boundary:
152 self.view_left += self.player_sprite.right - right_boundary
153 changed = True
154
155 # Scroll up
156 top_boundary = self.view_bottom + SCREEN_HEIGHT - TOP_VIEWPORT_MARGIN
157 if self.player_sprite.top > top_boundary:
158 self.view_bottom += self.player_sprite.top - top_boundary
159 changed = True
160
161 # Scroll down
162 bottom_boundary = self.view_bottom + BOTTOM_VIEWPORT_MARGIN
163 if self.player_sprite.bottom < bottom_boundary:
164 self.view_bottom -= bottom_boundary - self.player_sprite.bottom
165 changed = True
166
167 if changed:
168 # Only scroll to integers. Otherwise we end up with pixels that
169 # don't line up on the screen
170 self.view_bottom = int(self.view_bottom)
171 self.view_left = int(self.view_left)
172
173 # Do the scrolling
174 arcade.set_viewport(self.view_left,
175 SCREEN_WIDTH + self.view_left,
176 self.view_bottom,
177 SCREEN_HEIGHT + self.view_bottom)
178
179
180def main():
181 """ Main method """
182 window = MyGame()
183 window.setup()
184 arcade.run()
185
186
187if __name__ == "__main__":
188 main()
Note
Work at changing the viewport margins to something that you like.
Step 6 - Add Coins And Sound¶

The code below adds coins that we can collect. It also adds a sound to be played when the user hits a coin, or presses the jump button.
We check to see if the user hits a coin by the arcade.check_for_collision_with_list
function. Just pass the player sprite, along with a SpriteList
that holds
the coins. The function returns a list of coins in contact with the player sprite.
If no coins are in contact, the list is empty.
The method Sprite.remove_from_sprite_lists
will remove that sprite from all
lists, and effectively the game.
Notice that any transparent “white-space” around the image counts as the hitbox. You can trim the space in a graphics editor, or in the second section, we’ll show you how to specify the hitbox.
1"""
2Platformer Game
3"""
4import arcade
5
6# Constants
7SCREEN_WIDTH = 1000
8SCREEN_HEIGHT = 650
9SCREEN_TITLE = "Platformer"
10
11# Constants used to scale our sprites from their original size
12CHARACTER_SCALING = 1
13TILE_SCALING = 0.5
14COIN_SCALING = 0.5
15
16# Movement speed of player, in pixels per frame
17PLAYER_MOVEMENT_SPEED = 5
18GRAVITY = 1
19PLAYER_JUMP_SPEED = 20
20
21# How many pixels to keep as a minimum margin between the character
22# and the edge of the screen.
23LEFT_VIEWPORT_MARGIN = 250
24RIGHT_VIEWPORT_MARGIN = 250
25BOTTOM_VIEWPORT_MARGIN = 50
26TOP_VIEWPORT_MARGIN = 100
27
28
29class MyGame(arcade.Window):
30 """
31 Main application class.
32 """
33
34 def __init__(self):
35
36 # Call the parent class and set up the window
37 super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
38
39 # These are 'lists' that keep track of our sprites. Each sprite should
40 # go into a list.
41 self.coin_list = None
42 self.wall_list = None
43 self.player_list = None
44
45 # Separate variable that holds the player sprite
46 self.player_sprite = None
47
48 # Our physics engine
49 self.physics_engine = None
50
51 # Used to keep track of our scrolling
52 self.view_bottom = 0
53 self.view_left = 0
54
55 # Load sounds
56 self.collect_coin_sound = arcade.load_sound(":resources:sounds/coin1.wav")
57 self.jump_sound = arcade.load_sound(":resources:sounds/jump1.wav")
58
59 arcade.set_background_color(arcade.csscolor.CORNFLOWER_BLUE)
60
61 def setup(self):
62 """ Set up the game here. Call this function to restart the game. """
63
64 # Used to keep track of our scrolling
65 self.view_bottom = 0
66 self.view_left = 0
67
68 # Create the Sprite lists
69 self.player_list = arcade.SpriteList()
70 self.wall_list = arcade.SpriteList()
71 self.coin_list = arcade.SpriteList()
72
73 # Set up the player, specifically placing it at these coordinates.
74 image_source = ":resources:images/animated_characters/female_adventurer/femaleAdventurer_idle.png"
75 self.player_sprite = arcade.Sprite(image_source, CHARACTER_SCALING)
76 self.player_sprite.center_x = 64
77 self.player_sprite.center_y = 128
78 self.player_list.append(self.player_sprite)
79
80 # Create the ground
81 # This shows using a loop to place multiple sprites horizontally
82 for x in range(0, 1250, 64):
83 wall = arcade.Sprite(":resources:images/tiles/grassMid.png", TILE_SCALING)
84 wall.center_x = x
85 wall.center_y = 32
86 self.wall_list.append(wall)
87
88 # Put some crates on the ground
89 # This shows using a coordinate list to place sprites
90 coordinate_list = [[512, 96],
91 [256, 96],
92 [768, 96]]
93
94 for coordinate in coordinate_list:
95 # Add a crate on the ground
96 wall = arcade.Sprite(":resources:images/tiles/boxCrate_double.png", TILE_SCALING)
97 wall.position = coordinate
98 self.wall_list.append(wall)
99
100 # Use a loop to place some coins for our character to pick up
101 for x in range(128, 1250, 256):
102 coin = arcade.Sprite(":resources:images/items/coinGold.png", COIN_SCALING)
103 coin.center_x = x
104 coin.center_y = 96
105 self.coin_list.append(coin)
106
107 # Create the 'physics engine'
108 self.physics_engine = arcade.PhysicsEnginePlatformer(self.player_sprite,
109 self.wall_list,
110 GRAVITY)
111
112 def on_draw(self):
113 """ Render the screen. """
114
115 # Clear the screen to the background color
116 arcade.start_render()
117
118 # Draw our sprites
119 self.wall_list.draw()
120 self.coin_list.draw()
121 self.player_list.draw()
122
123 def on_key_press(self, key, modifiers):
124 """Called whenever a key is pressed. """
125
126 if key == arcade.key.UP or key == arcade.key.W:
127 if self.physics_engine.can_jump():
128 self.player_sprite.change_y = PLAYER_JUMP_SPEED
129 arcade.play_sound(self.jump_sound)
130 elif key == arcade.key.LEFT or key == arcade.key.A:
131 self.player_sprite.change_x = -PLAYER_MOVEMENT_SPEED
132 elif key == arcade.key.RIGHT or key == arcade.key.D:
133 self.player_sprite.change_x = PLAYER_MOVEMENT_SPEED
134
135 def on_key_release(self, key, modifiers):
136 """Called when the user releases a key. """
137
138 if key == arcade.key.LEFT or key == arcade.key.A:
139 self.player_sprite.change_x = 0
140 elif key == arcade.key.RIGHT or key == arcade.key.D:
141 self.player_sprite.change_x = 0
142
143 def update(self, delta_time):
144 """ Movement and game logic """
145
146 # Move the player with the physics engine
147 self.physics_engine.update()
148
149 # See if we hit any coins
150 coin_hit_list = arcade.check_for_collision_with_list(self.player_sprite,
151 self.coin_list)
152
153 # Loop through each coin we hit (if any) and remove it
154 for coin in coin_hit_list:
155 # Remove the coin
156 coin.remove_from_sprite_lists()
157 # Play a sound
158 arcade.play_sound(self.collect_coin_sound)
159
160 # --- Manage Scrolling ---
161
162 # Track if we need to change the viewport
163
164 changed = False
165
166 # Scroll left
167 left_boundary = self.view_left + LEFT_VIEWPORT_MARGIN
168 if self.player_sprite.left < left_boundary:
169 self.view_left -= left_boundary - self.player_sprite.left
170 changed = True
171
172 # Scroll right
173 right_boundary = self.view_left + SCREEN_WIDTH - RIGHT_VIEWPORT_MARGIN
174 if self.player_sprite.right > right_boundary:
175 self.view_left += self.player_sprite.right - right_boundary
176 changed = True
177
178 # Scroll up
179 top_boundary = self.view_bottom + SCREEN_HEIGHT - TOP_VIEWPORT_MARGIN
180 if self.player_sprite.top > top_boundary:
181 self.view_bottom += self.player_sprite.top - top_boundary
182 changed = True
183
184 # Scroll down
185 bottom_boundary = self.view_bottom + BOTTOM_VIEWPORT_MARGIN
186 if self.player_sprite.bottom < bottom_boundary:
187 self.view_bottom -= bottom_boundary - self.player_sprite.bottom
188 changed = True
189
190 if changed:
191 # Only scroll to integers. Otherwise we end up with pixels that
192 # don't line up on the screen
193 self.view_bottom = int(self.view_bottom)
194 self.view_left = int(self.view_left)
195
196 # Do the scrolling
197 arcade.set_viewport(self.view_left,
198 SCREEN_WIDTH + self.view_left,
199 self.view_bottom,
200 SCREEN_HEIGHT + self.view_bottom)
201
202
203def main():
204 """ Main method """
205 window = MyGame()
206 window.setup()
207 arcade.run()
208
209
210if __name__ == "__main__":
211 main()
Note
Spend time placing the coins where you would like them. If you have extra time, try adding more than just coins. Also add gems or keys from the graphics provided.
You could also subclass the coin sprite and add an attribute for a score value. Then you could have coins worth one point, and gems worth 5, 10, and 15 points.
Step 7 - Display The Score¶
Now that we can collect coins and get points, we need a way to display the score on the screen.
This is a bit more complex
than just drawing the score at the same x, y location every time because
we have to “scroll” the score right with the player if we have a scrolling
screen. To do this, we just add in the view_bottom
and view_left
coordinates.
1"""
2Platformer Game
3"""
4import arcade
5
6# Constants
7SCREEN_WIDTH = 1000
8SCREEN_HEIGHT = 650
9SCREEN_TITLE = "Platformer"
10
11# Constants used to scale our sprites from their original size
12CHARACTER_SCALING = 1
13TILE_SCALING = 0.5
14COIN_SCALING = 0.5
15
16# Movement speed of player, in pixels per frame
17PLAYER_MOVEMENT_SPEED = 5
18GRAVITY = 1
19PLAYER_JUMP_SPEED = 20
20
21# How many pixels to keep as a minimum margin between the character
22# and the edge of the screen.
23LEFT_VIEWPORT_MARGIN = 250
24RIGHT_VIEWPORT_MARGIN = 250
25BOTTOM_VIEWPORT_MARGIN = 50
26TOP_VIEWPORT_MARGIN = 100
27
28
29class MyGame(arcade.Window):
30 """
31 Main application class.
32 """
33
34 def __init__(self):
35
36 # Call the parent class and set up the window
37 super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
38
39 # These are 'lists' that keep track of our sprites. Each sprite should
40 # go into a list.
41 self.coin_list = None
42 self.wall_list = None
43 self.player_list = None
44
45 # Separate variable that holds the player sprite
46 self.player_sprite = None
47
48 # Our physics engine
49 self.physics_engine = None
50
51 # Used to keep track of our scrolling
52 self.view_bottom = 0
53 self.view_left = 0
54
55 # Keep track of the score
56 self.score = 0
57
58 # Load sounds
59 self.collect_coin_sound = arcade.load_sound(":resources:sounds/coin1.wav")
60 self.jump_sound = arcade.load_sound(":resources:sounds/jump1.wav")
61
62 arcade.set_background_color(arcade.csscolor.CORNFLOWER_BLUE)
63
64 def setup(self):
65 """ Set up the game here. Call this function to restart the game. """
66
67 # Used to keep track of our scrolling
68 self.view_bottom = 0
69 self.view_left = 0
70
71 # Keep track of the score
72 self.score = 0
73
74 # Create the Sprite lists
75 self.player_list = arcade.SpriteList()
76 self.wall_list = arcade.SpriteList()
77 self.coin_list = arcade.SpriteList()
78
79 # Set up the player, specifically placing it at these coordinates.
80 image_source = ":resources:images/animated_characters/female_adventurer/femaleAdventurer_idle.png"
81 self.player_sprite = arcade.Sprite(image_source, CHARACTER_SCALING)
82 self.player_sprite.center_x = 64
83 self.player_sprite.center_y = 96
84 self.player_list.append(self.player_sprite)
85
86 # Create the ground
87 # This shows using a loop to place multiple sprites horizontally
88 for x in range(0, 1250, 64):
89 wall = arcade.Sprite(":resources:images/tiles/grassMid.png", TILE_SCALING)
90 wall.center_x = x
91 wall.center_y = 32
92 self.wall_list.append(wall)
93
94 # Put some crates on the ground
95 # This shows using a coordinate list to place sprites
96 coordinate_list = [[512, 96],
97 [256, 96],
98 [768, 96]]
99
100 for coordinate in coordinate_list:
101 # Add a crate on the ground
102 wall = arcade.Sprite(":resources:images/tiles/boxCrate_double.png", TILE_SCALING)
103 wall.position = coordinate
104 self.wall_list.append(wall)
105
106 # Use a loop to place some coins for our character to pick up
107 for x in range(128, 1250, 256):
108 coin = arcade.Sprite(":resources:images/items/coinGold.png", COIN_SCALING)
109 coin.center_x = x
110 coin.center_y = 96
111 self.coin_list.append(coin)
112
113 # Create the 'physics engine'
114 self.physics_engine = arcade.PhysicsEnginePlatformer(self.player_sprite,
115 self.wall_list,
116 GRAVITY)
117
118 def on_draw(self):
119 """ Render the screen. """
120
121 # Clear the screen to the background color
122 arcade.start_render()
123
124 # Draw our sprites
125 self.wall_list.draw()
126 self.coin_list.draw()
127 self.player_list.draw()
128
129 # Draw our score on the screen, scrolling it with the viewport
130 score_text = f"Score: {self.score}"
131 arcade.draw_text(score_text, 10 + self.view_left, 10 + self.view_bottom,
132 arcade.csscolor.WHITE, 18)
133
134 def on_key_press(self, key, modifiers):
135 """Called whenever a key is pressed. """
136
137 if key == arcade.key.UP or key == arcade.key.W:
138 if self.physics_engine.can_jump():
139 self.player_sprite.change_y = PLAYER_JUMP_SPEED
140 arcade.play_sound(self.jump_sound)
141 elif key == arcade.key.LEFT or key == arcade.key.A:
142 self.player_sprite.change_x = -PLAYER_MOVEMENT_SPEED
143 elif key == arcade.key.RIGHT or key == arcade.key.D:
144 self.player_sprite.change_x = PLAYER_MOVEMENT_SPEED
145
146 def on_key_release(self, key, modifiers):
147 """Called when the user releases a key. """
148
149 if key == arcade.key.LEFT or key == arcade.key.A:
150 self.player_sprite.change_x = 0
151 elif key == arcade.key.RIGHT or key == arcade.key.D:
152 self.player_sprite.change_x = 0
153
154 def on_update(self, delta_time):
155 """ Movement and game logic """
156
157 # Move the player with the physics engine
158 self.physics_engine.update()
159
160 # See if we hit any coins
161 coin_hit_list = arcade.check_for_collision_with_list(self.player_sprite,
162 self.coin_list)
163
164 # Loop through each coin we hit (if any) and remove it
165 for coin in coin_hit_list:
166 # Remove the coin
167 coin.remove_from_sprite_lists()
168 # Play a sound
169 arcade.play_sound(self.collect_coin_sound)
170 # Add one to the score
171 self.score += 1
172
173 # --- Manage Scrolling ---
174
175 # Track if we need to change the viewport
176
177 changed = False
178
179 # Scroll left
180 left_boundary = self.view_left + LEFT_VIEWPORT_MARGIN
181 if self.player_sprite.left < left_boundary:
182 self.view_left -= left_boundary - self.player_sprite.left
183 changed = True
184
185 # Scroll right
186 right_boundary = self.view_left + SCREEN_WIDTH - RIGHT_VIEWPORT_MARGIN
187 if self.player_sprite.right > right_boundary:
188 self.view_left += self.player_sprite.right - right_boundary
189 changed = True
190
191 # Scroll up
192 top_boundary = self.view_bottom + SCREEN_HEIGHT - TOP_VIEWPORT_MARGIN
193 if self.player_sprite.top > top_boundary:
194 self.view_bottom += self.player_sprite.top - top_boundary
195 changed = True
196
197 # Scroll down
198 bottom_boundary = self.view_bottom + BOTTOM_VIEWPORT_MARGIN
199 if self.player_sprite.bottom < bottom_boundary:
200 self.view_bottom -= bottom_boundary - self.player_sprite.bottom
201 changed = True
202
203 if changed:
204 # Only scroll to integers. Otherwise we end up with pixels that
205 # don't line up on the screen
206 self.view_bottom = int(self.view_bottom)
207 self.view_left = int(self.view_left)
208
209 # Do the scrolling
210 arcade.set_viewport(self.view_left,
211 SCREEN_WIDTH + self.view_left,
212 self.view_bottom,
213 SCREEN_HEIGHT + self.view_bottom)
214
215
216def main():
217 """ Main method """
218 window = MyGame()
219 window.setup()
220 arcade.run()
221
222
223if __name__ == "__main__":
224 main()
Note
You might also want to add:
A count of how many coins are left to be collected.
Number of lives left.
A timer: On-Screen Timer
This example shows how to add an FPS timer: Draw Moving Sprites Stress Test
Explore On Your Own¶
Practice creating your own layout with different tiles.
Add background images. See Using a Background Image
Add moving platforms. See Moving Platforms
Change the character image based on the direction she is facing. See Sprite: Face Left or Right
Add instruction and game over screens.
Step 8 - Use a Map Editor¶

Create a Map File¶
For this part, we’ll restart with a new program. Instead of placing our tiles by code, we’ll use a map editor.
Download and install the Tiled Map Editor. (Think about donating, as it is a wonderful project.)
Open a new file with options similar to these:
Orthogonal - This is a normal square-grid layout. It is the only version that Arcade supports very well at this time.
Tile layer format - This selects how the data is stored inside the file. Any option works, but Base64 zlib compressed is the smallest.
Tile render order - Any of these should work. It simply specifies what order the tiles are added. Right-down has tiles added left->right and top->down.
Map size - You can change this later, but this is your total grid size.
Tile size - the size, in pixels, of your tiles. Your tiles all need to be the same size. Also, rendering works better if the tile size is a power of 2, such as 16, 32, 64, 128, and 256.

Save it as map.tmx
.
Rename the layer “Platforms”. We’ll use layer names to load our data later. Eventually you might have layers for:
Platforms that you run into (or you can think of them as walls)
Coins or objects to pick up
Background objects that you don’t interact with, but appear behind the player
Foreground objects that you don’t interact with, but appear in front of the player
Insta-death blocks (like lava)
Ladders
Note
Once you get multiple layers it is VERY easy to add items to the wrong layer.

Create a Tileset¶
Before we can add anything to the layer we need to create a set of tiles. This isn’t as obvious or intuitive as it should be. To create a new tileset click “New Tileset” in the window on the lower right:

Right now, Arcade only supports a “collection of images” for a tileset. I find it convenient to embed the tileset in the map.

Once you create a new tile, the button to add tiles to the tileset is hard to find. Click the wrench:

Then click the ‘plus’ and add in your tiles

Draw a Level¶
At this point you should be able to “paint” a level. At the very least, put in a floor and then see if you can get this program working. (Don’t put in a lot of time designing a level until you are sure you can get it to load.)
The program below assumes there are layers created by the tiled map editor for for “Platforms” and “Coins”.
Test the Level¶
1"""
2Platformer Game
3"""
4import arcade
5
6# Constants
7SCREEN_WIDTH = 1000
8SCREEN_HEIGHT = 650
9SCREEN_TITLE = "Platformer"
10
11# Constants used to scale our sprites from their original size
12CHARACTER_SCALING = 1
13TILE_SCALING = 0.5
14COIN_SCALING = 0.5
15SPRITE_PIXEL_SIZE = 128
16GRID_PIXEL_SIZE = (SPRITE_PIXEL_SIZE * TILE_SCALING)
17
18# Movement speed of player, in pixels per frame
19PLAYER_MOVEMENT_SPEED = 10
20GRAVITY = 1
21PLAYER_JUMP_SPEED = 20
22
23# How many pixels to keep as a minimum margin between the character
24# and the edge of the screen.
25LEFT_VIEWPORT_MARGIN = 250
26RIGHT_VIEWPORT_MARGIN = 250
27BOTTOM_VIEWPORT_MARGIN = 100
28TOP_VIEWPORT_MARGIN = 100
29
30
31class MyGame(arcade.Window):
32 """
33 Main application class.
34 """
35
36 def __init__(self):
37
38 # Call the parent class and set up the window
39 super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
40
41 # These are 'lists' that keep track of our sprites. Each sprite should
42 # go into a list.
43 self.coin_list = None
44 self.wall_list = None
45 self.player_list = None
46
47 # Separate variable that holds the player sprite
48 self.player_sprite = None
49
50 # Our physics engine
51 self.physics_engine = None
52
53 # Used to keep track of our scrolling
54 self.view_bottom = 0
55 self.view_left = 0
56
57 # Keep track of the score
58 self.score = 0
59
60 # Load sounds
61 self.collect_coin_sound = arcade.load_sound(":resources:sounds/coin1.wav")
62 self.jump_sound = arcade.load_sound(":resources:sounds/jump1.wav")
63
64 arcade.set_background_color(arcade.csscolor.CORNFLOWER_BLUE)
65
66 def setup(self):
67 """ Set up the game here. Call this function to restart the game. """
68
69 # Used to keep track of our scrolling
70 self.view_bottom = 0
71 self.view_left = 0
72
73 # Keep track of the score
74 self.score = 0
75
76 # Create the Sprite lists
77 self.player_list = arcade.SpriteList()
78 self.wall_list = arcade.SpriteList()
79 self.coin_list = arcade.SpriteList()
80
81 # Set up the player, specifically placing it at these coordinates.
82 image_source = ":resources:images/animated_characters/female_adventurer/femaleAdventurer_idle.png"
83 self.player_sprite = arcade.Sprite(image_source, CHARACTER_SCALING)
84 self.player_sprite.center_x = 128
85 self.player_sprite.center_y = 128
86 self.player_list.append(self.player_sprite)
87
88 # --- Load in a map from the tiled editor ---
89
90 # Name of map file to load
91 map_name = ":resources:tmx_maps/map.tmx"
92 # Name of the layer in the file that has our platforms/walls
93 platforms_layer_name = 'Platforms'
94 # Name of the layer that has items for pick-up
95 coins_layer_name = 'Coins'
96
97 # Read in the tiled map
98 my_map = arcade.tilemap.read_tmx(map_name)
99
100 # -- Platforms
101 self.wall_list = arcade.tilemap.process_layer(map_object=my_map,
102 layer_name=platforms_layer_name,
103 scaling=TILE_SCALING,
104 use_spatial_hash=True)
105
106 # -- Coins
107 self.coin_list = arcade.tilemap.process_layer(my_map, coins_layer_name, TILE_SCALING)
108
109 # --- Other stuff
110 # Set the background color
111 if my_map.background_color:
112 arcade.set_background_color(my_map.background_color)
113
114 # Create the 'physics engine'
115 self.physics_engine = arcade.PhysicsEnginePlatformer(self.player_sprite,
116 self.wall_list,
117 GRAVITY)
118
119 def on_draw(self):
120 """ Render the screen. """
121
122 # Clear the screen to the background color
123 arcade.start_render()
124
125 # Draw our sprites
126 self.wall_list.draw()
127 self.coin_list.draw()
128 self.player_list.draw()
129
130 # Draw our score on the screen, scrolling it with the viewport
131 score_text = f"Score: {self.score}"
132 arcade.draw_text(score_text, 10 + self.view_left, 10 + self.view_bottom,
133 arcade.csscolor.WHITE, 18)
134
135 def on_key_press(self, key, modifiers):
136 """Called whenever a key is pressed. """
137
138 if key == arcade.key.UP or key == arcade.key.W:
139 if self.physics_engine.can_jump():
140 self.player_sprite.change_y = PLAYER_JUMP_SPEED
141 arcade.play_sound(self.jump_sound)
142 elif key == arcade.key.LEFT or key == arcade.key.A:
143 self.player_sprite.change_x = -PLAYER_MOVEMENT_SPEED
144 elif key == arcade.key.RIGHT or key == arcade.key.D:
145 self.player_sprite.change_x = PLAYER_MOVEMENT_SPEED
146
147 def on_key_release(self, key, modifiers):
148 """Called when the user releases a key. """
149
150 if key == arcade.key.LEFT or key == arcade.key.A:
151 self.player_sprite.change_x = 0
152 elif key == arcade.key.RIGHT or key == arcade.key.D:
153 self.player_sprite.change_x = 0
154
155 def on_update(self, delta_time):
156 """ Movement and game logic """
157
158 # Move the player with the physics engine
159 self.physics_engine.update()
160
161 # See if we hit any coins
162 coin_hit_list = arcade.check_for_collision_with_list(self.player_sprite,
163 self.coin_list)
164
165 # Loop through each coin we hit (if any) and remove it
166 for coin in coin_hit_list:
167 # Remove the coin
168 coin.remove_from_sprite_lists()
169 # Play a sound
170 arcade.play_sound(self.collect_coin_sound)
171 # Add one to the score
172 self.score += 1
173
174 # --- Manage Scrolling ---
175
176 # Track if we need to change the viewport
177
178 changed = False
179
180 # Scroll left
181 left_boundary = self.view_left + LEFT_VIEWPORT_MARGIN
182 if self.player_sprite.left < left_boundary:
183 self.view_left -= left_boundary - self.player_sprite.left
184 changed = True
185
186 # Scroll right
187 right_boundary = self.view_left + SCREEN_WIDTH - RIGHT_VIEWPORT_MARGIN
188 if self.player_sprite.right > right_boundary:
189 self.view_left += self.player_sprite.right - right_boundary
190 changed = True
191
192 # Scroll up
193 top_boundary = self.view_bottom + SCREEN_HEIGHT - TOP_VIEWPORT_MARGIN
194 if self.player_sprite.top > top_boundary:
195 self.view_bottom += self.player_sprite.top - top_boundary
196 changed = True
197
198 # Scroll down
199 bottom_boundary = self.view_bottom + BOTTOM_VIEWPORT_MARGIN
200 if self.player_sprite.bottom < bottom_boundary:
201 self.view_bottom -= bottom_boundary - self.player_sprite.bottom
202 changed = True
203
204 if changed:
205 # Only scroll to integers. Otherwise we end up with pixels that
206 # don't line up on the screen
207 self.view_bottom = int(self.view_bottom)
208 self.view_left = int(self.view_left)
209
210 # Do the scrolling
211 arcade.set_viewport(self.view_left,
212 SCREEN_WIDTH + self.view_left,
213 self.view_bottom,
214 SCREEN_HEIGHT + self.view_bottom)
215
216
217def main():
218 """ Main method """
219 window = MyGame()
220 window.setup()
221 arcade.run()
222
223
224if __name__ == "__main__":
225 main()
Note
You can set the background color of the map by selecting “Map…Map Properties”. Then click on the three dots to pull up a color picker.
You can edit the hitbox of a tile to make ramps or platforms that only cover a portion of the rectangle in the grid.
To edit the hitbox, use the polygon tool (only) and draw a polygon around the item. You can hold down “CTRL” when positioning a point to get the exact corner of an item.

Step 9 - Multiple Levels and Other Layers¶
Here’s an expanded example:
This adds foreground, background, and “Don’t Touch” layers.
The background tiles appear behind the player
The foreground appears in front of the player
The Don’t Touch layer will reset the player to the start (228-237)
The player resets to the start if they fall off the map (217-226)
If the player gets to the right side of the map, the program attempts to load another layer
Add
level
attribute (69-70)Updated
setup
to load a file based on the level (76-144, specifically lines 77 and 115)Added end-of-map check(245-256)
1"""
2Platformer Game
3"""
4import arcade
5
6# Constants
7SCREEN_WIDTH = 1000
8SCREEN_HEIGHT = 650
9SCREEN_TITLE = "Platformer"
10
11# Constants used to scale our sprites from their original size
12CHARACTER_SCALING = 1
13TILE_SCALING = 0.5
14COIN_SCALING = 0.5
15SPRITE_PIXEL_SIZE = 128
16GRID_PIXEL_SIZE = (SPRITE_PIXEL_SIZE * TILE_SCALING)
17
18# Movement speed of player, in pixels per frame
19PLAYER_MOVEMENT_SPEED = 10
20GRAVITY = 1
21PLAYER_JUMP_SPEED = 20
22
23# How many pixels to keep as a minimum margin between the character
24# and the edge of the screen.
25LEFT_VIEWPORT_MARGIN = 200
26RIGHT_VIEWPORT_MARGIN = 200
27BOTTOM_VIEWPORT_MARGIN = 150
28TOP_VIEWPORT_MARGIN = 100
29
30PLAYER_START_X = 64
31PLAYER_START_Y = 225
32
33
34class MyGame(arcade.Window):
35 """
36 Main application class.
37 """
38
39 def __init__(self):
40
41 # Call the parent class and set up the window
42 super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
43
44 # These are 'lists' that keep track of our sprites. Each sprite should
45 # go into a list.
46 self.coin_list = None
47 self.wall_list = None
48 self.foreground_list = None
49 self.background_list = None
50 self.dont_touch_list = None
51 self.player_list = None
52
53 # Separate variable that holds the player sprite
54 self.player_sprite = None
55
56 # Our physics engine
57 self.physics_engine = None
58
59 # Used to keep track of our scrolling
60 self.view_bottom = 0
61 self.view_left = 0
62
63 # Keep track of the score
64 self.score = 0
65
66 # Where is the right edge of the map?
67 self.end_of_map = 0
68
69 # Level
70 self.level = 1
71
72 # Load sounds
73 self.collect_coin_sound = arcade.load_sound(":resources:sounds/coin1.wav")
74 self.jump_sound = arcade.load_sound(":resources:sounds/jump1.wav")
75 self.game_over = arcade.load_sound(":resources:sounds/gameover1.wav")
76
77 def setup(self, level):
78 """ Set up the game here. Call this function to restart the game. """
79
80 # Used to keep track of our scrolling
81 self.view_bottom = 0
82 self.view_left = 0
83
84 # Keep track of the score
85 self.score = 0
86
87 # Create the Sprite lists
88 self.player_list = arcade.SpriteList()
89 self.foreground_list = arcade.SpriteList()
90 self.background_list = arcade.SpriteList()
91 self.wall_list = arcade.SpriteList()
92 self.coin_list = arcade.SpriteList()
93
94 # Set up the player, specifically placing it at these coordinates.
95 image_source = ":resources:images/animated_characters/female_adventurer/femaleAdventurer_idle.png"
96 self.player_sprite = arcade.Sprite(image_source, CHARACTER_SCALING)
97 self.player_sprite.center_x = PLAYER_START_X
98 self.player_sprite.center_y = PLAYER_START_Y
99 self.player_list.append(self.player_sprite)
100
101 # --- Load in a map from the tiled editor ---
102
103 # Name of the layer in the file that has our platforms/walls
104 platforms_layer_name = 'Platforms'
105 # Name of the layer that has items for pick-up
106 coins_layer_name = 'Coins'
107 # Name of the layer that has items for foreground
108 foreground_layer_name = 'Foreground'
109 # Name of the layer that has items for background
110 background_layer_name = 'Background'
111 # Name of the layer that has items we shouldn't touch
112 dont_touch_layer_name = "Don't Touch"
113
114 # Map name
115 map_name = f":resources:tmx_maps/map2_level_{level}.tmx"
116
117 # Read in the tiled map
118 my_map = arcade.tilemap.read_tmx(map_name)
119
120 # Calculate the right edge of the my_map in pixels
121 self.end_of_map = my_map.map_size.width * GRID_PIXEL_SIZE
122
123 # -- Background
124 self.background_list = arcade.tilemap.process_layer(my_map,
125 background_layer_name,
126 TILE_SCALING)
127
128 # -- Foreground
129 self.foreground_list = arcade.tilemap.process_layer(my_map,
130 foreground_layer_name,
131 TILE_SCALING)
132
133 # -- Platforms
134 self.wall_list = arcade.tilemap.process_layer(map_object=my_map,
135 layer_name=platforms_layer_name,
136 scaling=TILE_SCALING,
137 use_spatial_hash=True)
138
139 # -- Coins
140 self.coin_list = arcade.tilemap.process_layer(my_map,
141 coins_layer_name,
142 TILE_SCALING,
143 use_spatial_hash=True)
144
145 # -- Don't Touch Layer
146 self.dont_touch_list = arcade.tilemap.process_layer(my_map,
147 dont_touch_layer_name,
148 TILE_SCALING,
149 use_spatial_hash=True)
150
151 # --- Other stuff
152 # Set the background color
153 if my_map.background_color:
154 arcade.set_background_color(my_map.background_color)
155
156 # Create the 'physics engine'
157 self.physics_engine = arcade.PhysicsEnginePlatformer(self.player_sprite,
158 self.wall_list,
159 GRAVITY)
160
161 def on_draw(self):
162 """ Render the screen. """
163
164 # Clear the screen to the background color
165 arcade.start_render()
166
167 # Draw our sprites
168 self.wall_list.draw()
169 self.background_list.draw()
170 self.wall_list.draw()
171 self.coin_list.draw()
172 self.dont_touch_list.draw()
173 self.player_list.draw()
174 self.foreground_list.draw()
175
176 # Draw our score on the screen, scrolling it with the viewport
177 score_text = f"Score: {self.score}"
178 arcade.draw_text(score_text, 10 + self.view_left, 10 + self.view_bottom,
179 arcade.csscolor.BLACK, 18)
180
181 def on_key_press(self, key, modifiers):
182 """Called whenever a key is pressed. """
183
184 if key == arcade.key.UP or key == arcade.key.W:
185 if self.physics_engine.can_jump():
186 self.player_sprite.change_y = PLAYER_JUMP_SPEED
187 arcade.play_sound(self.jump_sound)
188 elif key == arcade.key.LEFT or key == arcade.key.A:
189 self.player_sprite.change_x = -PLAYER_MOVEMENT_SPEED
190 elif key == arcade.key.RIGHT or key == arcade.key.D:
191 self.player_sprite.change_x = PLAYER_MOVEMENT_SPEED
192
193 def on_key_release(self, key, modifiers):
194 """Called when the user releases a key. """
195
196 if key == arcade.key.LEFT or key == arcade.key.A:
197 self.player_sprite.change_x = 0
198 elif key == arcade.key.RIGHT or key == arcade.key.D:
199 self.player_sprite.change_x = 0
200
201 def update(self, delta_time):
202 """ Movement and game logic """
203
204 # Move the player with the physics engine
205 self.physics_engine.update()
206
207 # See if we hit any coins
208 coin_hit_list = arcade.check_for_collision_with_list(self.player_sprite,
209 self.coin_list)
210
211 # Loop through each coin we hit (if any) and remove it
212 for coin in coin_hit_list:
213 # Remove the coin
214 coin.remove_from_sprite_lists()
215 # Play a sound
216 arcade.play_sound(self.collect_coin_sound)
217 # Add one to the score
218 self.score += 1
219
220 # Track if we need to change the viewport
221 changed_viewport = False
222
223 # Did the player fall off the map?
224 if self.player_sprite.center_y < -100:
225 self.player_sprite.center_x = PLAYER_START_X
226 self.player_sprite.center_y = PLAYER_START_Y
227
228 # Set the camera to the start
229 self.view_left = 0
230 self.view_bottom = 0
231 changed_viewport = True
232 arcade.play_sound(self.game_over)
233
234 # Did the player touch something they should not?
235 if arcade.check_for_collision_with_list(self.player_sprite,
236 self.dont_touch_list):
237 self.player_sprite.change_x = 0
238 self.player_sprite.change_y = 0
239 self.player_sprite.center_x = PLAYER_START_X
240 self.player_sprite.center_y = PLAYER_START_Y
241
242 # Set the camera to the start
243 self.view_left = 0
244 self.view_bottom = 0
245 changed_viewport = True
246 arcade.play_sound(self.game_over)
247
248 # See if the user got to the end of the level
249 if self.player_sprite.center_x >= self.end_of_map:
250 # Advance to the next level
251 self.level += 1
252
253 # Load the next level
254 self.setup(self.level)
255
256 # Set the camera to the start
257 self.view_left = 0
258 self.view_bottom = 0
259 changed_viewport = True
260
261 # --- Manage Scrolling ---
262
263 # Scroll left
264 left_boundary = self.view_left + LEFT_VIEWPORT_MARGIN
265 if self.player_sprite.left < left_boundary:
266 self.view_left -= left_boundary - self.player_sprite.left
267 changed_viewport = True
268
269 # Scroll right
270 right_boundary = self.view_left + SCREEN_WIDTH - RIGHT_VIEWPORT_MARGIN
271 if self.player_sprite.right > right_boundary:
272 self.view_left += self.player_sprite.right - right_boundary
273 changed_viewport = True
274
275 # Scroll up
276 top_boundary = self.view_bottom + SCREEN_HEIGHT - TOP_VIEWPORT_MARGIN
277 if self.player_sprite.top > top_boundary:
278 self.view_bottom += self.player_sprite.top - top_boundary
279 changed_viewport = True
280
281 # Scroll down
282 bottom_boundary = self.view_bottom + BOTTOM_VIEWPORT_MARGIN
283 if self.player_sprite.bottom < bottom_boundary:
284 self.view_bottom -= bottom_boundary - self.player_sprite.bottom
285 changed_viewport = True
286
287 if changed_viewport:
288 # Only scroll to integers. Otherwise we end up with pixels that
289 # don't line up on the screen
290 self.view_bottom = int(self.view_bottom)
291 self.view_left = int(self.view_left)
292
293 # Do the scrolling
294 arcade.set_viewport(self.view_left,
295 SCREEN_WIDTH + self.view_left,
296 self.view_bottom,
297 SCREEN_HEIGHT + self.view_bottom)
298
299
300def main():
301 """ Main method """
302 window = MyGame()
303 window.setup(window.level)
304 arcade.run()
305
306
307if __name__ == "__main__":
308 main()
Note
What else might you want to do?
Bullets (or something you can shoot)
Step 10 - Add Ladders, Properties, and a Moving Platform¶

This example shows using:
Ladders
Properties to define point value of coins and flags
Properties and an object layer to define a moving platform.
To create a moving platform using TMX editor, there are a few steps:
Define an object layer instead of a tile layer.
Select Insert Tile
Select the tile you wish to insert.
Place the tile.
Add custom properties. You can add:
change_x
change_y
boundary_bottom
boundary_top
boundary_left
boundary_right

1"""
2Platformer Game
3"""
4import arcade
5import os
6
7# Constants
8SCREEN_WIDTH = 1000
9SCREEN_HEIGHT = 650
10SCREEN_TITLE = "Platformer"
11
12# Constants used to scale our sprites from their original size
13CHARACTER_SCALING = 1
14TILE_SCALING = 0.5
15COIN_SCALING = 0.5
16SPRITE_PIXEL_SIZE = 128
17GRID_PIXEL_SIZE = (SPRITE_PIXEL_SIZE * TILE_SCALING)
18
19# Movement speed of player, in pixels per frame
20PLAYER_MOVEMENT_SPEED = 7
21GRAVITY = 1.5
22PLAYER_JUMP_SPEED = 30
23
24# How many pixels to keep as a minimum margin between the character
25# and the edge of the screen.
26LEFT_VIEWPORT_MARGIN = 200
27RIGHT_VIEWPORT_MARGIN = 200
28BOTTOM_VIEWPORT_MARGIN = 150
29TOP_VIEWPORT_MARGIN = 100
30
31PLAYER_START_X = 64
32PLAYER_START_Y = 256
33
34
35class MyGame(arcade.Window):
36 """
37 Main application class.
38 """
39
40 def __init__(self):
41 """
42 Initializer for the game
43 """
44
45 # Call the parent class and set up the window
46 super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
47
48 # Set the path to start with this program
49 file_path = os.path.dirname(os.path.abspath(__file__))
50 os.chdir(file_path)
51
52 # These are 'lists' that keep track of our sprites. Each sprite should
53 # go into a list.
54 self.coin_list = None
55 self.wall_list = None
56 self.background_list = None
57 self.ladder_list = None
58 self.player_list = None
59
60 # Separate variable that holds the player sprite
61 self.player_sprite = None
62
63 # Our 'physics' engine
64 self.physics_engine = None
65
66 # Used to keep track of our scrolling
67 self.view_bottom = 0
68 self.view_left = 0
69
70 self.end_of_map = 0
71
72 # Keep track of the score
73 self.score = 0
74
75 # Load sounds
76 self.collect_coin_sound = arcade.load_sound(":resources:sounds/coin1.wav")
77 self.jump_sound = arcade.load_sound(":resources:sounds/jump1.wav")
78 self.game_over = arcade.load_sound(":resources:sounds/gameover1.wav")
79
80 def setup(self):
81 """ Set up the game here. Call this function to restart the game. """
82
83 # Used to keep track of our scrolling
84 self.view_bottom = 0
85 self.view_left = 0
86
87 # Keep track of the score
88 self.score = 0
89
90 # Create the Sprite lists
91 self.player_list = arcade.SpriteList()
92 self.background_list = arcade.SpriteList()
93 self.wall_list = arcade.SpriteList()
94 self.coin_list = arcade.SpriteList()
95
96 # Set up the player, specifically placing it at these coordinates.
97 image_source = ":resources:images/animated_characters/female_adventurer/femaleAdventurer_idle.png"
98 self.player_sprite = arcade.Sprite(image_source, CHARACTER_SCALING)
99 self.player_sprite.center_x = PLAYER_START_X
100 self.player_sprite.center_y = PLAYER_START_Y
101 self.player_list.append(self.player_sprite)
102
103 # --- Load in a map from the tiled editor ---
104
105 # Name of the layer in the file that has our platforms/walls
106 platforms_layer_name = 'Platforms'
107 moving_platforms_layer_name = 'Moving Platforms'
108
109 # Name of the layer that has items for pick-up
110 coins_layer_name = 'Coins'
111
112 # Map name
113 map_name = f":resources:tmx_maps/map_with_ladders.tmx"
114
115 # Read in the tiled map
116 my_map = arcade.tilemap.read_tmx(map_name)
117
118 # Calculate the right edge of the my_map in pixels
119 self.end_of_map = my_map.map_size.width * GRID_PIXEL_SIZE
120
121 # -- Platforms
122 self.wall_list = arcade.tilemap.process_layer(my_map,
123 platforms_layer_name,
124 scaling=TILE_SCALING,
125 use_spatial_hash=True)
126
127 # -- Moving Platforms
128 moving_platforms_list = arcade.tilemap.process_layer(my_map, moving_platforms_layer_name, TILE_SCALING)
129 for sprite in moving_platforms_list:
130 self.wall_list.append(sprite)
131
132 # -- Background objects
133 self.background_list = arcade.tilemap.process_layer(my_map, "Background", TILE_SCALING)
134
135 # -- Background objects
136 self.ladder_list = arcade.tilemap.process_layer(my_map,
137 "Ladders",
138 scaling=TILE_SCALING,
139 use_spatial_hash=True)
140
141 # -- Coins
142 self.coin_list = arcade.tilemap.process_layer(my_map,
143 coins_layer_name,
144 scaling=TILE_SCALING,
145 use_spatial_hash=True)
146
147 # --- Other stuff
148 # Set the background color
149 if my_map.background_color:
150 arcade.set_background_color(my_map.background_color)
151
152 # Create the 'physics engine'
153 self.physics_engine = arcade.PhysicsEnginePlatformer(self.player_sprite,
154 self.wall_list,
155 gravity_constant=GRAVITY,
156 ladders=self.ladder_list)
157
158 def on_draw(self):
159 """ Render the screen. """
160
161 # Clear the screen to the background color
162 arcade.start_render()
163
164 # Draw our sprites
165 self.wall_list.draw()
166 self.background_list.draw()
167 self.ladder_list.draw()
168 self.coin_list.draw()
169 self.player_list.draw()
170
171 # Draw our score on the screen, scrolling it with the viewport
172 score_text = f"Score: {self.score}"
173 arcade.draw_text(score_text, 10 + self.view_left, 10 + self.view_bottom,
174 arcade.csscolor.BLACK, 18)
175
176 def on_key_press(self, key, modifiers):
177 """Called whenever a key is pressed. """
178
179 if key == arcade.key.UP or key == arcade.key.W:
180 if self.physics_engine.is_on_ladder():
181 self.player_sprite.change_y = PLAYER_MOVEMENT_SPEED
182 elif self.physics_engine.can_jump():
183 self.player_sprite.change_y = PLAYER_JUMP_SPEED
184 arcade.play_sound(self.jump_sound)
185 elif key == arcade.key.DOWN or key == arcade.key.S:
186 if self.physics_engine.is_on_ladder():
187 self.player_sprite.change_y = -PLAYER_MOVEMENT_SPEED
188 elif key == arcade.key.LEFT or key == arcade.key.A:
189 self.player_sprite.change_x = -PLAYER_MOVEMENT_SPEED
190 elif key == arcade.key.RIGHT or key == arcade.key.D:
191 self.player_sprite.change_x = PLAYER_MOVEMENT_SPEED
192
193 def on_key_release(self, key, modifiers):
194 """Called when the user releases a key. """
195
196 if key == arcade.key.UP or key == arcade.key.W:
197 if self.physics_engine.is_on_ladder():
198 self.player_sprite.change_y = 0
199 elif key == arcade.key.DOWN or key == arcade.key.S:
200 if self.physics_engine.is_on_ladder():
201 self.player_sprite.change_y = 0
202 elif key == arcade.key.LEFT or key == arcade.key.A:
203 self.player_sprite.change_x = 0
204 elif key == arcade.key.RIGHT or key == arcade.key.D:
205 self.player_sprite.change_x = 0
206
207 def update(self, delta_time):
208 """ Movement and game logic """
209
210 # Move the player with the physics engine
211 self.physics_engine.update()
212
213 # Update animations
214 self.coin_list.update_animation(delta_time)
215 self.background_list.update_animation(delta_time)
216
217 # Update walls, used with moving platforms
218 self.wall_list.update()
219
220 # See if the wall hit a boundary and needs to reverse direction.
221 for wall in self.wall_list:
222
223 if wall.boundary_right and wall.right > wall.boundary_right and wall.change_x > 0:
224 wall.change_x *= -1
225 if wall.boundary_left and wall.left < wall.boundary_left and wall.change_x < 0:
226 wall.change_x *= -1
227 if wall.boundary_top and wall.top > wall.boundary_top and wall.change_y > 0:
228 wall.change_y *= -1
229 if wall.boundary_bottom and wall.bottom < wall.boundary_bottom and wall.change_y < 0:
230 wall.change_y *= -1
231
232 # See if we hit any coins
233 coin_hit_list = arcade.check_for_collision_with_list(self.player_sprite,
234 self.coin_list)
235
236 # Loop through each coin we hit (if any) and remove it
237 for coin in coin_hit_list:
238
239 # Figure out how many points this coin is worth
240 if 'Points' not in coin.properties:
241 print("Warning, collected a coing without a Points property.")
242 else:
243 points = int(coin.properties['Points'])
244 self.score += points
245
246 # Remove the coin
247 coin.remove_from_sprite_lists()
248 arcade.play_sound(self.collect_coin_sound)
249
250 # Track if we need to change the viewport
251 changed_viewport = False
252
253 # --- Manage Scrolling ---
254
255 # Scroll left
256 left_boundary = self.view_left + LEFT_VIEWPORT_MARGIN
257 if self.player_sprite.left < left_boundary:
258 self.view_left -= left_boundary - self.player_sprite.left
259 changed_viewport = True
260
261 # Scroll right
262 right_boundary = self.view_left + SCREEN_WIDTH - RIGHT_VIEWPORT_MARGIN
263 if self.player_sprite.right > right_boundary:
264 self.view_left += self.player_sprite.right - right_boundary
265 changed_viewport = True
266
267 # Scroll up
268 top_boundary = self.view_bottom + SCREEN_HEIGHT - TOP_VIEWPORT_MARGIN
269 if self.player_sprite.top > top_boundary:
270 self.view_bottom += self.player_sprite.top - top_boundary
271 changed_viewport = True
272
273 # Scroll down
274 bottom_boundary = self.view_bottom + BOTTOM_VIEWPORT_MARGIN
275 if self.player_sprite.bottom < bottom_boundary:
276 self.view_bottom -= bottom_boundary - self.player_sprite.bottom
277 changed_viewport = True
278
279 if changed_viewport:
280 # Only scroll to integers. Otherwise we end up with pixels that
281 # don't line up on the screen
282 self.view_bottom = int(self.view_bottom)
283 self.view_left = int(self.view_left)
284
285 # Do the scrolling
286 arcade.set_viewport(self.view_left,
287 SCREEN_WIDTH + self.view_left,
288 self.view_bottom,
289 SCREEN_HEIGHT + self.view_bottom)
290
291
292def main():
293 """ Main method """
294 window = MyGame()
295 window.setup()
296 arcade.run()
297
298
299if __name__ == "__main__":
300 main()
Step 11 - Add Character Animations, and Better Keyboard Control¶
Add character animations!
1"""
2Platformer Game
3
4python -m arcade.examples.platform_tutorial.11_animate_character
5"""
6import arcade
7import os
8
9# Constants
10SCREEN_WIDTH = 1000
11SCREEN_HEIGHT = 650
12SCREEN_TITLE = "Platformer"
13
14# Constants used to scale our sprites from their original size
15TILE_SCALING = 0.5
16CHARACTER_SCALING = TILE_SCALING * 2
17COIN_SCALING = TILE_SCALING
18SPRITE_PIXEL_SIZE = 128
19GRID_PIXEL_SIZE = (SPRITE_PIXEL_SIZE * TILE_SCALING)
20
21# Movement speed of player, in pixels per frame
22PLAYER_MOVEMENT_SPEED = 7
23GRAVITY = 1.5
24PLAYER_JUMP_SPEED = 30
25
26# How many pixels to keep as a minimum margin between the character
27# and the edge of the screen.
28LEFT_VIEWPORT_MARGIN = 200
29RIGHT_VIEWPORT_MARGIN = 200
30BOTTOM_VIEWPORT_MARGIN = 150
31TOP_VIEWPORT_MARGIN = 100
32
33PLAYER_START_X = SPRITE_PIXEL_SIZE * TILE_SCALING * 2
34PLAYER_START_Y = SPRITE_PIXEL_SIZE * TILE_SCALING * 1
35
36# Constants used to track if the player is facing left or right
37RIGHT_FACING = 0
38LEFT_FACING = 1
39
40
41def load_texture_pair(filename):
42 """
43 Load a texture pair, with the second being a mirror image.
44 """
45 return [
46 arcade.load_texture(filename),
47 arcade.load_texture(filename, flipped_horizontally=True)
48 ]
49
50
51class PlayerCharacter(arcade.Sprite):
52 """ Player Sprite"""
53 def __init__(self):
54
55 # Set up parent class
56 super().__init__()
57
58 # Default to face-right
59 self.character_face_direction = RIGHT_FACING
60
61 # Used for flipping between image sequences
62 self.cur_texture = 0
63 self.scale = CHARACTER_SCALING
64
65 # Track our state
66 self.jumping = False
67 self.climbing = False
68 self.is_on_ladder = False
69
70 # --- Load Textures ---
71
72 # Images from Kenney.nl's Asset Pack 3
73 # main_path = ":resources:images/animated_characters/female_adventurer/femaleAdventurer"
74 # main_path = ":resources:images/animated_characters/female_person/femalePerson"
75 main_path = ":resources:images/animated_characters/male_person/malePerson"
76 # main_path = ":resources:images/animated_characters/male_adventurer/maleAdventurer"
77 # main_path = ":resources:images/animated_characters/zombie/zombie"
78 # main_path = ":resources:images/animated_characters/robot/robot"
79
80 # Load textures for idle standing
81 self.idle_texture_pair = load_texture_pair(f"{main_path}_idle.png")
82 self.jump_texture_pair = load_texture_pair(f"{main_path}_jump.png")
83 self.fall_texture_pair = load_texture_pair(f"{main_path}_fall.png")
84
85 # Load textures for walking
86 self.walk_textures = []
87 for i in range(8):
88 texture = load_texture_pair(f"{main_path}_walk{i}.png")
89 self.walk_textures.append(texture)
90
91 # Load textures for climbing
92 self.climbing_textures = []
93 texture = arcade.load_texture(f"{main_path}_climb0.png")
94 self.climbing_textures.append(texture)
95 texture = arcade.load_texture(f"{main_path}_climb1.png")
96 self.climbing_textures.append(texture)
97
98 # Set the initial texture
99 self.texture = self.idle_texture_pair[0]
100
101 # Hit box will be set based on the first image used. If you want to specify
102 # a different hit box, you can do it like the code below.
103 # self.set_hit_box([[-22, -64], [22, -64], [22, 28], [-22, 28]])
104 self.set_hit_box(self.texture.hit_box_points)
105
106 def update_animation(self, delta_time: float = 1/60):
107
108 # Figure out if we need to flip face left or right
109 if self.change_x < 0 and self.character_face_direction == RIGHT_FACING:
110 self.character_face_direction = LEFT_FACING
111 elif self.change_x > 0 and self.character_face_direction == LEFT_FACING:
112 self.character_face_direction = RIGHT_FACING
113
114 # Climbing animation
115 if self.is_on_ladder:
116 self.climbing = True
117 if not self.is_on_ladder and self.climbing:
118 self.climbing = False
119 if self.climbing and abs(self.change_y) > 1:
120 self.cur_texture += 1
121 if self.cur_texture > 7:
122 self.cur_texture = 0
123 if self.climbing:
124 self.texture = self.climbing_textures[self.cur_texture // 4]
125 return
126
127 # Jumping animation
128 if self.change_y > 0 and not self.is_on_ladder:
129 self.texture = self.jump_texture_pair[self.character_face_direction]
130 return
131 elif self.change_y < 0 and not self.is_on_ladder:
132 self.texture = self.fall_texture_pair[self.character_face_direction]
133 return
134
135 # Idle animation
136 if self.change_x == 0:
137 self.texture = self.idle_texture_pair[self.character_face_direction]
138 return
139
140 # Walking animation
141 self.cur_texture += 1
142 if self.cur_texture > 7:
143 self.cur_texture = 0
144 self.texture = self.walk_textures[self.cur_texture][self.character_face_direction]
145
146
147class MyGame(arcade.Window):
148 """
149 Main application class.
150 """
151
152 def __init__(self):
153 """
154 Initializer for the game
155 """
156
157 # Call the parent class and set up the window
158 super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
159
160 # Set the path to start with this program
161 file_path = os.path.dirname(os.path.abspath(__file__))
162 os.chdir(file_path)
163
164 # Track the current state of what key is pressed
165 self.left_pressed = False
166 self.right_pressed = False
167 self.up_pressed = False
168 self.down_pressed = False
169 self.jump_needs_reset = False
170
171 # These are 'lists' that keep track of our sprites. Each sprite should
172 # go into a list.
173 self.coin_list = None
174 self.wall_list = None
175 self.background_list = None
176 self.ladder_list = None
177 self.player_list = None
178
179 # Separate variable that holds the player sprite
180 self.player_sprite = None
181
182 # Our 'physics' engine
183 self.physics_engine = None
184
185 # Used to keep track of our scrolling
186 self.view_bottom = 0
187 self.view_left = 0
188
189 self.end_of_map = 0
190
191 # Keep track of the score
192 self.score = 0
193
194 # Load sounds
195 self.collect_coin_sound = arcade.load_sound(":resources:sounds/coin1.wav")
196 self.jump_sound = arcade.load_sound(":resources:sounds/jump1.wav")
197 self.game_over = arcade.load_sound(":resources:sounds/gameover1.wav")
198
199 def setup(self):
200 """ Set up the game here. Call this function to restart the game. """
201
202 # Used to keep track of our scrolling
203 self.view_bottom = 0
204 self.view_left = 0
205
206 # Keep track of the score
207 self.score = 0
208
209 # Create the Sprite lists
210 self.player_list = arcade.SpriteList()
211 self.background_list = arcade.SpriteList()
212 self.wall_list = arcade.SpriteList()
213 self.coin_list = arcade.SpriteList()
214
215 # Set up the player, specifically placing it at these coordinates.
216 self.player_sprite = PlayerCharacter()
217
218 self.player_sprite.center_x = PLAYER_START_X
219 self.player_sprite.center_y = PLAYER_START_Y
220 self.player_list.append(self.player_sprite)
221
222 # --- Load in a map from the tiled editor ---
223
224 # Name of the layer in the file that has our platforms/walls
225 platforms_layer_name = 'Platforms'
226 moving_platforms_layer_name = 'Moving Platforms'
227
228 # Name of the layer that has items for pick-up
229 coins_layer_name = 'Coins'
230
231 # Map name
232 map_name = f":resources:tmx_maps/map_with_ladders.tmx"
233
234 # Read in the tiled map
235 my_map = arcade.tilemap.read_tmx(map_name)
236
237 # Calculate the right edge of the my_map in pixels
238 self.end_of_map = my_map.map_size.width * GRID_PIXEL_SIZE
239
240 # -- Platforms
241 self.wall_list = arcade.tilemap.process_layer(my_map,
242 platforms_layer_name,
243 TILE_SCALING,
244 use_spatial_hash=True)
245
246 # -- Moving Platforms
247 moving_platforms_list = arcade.tilemap.process_layer(my_map, moving_platforms_layer_name, TILE_SCALING)
248 for sprite in moving_platforms_list:
249 self.wall_list.append(sprite)
250
251 # -- Background objects
252 self.background_list = arcade.tilemap.process_layer(my_map, "Background", TILE_SCALING)
253
254 # -- Background objects
255 self.ladder_list = arcade.tilemap.process_layer(my_map, "Ladders",
256 TILE_SCALING,
257 use_spatial_hash=True)
258
259 # -- Coins
260 self.coin_list = arcade.tilemap.process_layer(my_map, coins_layer_name,
261 TILE_SCALING,
262 use_spatial_hash=True)
263
264 # --- Other stuff
265 # Set the background color
266 if my_map.background_color:
267 arcade.set_background_color(my_map.background_color)
268
269 # Create the 'physics engine'
270 self.physics_engine = arcade.PhysicsEnginePlatformer(self.player_sprite,
271 self.wall_list,
272 gravity_constant=GRAVITY,
273 ladders=self.ladder_list)
274
275 def on_draw(self):
276 """ Render the screen. """
277
278 # Clear the screen to the background color
279 arcade.start_render()
280
281 # Draw our sprites
282 self.wall_list.draw()
283 self.background_list.draw()
284 self.ladder_list.draw()
285 self.coin_list.draw()
286 self.player_list.draw()
287
288 # Draw our score on the screen, scrolling it with the viewport
289 score_text = f"Score: {self.score}"
290 arcade.draw_text(score_text, 10 + self.view_left, 10 + self.view_bottom,
291 arcade.csscolor.BLACK, 18)
292
293 # Draw hit boxes.
294 # for wall in self.wall_list:
295 # wall.draw_hit_box(arcade.color.BLACK, 3)
296 #
297 # self.player_sprite.draw_hit_box(arcade.color.RED, 3)
298
299 def process_keychange(self):
300 """
301 Called when we change a key up/down or we move on/off a ladder.
302 """
303 # Process up/down
304 if self.up_pressed and not self.down_pressed:
305 if self.physics_engine.is_on_ladder():
306 self.player_sprite.change_y = PLAYER_MOVEMENT_SPEED
307 elif self.physics_engine.can_jump(y_distance=10) and not self.jump_needs_reset:
308 self.player_sprite.change_y = PLAYER_JUMP_SPEED
309 self.jump_needs_reset = True
310 arcade.play_sound(self.jump_sound)
311 elif self.down_pressed and not self.up_pressed:
312 if self.physics_engine.is_on_ladder():
313 self.player_sprite.change_y = -PLAYER_MOVEMENT_SPEED
314
315 # Process up/down when on a ladder and no movement
316 if self.physics_engine.is_on_ladder():
317 if not self.up_pressed and not self.down_pressed:
318 self.player_sprite.change_y = 0
319 elif self.up_pressed and self.down_pressed:
320 self.player_sprite.change_y = 0
321
322 # Process left/right
323 if self.right_pressed and not self.left_pressed:
324 self.player_sprite.change_x = PLAYER_MOVEMENT_SPEED
325 elif self.left_pressed and not self.right_pressed:
326 self.player_sprite.change_x = -PLAYER_MOVEMENT_SPEED
327 else:
328 self.player_sprite.change_x = 0
329
330 def on_key_press(self, key, modifiers):
331 """Called whenever a key is pressed. """
332
333 if key == arcade.key.UP or key == arcade.key.W:
334 self.up_pressed = True
335 elif key == arcade.key.DOWN or key == arcade.key.S:
336 self.down_pressed = True
337 elif key == arcade.key.LEFT or key == arcade.key.A:
338 self.left_pressed = True
339 elif key == arcade.key.RIGHT or key == arcade.key.D:
340 self.right_pressed = True
341
342 self.process_keychange()
343
344 def on_key_release(self, key, modifiers):
345 """Called when the user releases a key. """
346
347 if key == arcade.key.UP or key == arcade.key.W:
348 self.up_pressed = False
349 self.jump_needs_reset = False
350 elif key == arcade.key.DOWN or key == arcade.key.S:
351 self.down_pressed = False
352 elif key == arcade.key.LEFT or key == arcade.key.A:
353 self.left_pressed = False
354 elif key == arcade.key.RIGHT or key == arcade.key.D:
355 self.right_pressed = False
356
357 self.process_keychange()
358
359 def on_update(self, delta_time):
360 """ Movement and game logic """
361
362 # Move the player with the physics engine
363 self.physics_engine.update()
364
365 # Update animations
366 if self.physics_engine.can_jump():
367 self.player_sprite.can_jump = False
368 else:
369 self.player_sprite.can_jump = True
370
371 if self.physics_engine.is_on_ladder() and not self.physics_engine.can_jump():
372 self.player_sprite.is_on_ladder = True
373 self.process_keychange()
374 else:
375 self.player_sprite.is_on_ladder = False
376 self.process_keychange()
377
378 self.coin_list.update_animation(delta_time)
379 self.background_list.update_animation(delta_time)
380 self.player_list.update_animation(delta_time)
381
382 # Update walls, used with moving platforms
383 self.wall_list.update()
384
385 # See if the moving wall hit a boundary and needs to reverse direction.
386 for wall in self.wall_list:
387
388 if wall.boundary_right and wall.right > wall.boundary_right and wall.change_x > 0:
389 wall.change_x *= -1
390 if wall.boundary_left and wall.left < wall.boundary_left and wall.change_x < 0:
391 wall.change_x *= -1
392 if wall.boundary_top and wall.top > wall.boundary_top and wall.change_y > 0:
393 wall.change_y *= -1
394 if wall.boundary_bottom and wall.bottom < wall.boundary_bottom and wall.change_y < 0:
395 wall.change_y *= -1
396
397 # See if we hit any coins
398 coin_hit_list = arcade.check_for_collision_with_list(self.player_sprite,
399 self.coin_list)
400
401 # Loop through each coin we hit (if any) and remove it
402 for coin in coin_hit_list:
403
404 # Figure out how many points this coin is worth
405 if 'Points' not in coin.properties:
406 print("Warning, collected a coin without a Points property.")
407 else:
408 points = int(coin.properties['Points'])
409 self.score += points
410
411 # Remove the coin
412 coin.remove_from_sprite_lists()
413 arcade.play_sound(self.collect_coin_sound)
414
415 # Track if we need to change the viewport
416 changed_viewport = False
417
418 # --- Manage Scrolling ---
419
420 # Scroll left
421 left_boundary = self.view_left + LEFT_VIEWPORT_MARGIN
422 if self.player_sprite.left < left_boundary:
423 self.view_left -= left_boundary - self.player_sprite.left
424 changed_viewport = True
425
426 # Scroll right
427 right_boundary = self.view_left + SCREEN_WIDTH - RIGHT_VIEWPORT_MARGIN
428 if self.player_sprite.right > right_boundary:
429 self.view_left += self.player_sprite.right - right_boundary
430 changed_viewport = True
431
432 # Scroll up
433 top_boundary = self.view_bottom + SCREEN_HEIGHT - TOP_VIEWPORT_MARGIN
434 if self.player_sprite.top > top_boundary:
435 self.view_bottom += self.player_sprite.top - top_boundary
436 changed_viewport = True
437
438 # Scroll down
439 bottom_boundary = self.view_bottom + BOTTOM_VIEWPORT_MARGIN
440 if self.player_sprite.bottom < bottom_boundary:
441 self.view_bottom -= bottom_boundary - self.player_sprite.bottom
442 changed_viewport = True
443
444 if changed_viewport:
445 # Only scroll to integers. Otherwise we end up with pixels that
446 # don't line up on the screen
447 self.view_bottom = int(self.view_bottom)
448 self.view_left = int(self.view_left)
449
450 # Do the scrolling
451 arcade.set_viewport(self.view_left,
452 SCREEN_WIDTH + self.view_left,
453 self.view_bottom,
454 SCREEN_HEIGHT + self.view_bottom)
455
456
457def main():
458 """ Main method """
459 window = MyGame()
460 window.setup()
461 arcade.run()
462
463
464if __name__ == "__main__":
465 main()
Pymunk Platformer¶

This tutorial covers how to write a platformer using Arcade and its Pymunk API. This tutorial assumes the you are somewhat familiar with Python, Arcade, and the Tiled Map Editor.
If you aren’t familiar with programming in Python, check out https://learn.arcade.academy
If you aren’t familiar with the Arcade library, work through the Simple Platformer.
If you aren’t familiar with the Tiled Map Editor, the Simple Platformer also introduces how to create a map with the Tiled Map Editor.
Common Issues¶
There are a few items with the Pymunk physics engine that should be pointed out before you get started:
Object overlap - A fast moving object is allowed to overlap with the object it collides with, and Pymunk will push them apart later. See collision bias for more information.
Pass-through - A fast moving object can pass through another object if its speed is so quick it never overlaps the other object between frames. See object tunneling.
When stepping the physics engine forward in time, the default is to move forward 1/60th of a second. Whatever increment is picked, increments should always be kept the same. Don’t use the variable delta_time from the
update
method as a unit, or results will be unstable and unpredictable. For a more accurate simulation, you can step forward 1/120th of a second twice per frame. This increases the time required, but takes more time to calculate.A sprite moving across a floor made up of many rectangles can get “caught” on the edges. The corner of the player sprite can get caught the corner of the floor sprite. To get around this, make sure the hit box for the bottom of the player sprite is rounded. Also, look into the possibility of merging horizontal rows of sprites.
Open a Window¶
To begin with, let’s start with a program that will use Arcade to open a blank window. It also has stubs for methods we’ll fill in later. Try this code and make sure you can run it. It should pop open a black window.
1"""
2Example of Pymunk Physics Engine Platformer
3"""
4import arcade
5
6SCREEN_TITLE = "PyMunk Platformer"
7
8# Size of screen to show, in pixels
9SCREEN_WIDTH = 800
10SCREEN_HEIGHT = 600
11
12
13class GameWindow(arcade.Window):
14 """ Main Window """
15
16 def __init__(self, width, height, title):
17 """ Create the variables """
18
19 # Init the parent class
20 super().__init__(width, height, title)
21
22 def setup(self):
23 """ Set up everything with the game """
24 pass
25
26 def on_key_press(self, key, modifiers):
27 """Called whenever a key is pressed. """
28 pass
29
30 def on_key_release(self, key, modifiers):
31 """Called when the user releases a key. """
32 pass
33
34 def on_update(self, delta_time):
35 """ Movement and game logic """
36 pass
37
38 def on_draw(self):
39 """ Draw everything """
40 arcade.start_render()
41
42def main():
43 """ Main method """
44 window = GameWindow(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
45 window.setup()
46 arcade.run()
47
48
49if __name__ == "__main__":
50 main()
Create Constants¶
Now let’s set up the import
statements, and define the constants we are going
to use. In this case, we’ve got sprite tiles that are 128x128 pixels. They are
scaled down to 50% of the width and 50% of the height (scale of 0.5). The screen
size is set to 25x15 grid.
To keep things simple, this example will not scroll the screen with the player. See Simple Platformer or Move with a Scrolling Screen.
When you run this program, the screen should be larger.
1"""
2Example of Pymunk Physics Engine Platformer
3"""
4import math
5from typing import Optional
6import arcade
7
8SCREEN_TITLE = "PyMunk Platformer"
9
10# How big are our image tiles?
11SPRITE_IMAGE_SIZE = 128
12
13# Scale sprites up or down
14SPRITE_SCALING_PLAYER = 0.5
15SPRITE_SCALING_TILES = 0.5
16
17# Scaled sprite size for tiles
18SPRITE_SIZE = int(SPRITE_IMAGE_SIZE * SPRITE_SCALING_PLAYER)
19
20# Size of grid to show on screen, in number of tiles
21SCREEN_GRID_WIDTH = 25
22SCREEN_GRID_HEIGHT = 15
23
24# Size of screen to show, in pixels
25SCREEN_WIDTH = SPRITE_SIZE * SCREEN_GRID_WIDTH
26SCREEN_HEIGHT = SPRITE_SIZE * SCREEN_GRID_HEIGHT
27
28
29class GameWindow(arcade.Window):
Create Instance Variables¶
Next, let’s create instance variables we are going to use, and set a background
color that’s green: arcade.color.AMAZON
If you aren’t familiar with type-casting on Python, you might not be familiar with lines of code like this:
self.player_list: Optional[arcade.SpriteList] = None
This means the player_list
attribute is going to be an instance of
SpriteList
or None
. If you don’t want to mess with typing, then
this code also works just as well:
self.player_list = None
Running this program should show the same window, but with a green background.
1class GameWindow(arcade.Window):
2 """ Main Window """
3
4 def __init__(self, width, height, title):
5 """ Create the variables """
6
7 # Init the parent class
8 super().__init__(width, height, title)
9
10 # Player sprite
11 self.player_sprite: Optional[arcade.Sprite] = None
12
13 # Sprite lists we need
14 self.player_list: Optional[arcade.SpriteList] = None
15 self.wall_list: Optional[arcade.SpriteList] = None
16 self.bullet_list: Optional[arcade.SpriteList] = None
17 self.item_list: Optional[arcade.SpriteList] = None
18
19 # Track the current state of what key is pressed
20 self.left_pressed: bool = False
21 self.right_pressed: bool = False
22
23 # Set background color
24 arcade.set_background_color(arcade.color.AMAZON)
Load and Display Map¶
To get started, create a map with the Tiled Map Editor. Place items that you don’t want to move, and to act as platforms in a layer named “Platforms”. Place items you want to push around in a layer called “Dynamic Items”. Name the file “pymunk_test_map.tmx” and place in the exact same directory as your code.

If you aren’t sure how to use the Tiled Map Editor, see Step 8 - Use a Map Editor.
Now, in the setup
function, we are going add code to:
Create instances of
SpriteList
for each group of sprites we are doing to work with.Create the player sprite.
Read in the tiled map.
Make sprites from the layers in the tiled map.
Note
When making sprites from the tiled map layer, the name of the layer you load must match exactly with the layer created in the tiled map editor. It is case-sensitive.
1 def setup(self):
2 """ Set up everything with the game """
3
4 # Create the sprite lists
5 self.player_list = arcade.SpriteList()
6 self.bullet_list = arcade.SpriteList()
7
8 # Read in the tiled map
9 map_name = "pymunk_test_map.tmx"
10 my_map = arcade.tilemap.read_tmx(map_name)
11
12 # Read in the map layers
13 self.wall_list = arcade.tilemap.process_layer(my_map, 'Platforms', SPRITE_SCALING_TILES)
14 self.item_list = arcade.tilemap.process_layer(my_map, 'Dynamic Items', SPRITE_SCALING_TILES)
15
16 # Create player sprite
17 self.player_sprite = arcade.Sprite(":resources:images/animated_characters/female_person/femalePerson_idle.png",
18 SPRITE_SCALING_PLAYER)
19 # Set player location
20 grid_x = 1
21 grid_y = 1
22 self.player_sprite.center_x = SPRITE_SIZE * grid_x + SPRITE_SIZE / 2
23 self.player_sprite.center_y = SPRITE_SIZE * grid_y + SPRITE_SIZE / 2
24 # Add to player sprite list
25 self.player_list.append(self.player_sprite)
There’s no point in having sprites if we don’t draw them, so in the on_draw
method, let’s draw out sprite lists.
1 def on_draw(self):
2 """ Draw everything """
3 arcade.start_render()
4 self.wall_list.draw()
5 self.bullet_list.draw()
6 self.item_list.draw()
7 self.player_list.draw()
With the additions in the program below, running your program should show the tiled map you created:

Add Physics Engine¶
The next step is to add in the physics engine.
First, add some constants for our physics. Here we are setting:
A constant for the force of gravity
Values for “damping”. A damping of 1.0 will cause an item to lose all it’s velocity once a force no longer applies to it. A damping of 0.5 causes 50% of speed to be lost in 1 second. A value of 0 is free-fall.
Values for friction. 0.0 is ice, 1.0 is like rubber.
Mass. Item default to 1. We make the player 2, so she can push items around easier.
Limits are the players horizontal and vertical speed. It is easier to play if the player is limited to a constant speed. And more realistic, because they aren’t on wheels.
1# --- Physics forces. Higher number, faster accelerating.
2
3# Gravity
4GRAVITY = 1500
5
6# Damping - Amount of speed lost per second
7DEFAULT_DAMPING = 1.0
8PLAYER_DAMPING = 0.4
9
10# Friction between objects
11PLAYER_FRICTION = 1.0
12WALL_FRICTION = 0.7
13DYNAMIC_ITEM_FRICTION = 0.6
14
15# Mass (defaults to 1)
16PLAYER_MASS = 2.0
17
18# Keep player from going too fast
19PLAYER_MAX_HORIZONTAL_SPEED = 450
20PLAYER_MAX_VERTICAL_SPEED = 1600
Second, add the following attributer in the __init__
method to hold our
physics engine:
1 # Physics engine
2 self.physics_engine = Optional[arcade.PymunkPhysicsEngine]
Third, in the setup
method we create the physics engine and add the sprites.
The player, walls, and dynamic items all have different properties so they are
added individually.
1 # --- Pymunk Physics Engine Setup ---
2
3 # The default damping for every object controls the percent of velocity
4 # the object will keep each second. A value of 1.0 is no speed loss,
5 # 0.9 is 10% per second, 0.1 is 90% per second.
6 # For top-down games, this is basically the friction for moving objects.
7 # For platformers with gravity, this should probably be set to 1.0.
8 # Default value is 1.0 if not specified.
9 damping = DEFAULT_DAMPING
10
11 # Set the gravity. (0, 0) is good for outer space and top-down.
12 gravity = (0, -GRAVITY)
13
14 # Create the physics engine
15 self.physics_engine = arcade.PymunkPhysicsEngine(damping=damping,
16 gravity=gravity)
17
18 # Add the player.
19 # For the player, we set the damping to a lower value, which increases
20 # the damping rate. This prevents the character from traveling too far
21 # after the player lets off the movement keys.
22 # Setting the moment to PymunkPhysicsEngine.MOMENT_INF prevents it from
23 # rotating.
24 # Friction normally goes between 0 (no friction) and 1.0 (high friction)
25 # Friction is between two objects in contact. It is important to remember
26 # in top-down games that friction moving along the 'floor' is controlled
27 # by damping.
28 self.physics_engine.add_sprite(self.player_sprite,
29 friction=PLAYER_FRICTION,
30 mass=PLAYER_MASS,
31 moment=arcade.PymunkPhysicsEngine.MOMENT_INF,
32 collision_type="player",
33 max_horizontal_velocity=PLAYER_MAX_HORIZONTAL_SPEED,
34 max_vertical_velocity=PLAYER_MAX_VERTICAL_SPEED)
35
36 # Create the walls.
37 # By setting the body type to PymunkPhysicsEngine.STATIC the walls can't
38 # move.
39 # Movable objects that respond to forces are PymunkPhysicsEngine.DYNAMIC
40 # PymunkPhysicsEngine.KINEMATIC objects will move, but are assumed to be
41 # repositioned by code and don't respond to physics forces.
42 # Dynamic is default.
43 self.physics_engine.add_sprite_list(self.wall_list,
44 friction=WALL_FRICTION,
45 collision_type="wall",
46 body_type=arcade.PymunkPhysicsEngine.STATIC)
47
48 # Create the items
49 self.physics_engine.add_sprite_list(self.item_list,
50 friction=DYNAMIC_ITEM_FRICTION,
51 collision_type="item")
Fourth, in the on_update
method we call the physics engine’s step
method.
1 def on_update(self, delta_time):
2 """ Movement and game logic """
3 self.physics_engine.step()
If you run the program, and you have dynamic items that are up in the air, you should see them fall when the game starts.
Add Player Movement¶
Next step is to get the player moving. In this section we’ll cover how to move left and right. In the next section we’ll show how to jump.
The force that we will move the player is defined as PLAYER_MOVE_FORCE_ON_GROUND
.
We’ll apply a different force later, if the player happens to be airborne.
1# Force applied while on the ground
2PLAYER_MOVE_FORCE_ON_GROUND = 8000
3
4class GameWindow(arcade.Window):
5 """ Main Window """
6
7 def __init__(self, width, height, title):
8 """ Create the variables """
9
10 # Init the parent class
11 super().__init__(width, height, title)
12
13 # Player sprite
14 self.player_sprite: Optional[arcade.Sprite] = None
15
16 # Sprite lists we need
17 self.player_list: Optional[arcade.SpriteList] = None
18 self.wall_list: Optional[arcade.SpriteList] = None
19 self.bullet_list: Optional[arcade.SpriteList] = None
20 self.item_list: Optional[arcade.SpriteList] = None
21
22 # Track the current state of what key is pressed
23 self.left_pressed: bool = False
24 self.right_pressed: bool = False
We need to track if the left/right keys are held down. To do this we define
instance variables left_pressed
and right_pressed
. These are set to
appropriate values in the key press and release handlers.
1 def on_key_press(self, key, modifiers):
2 """Called whenever a key is pressed. """
3
4 if key == arcade.key.LEFT:
5 self.left_pressed = True
6 elif key == arcade.key.RIGHT:
7 self.right_pressed = True
8
9 def on_key_release(self, key, modifiers):
10 """Called when the user releases a key. """
11
12 if key == arcade.key.LEFT:
13 self.left_pressed = False
14 elif key == arcade.key.RIGHT:
15 self.right_pressed = False
Finally, we need to apply the correct force in on_update
. Force is specified
in a tuple with horizontal force first, and vertical force second.
We also set the friction when we are moving to zero, and when we are not moving to 1. This is important to get realistic movement.
1 def on_update(self, delta_time):
2 """ Movement and game logic """
3
4 # Update player forces based on keys pressed
5 if self.left_pressed and not self.right_pressed:
6 # Create a force to the left. Apply it.
7 force = (-PLAYER_MOVE_FORCE_ON_GROUND, 0)
8 self.physics_engine.apply_force(self.player_sprite, force)
9 # Set friction to zero for the player while moving
10 self.physics_engine.set_friction(self.player_sprite, 0)
11 elif self.right_pressed and not self.left_pressed:
12 # Create a force to the right. Apply it.
13 force = (PLAYER_MOVE_FORCE_ON_GROUND, 0)
14 self.physics_engine.apply_force(self.player_sprite, force)
15 # Set friction to zero for the player while moving
16 self.physics_engine.set_friction(self.player_sprite, 0)
17 else:
18 # Player's feet are not moving. Therefore up the friction so we stop.
19 self.physics_engine.set_friction(self.player_sprite, 1.0)
20
21 # Move items in the physics engine
22 self.physics_engine.step()
Add Player Jumping¶
To get the player to jump we need to:
Make sure the player is on the ground
Apply an impulse force to the player upward
Change the left/right force to the player while they are in the air.
We can see if a sprite has a sprite below it with the is_on_ground
function.
Otherwise we’ll be able to jump while we are in the air.
(Double-jumps would allow this once.)
If we don’t allow the player to move left-right while in the air, they player will be very hard to control. If we allow them to move left/right with the same force as on the ground, that’s typically too much. So we’ve got a different left/right force depending if we are in the air or not.
For the code changes, first we’ll define some constants:
1# Force applied when moving left/right in the air
2PLAYER_MOVE_FORCE_IN_AIR = 900
3
4# Strength of a jump
5PLAYER_JUMP_IMPULSE = 1800
We’ll add logic that will apply the impulse force when we jump:
1 def on_key_press(self, key, modifiers):
2 """Called whenever a key is pressed. """
3
4 if key == arcade.key.LEFT:
5 self.left_pressed = True
6 elif key == arcade.key.RIGHT:
7 self.right_pressed = True
8 elif key == arcade.key.UP:
9 # find out if player is standing on ground
10 if self.physics_engine.is_on_ground(self.player_sprite):
11 # She is! Go ahead and jump
12 impulse = (0, PLAYER_JUMP_IMPULSE)
13 self.physics_engine.apply_impulse(self.player_sprite, impulse)
Then we will adjust the left/right force depending on if we are grounded or not:
1 """ Movement and game logic """
2
3 is_on_ground = self.physics_engine.is_on_ground(self.player_sprite)
4 # Update player forces based on keys pressed
5 if self.left_pressed and not self.right_pressed:
6 # Create a force to the left. Apply it.
7 if is_on_ground:
8 force = (-PLAYER_MOVE_FORCE_ON_GROUND, 0)
9 else:
10 force = (-PLAYER_MOVE_FORCE_IN_AIR, 0)
11 self.physics_engine.apply_force(self.player_sprite, force)
12 # Set friction to zero for the player while moving
13 self.physics_engine.set_friction(self.player_sprite, 0)
14 elif self.right_pressed and not self.left_pressed:
15 # Create a force to the right. Apply it.
16 if is_on_ground:
17 force = (PLAYER_MOVE_FORCE_ON_GROUND, 0)
18 else:
19 force = (PLAYER_MOVE_FORCE_IN_AIR, 0)
20 self.physics_engine.apply_force(self.player_sprite, force)
21 # Set friction to zero for the player while moving
22 self.physics_engine.set_friction(self.player_sprite, 0)
23 else:
24 # Player's feet are not moving. Therefore up the friction so we stop.
25 self.physics_engine.set_friction(self.player_sprite, 1.0)
26
27 # Move items in the physics engine
Add Player Animation¶
To create a player animation, we make a custom child class of Sprite
.
We load each frame of animation that we need, including a mirror image of it.
We will flip the player to face left or right. If the player is in the air, we’ll also change between a jump up and a falling graphics.
Because the physics engine works with small floating point numbers, it often flips above and below zero by small amounts. It is a good idea not to change the animation as the x and y float around zero. For that reason, in this code we have a “dead zone.” We don’t change the animation until it gets outside of that zone.
We also need to control how far the player moves before we change the walking animation, so that the feet appear in-sync with the ground.
1# Close enough to not-moving to have the animation go to idle.
2DEAD_ZONE = 0.1
3
4# Constants used to track if the player is facing left or right
5RIGHT_FACING = 0
6LEFT_FACING = 1
7
8# How many pixels to move before we change the texture in the walking animation
9DISTANCE_TO_CHANGE_TEXTURE = 20
Next, we create a Player
class that is a child to arcade.Sprite
. This
class will update the player animation.
The __init__
method loads all of the textures. Here we use Kenney.nl’s
Toon Characters 1 pack.
It has six different characters you can choose from with the same layout, so
it makes changing as simple as changing which line is enabled. There are
eight textures for walking, and textures for idle, jumping, and falling.
As the character can face left or right, we use arcade.load_texture_pair
which will load both a regular image, and one that’s mirrored.
For the multi-frame walking animation, we use an “odometer.” We need to move a certain number of pixels before changing the animation. If this value is too small our character moves her legs like Fred Flintstone, too large and it looks like you are ice skating. We keep track of the index of our current texture, 0-7 since there are eight of them.
Any sprite moved by the Pymunk engine will have its pymunk_moved
method
called. This can be used to update the animation.
1class PlayerSprite(arcade.Sprite):
2 """ Player Sprite """
3 def __init__(self):
4 """ Init """
5 # Let parent initialize
6 super().__init__()
7
8 # Set our scale
9 self.scale = SPRITE_SCALING_PLAYER
10
11 # Images from Kenney.nl's Character pack
12 # main_path = ":resources:images/animated_characters/female_adventurer/femaleAdventurer"
13 main_path = ":resources:images/animated_characters/female_person/femalePerson"
14 # main_path = ":resources:images/animated_characters/male_person/malePerson"
15 # main_path = ":resources:images/animated_characters/male_adventurer/maleAdventurer"
16 # main_path = ":resources:images/animated_characters/zombie/zombie"
17 # main_path = ":resources:images/animated_characters/robot/robot"
18
19 # Load textures for idle standing
20 self.idle_texture_pair = arcade.load_texture_pair(f"{main_path}_idle.png")
21 self.jump_texture_pair = arcade.load_texture_pair(f"{main_path}_jump.png")
22 self.fall_texture_pair = arcade.load_texture_pair(f"{main_path}_fall.png")
23
24 # Load textures for walking
25 self.walk_textures = []
26 for i in range(8):
27 texture = arcade.load_texture_pair(f"{main_path}_walk{i}.png")
28 self.walk_textures.append(texture)
29
30 # Set the initial texture
31 self.texture = self.idle_texture_pair[0]
32
33 # Hit box will be set based on the first image used.
34 self.hit_box = self.texture.hit_box_points
35
36 # Default to face-right
37 self.character_face_direction = RIGHT_FACING
38
39 # Index of our current texture
40 self.cur_texture = 0
41
42 # How far have we traveled horizontally since changing the texture
43 self.x_odometer = 0
44
45 def pymunk_moved(self, physics_engine, dx, dy, d_angle):
46 """ Handle being moved by the pymunk engine """
47 # Figure out if we need to face left or right
48 if dx < -DEAD_ZONE and self.character_face_direction == RIGHT_FACING:
49 self.character_face_direction = LEFT_FACING
50 elif dx > DEAD_ZONE and self.character_face_direction == LEFT_FACING:
51 self.character_face_direction = RIGHT_FACING
52
53 # Are we on the ground?
54 is_on_ground = physics_engine.is_on_ground(self)
55
56 # Add to the odometer how far we've moved
57 self.x_odometer += dx
58
59 # Jumping animation
60 if not is_on_ground:
61 if dy > DEAD_ZONE:
62 self.texture = self.jump_texture_pair[self.character_face_direction]
63 return
64 elif dy < -DEAD_ZONE:
65 self.texture = self.fall_texture_pair[self.character_face_direction]
66 return
67
68 # Idle animation
69 if abs(dx) <= DEAD_ZONE:
70 self.texture = self.idle_texture_pair[self.character_face_direction]
71 return
72
73 # Have we moved far enough to change the texture?
74 if abs(self.x_odometer) > DISTANCE_TO_CHANGE_TEXTURE:
75
76 # Reset the odometer
77 self.x_odometer = 0
78
79 # Advance the walking animation
80 self.cur_texture += 1
81 if self.cur_texture > 7:
82 self.cur_texture = 0
83 self.texture = self.walk_textures[self.cur_texture][self.character_face_direction]
Important! At this point, we are still creating an instance of arcade.Sprite
and not PlayerSprite
. We need to go back to the setup
method and
replace the line that creates the player
instance with:
# Create player sprite
self.player_sprite = PlayerSprite()
A really common mistake I’ve seen programmers make (and made myself) is to forget that last part. Then you can spend a lot of time looking at the player class when the error is in the setup.
We also need to go back and change the data type for the player sprite attribute
in our __init__
method:
# Player sprite
self.player_sprite: Optional[PlayerSprite] = None
Shoot Bullets¶
Getting the player to shoot something can add a lot to our game. To begin with we’ll define a few constants to use. How much force to shoot the bullet with, the bullet’s mass, and the gravity to use for the bullet.
If we use the same gravity for the bullet as everything else, it tends to drop too fast. We could set this to zero if we wanted it to not drop at all.
1# How much force to put on the bullet
2BULLET_MOVE_FORCE = 4500
3
4# Mass of the bullet
5BULLET_MASS = 0.1
6
7# Make bullet less affected by gravity
8BULLET_GRAVITY = 300
Next, we’ll put in a mouse press handler to put in the bullet shooting code.
We need to:
Create the bullet sprite
We need to calculate the angle from the player to the mouse click
Create the bullet away from the player in the proper direction, as spawning it inside the player will confuse the physics engine
Add the bullet to the physics engine
Apply the force to the bullet to make if move. Note that as we angled the bullet we don’t need to angle the force.
Warning
Does your platformer scroll?
If your window scrolls, you need to add in the coordinate off-set or else the angle calculation will be incorrect.
Warning
Bullets don’t disappear yet!
If the bullet flies off-screen, it doesn’t go away and the physics engine still has to track it.
1 def on_mouse_press(self, x, y, button, modifiers):
2 """ Called whenever the mouse button is clicked. """
3
4 bullet = arcade.SpriteSolidColor(20, 5, arcade.color.DARK_YELLOW)
5 self.bullet_list.append(bullet)
6
7 # Position the bullet at the player's current location
8 start_x = self.player_sprite.center_x
9 start_y = self.player_sprite.center_y
10 bullet.position = self.player_sprite.position
11
12 # Get from the mouse the destination location for the bullet
13 # IMPORTANT! If you have a scrolling screen, you will also need
14 # to add in self.view_bottom and self.view_left.
15 dest_x = x
16 dest_y = y
17
18 # Do math to calculate how to get the bullet to the destination.
19 # Calculation the angle in radians between the start points
20 # and end points. This is the angle the bullet will travel.
21 x_diff = dest_x - start_x
22 y_diff = dest_y - start_y
23 angle = math.atan2(y_diff, x_diff)
24
25 # What is the 1/2 size of this sprite, so we can figure out how far
26 # away to spawn the bullet
27 size = max(self.player_sprite.width, self.player_sprite.height) / 2
28
29 # Use angle to to spawn bullet away from player in proper direction
30 bullet.center_x += size * math.cos(angle)
31 bullet.center_y += size * math.sin(angle)
32
33 # Set angle of bullet
34 bullet.angle = math.degrees(angle)
35
36 # Gravity to use for the bullet
37 # If we don't use custom gravity, bullet drops too fast, or we have
38 # to make it go too fast.
39 # Force is in relation to bullet's angle.
40 bullet_gravity = (0, -BULLET_GRAVITY)
41
42 # Add the sprite. This needs to be done AFTER setting the fields above.
43 self.physics_engine.add_sprite(bullet,
44 mass=BULLET_MASS,
45 damping=1.0,
46 friction=0.6,
47 collision_type="bullet",
48 gravity=bullet_gravity,
49 elasticity=0.9)
50
51 # Add force to bullet
52 force = (BULLET_MOVE_FORCE, 0)
53 self.physics_engine.apply_force(bullet, force)
Destroy Bullets and Items¶
This section has two goals:
Get rid of the bullet if it flies off-screen
Handle collisions of the bullet and other items
Destroy Bullet If It Goes Off-Screen¶
First, we’ll create a custom bullet class. This class will define the
pymunk_moved
method, and check our location each time the bullet moves.
If our y value is too low, we’ll remove the bullet.
1class BulletSprite(arcade.SpriteSolidColor):
2 """ Bullet Sprite """
3 def pymunk_moved(self, physics_engine, dx, dy, d_angle):
4 """ Handle when the sprite is moved by the physics engine. """
5 # If the bullet falls below the screen, remove it
6 if self.center_y < -100:
7 self.remove_from_sprite_lists()
And, of course, once we create the bullet we have to update our code to use
it instead of the plain arcade.Sprite
class.
1 def on_mouse_press(self, x, y, button, modifiers):
2 """ Called whenever the mouse button is clicked. """
3
4 bullet = BulletSprite(20, 5, arcade.color.DARK_YELLOW)
5 self.bullet_list.append(bullet)
Handle Collisions¶
To handle collisions, we can add custom collision handler call-backs. If you’ll remember when we added items to the physics engine, we gave each item a collision type, such as “wall” or “bullet” or “item”. We can write a function and register it to handle all bullet/wall collisions.
In this case, bullets that hit a wall go away. Bullets that hit items cause both the item and the bullet to go away. We could also add code to track damage to a sprite, only removing it after so much damage was applied. Even changing the texture depending on its health.
1 def wall_hit_handler(bullet_sprite, _wall_sprite, _arbiter, _space, _data):
2 """ Called for bullet/wall collision """
3 bullet_sprite.remove_from_sprite_lists()
4
5 self.physics_engine.add_collision_handler("bullet", "wall", post_handler=wall_hit_handler)
6
7 def item_hit_handler(bullet_sprite, item_sprite, _arbiter, _space, _data):
8 """ Called for bullet/wall collision """
9 bullet_sprite.remove_from_sprite_lists()
10 item_sprite.remove_from_sprite_lists()
11
12 self.physics_engine.add_collision_handler("bullet", "item", post_handler=item_hit_handler)
Add Moving Platforms¶
We can add support for moving platforms. Platforms can be added in an object layer. An object layer allows platforms to be placed anywhere, and not just on exact grid locations. Object layers also allow us to add custom properties for each tile we place.

Adding an object layer.¶
Once we have the tile placed, we can add custom properties for it. Click the ‘+’ icon and add properties for all or some of:
change_x
change_y
left_boundary
right_boundary
top_boundary
bottom_boundary
If these are named exact matches, they’ll automatically copy their values into the sprite attributes of the same name.

Adding custom properties.¶
Now we need to update our code. In GameWindow.__init__
add a line to create
an attribute for moving_sprites_list
:
self.moving_sprites_list: Optional[arcade.SpriteList] = None
In the setup
method, load in the sprite list from the tmx layer.
self.moving_sprites_list = arcade.tilemap.process_layer(my_map,
'Moving Platforms',
SPRITE_SCALING_TILES)
Also in the setup
method, we need to add these sprites to the physics engine.
In this case we’ll add the sprites as KINEMATIC
. Static sprites don’t move.
Dynamic sprites move, and can have forces applied to them by other objects.
Kinematic sprites do move, but aren’t affected by other objects.
# Add kinematic sprites
self.physics_engine.add_sprite_list(self.moving_sprites_list,
body_type=arcade.PymunkPhysicsEngine.KINEMATIC)
We need to draw the moving platform sprites. After adding this line, you should be able to run the program and see the sprites from this layer, even if they don’t move yet.
1 def on_draw(self):
2 """ Draw everything """
3 arcade.start_render()
4 self.wall_list.draw()
5 self.moving_sprites_list.draw()
6 self.bullet_list.draw()
7 self.item_list.draw()
8 self.player_list.draw()
Next up, we need to get the sprites moving. First, we’ll check to see if there are any boundaries set, and if we need to reverse our direction.
After that we’ll create a velocity vector. Velocity is in pixels per second. In this case, I’m assuming the user set the velocity in pixels per frame in Tiled instead, so we’ll convert.
Warning
Changing center_x and center_y will not move the sprite. If you want to change
a sprite’s position, use the physics engine’s set_position
method.
Also, setting an item’s position “teleports” it there. The physics engine will happily move the object right into another object. Setting the item’s velocity instead will cause the physics engine to move the item, pushing any dynamic items out of the way.
# For each moving sprite, see if we've reached a boundary and need to
# reverse course.
for moving_sprite in self.moving_sprites_list:
if moving_sprite.boundary_right and \
moving_sprite.change_x > 0 and \
moving_sprite.right > moving_sprite.boundary_right:
moving_sprite.change_x *= -1
elif moving_sprite.boundary_left and \
moving_sprite.change_x < 0 and \
moving_sprite.left > moving_sprite.boundary_left:
moving_sprite.change_x *= -1
if moving_sprite.boundary_top and \
moving_sprite.change_y > 0 and \
moving_sprite.top > moving_sprite.boundary_top:
moving_sprite.change_y *= -1
elif moving_sprite.boundary_bottom and \
moving_sprite.change_y < 0 and \
moving_sprite.bottom < moving_sprite.boundary_bottom:
moving_sprite.change_y *= -1
# Figure out and set our moving platform velocity.
# Pymunk uses velocity is in pixels per second. If we instead have
# pixels per frame, we need to convert.
velocity = (moving_sprite.change_x * 1 / delta_time, moving_sprite.change_y * 1 / delta_time)
Add Ladders¶
The first step to adding ladders to our platformer is modify the __init__
to track some more items:
Have a reference to a list of ladder sprites
Add textures for a climbing animation
Keep track of our movement in the y direction
Add a boolean to track if we are on/off a ladder
1 def __init__(self,
2 ladder_list: arcade.SpriteList,
3 hit_box_algorithm):
4 """ Init """
5 # Let parent initialize
6 super().__init__()
7
8 # Set our scale
9 self.scale = SPRITE_SCALING_PLAYER
10
11 # Images from Kenney.nl's Character pack
12 # main_path = ":resources:images/animated_characters/female_adventurer/femaleAdventurer"
13 main_path = ":resources:images/animated_characters/female_person/femalePerson"
14 # main_path = ":resources:images/animated_characters/male_person/malePerson"
15 # main_path = ":resources:images/animated_characters/male_adventurer/maleAdventurer"
16 # main_path = ":resources:images/animated_characters/zombie/zombie"
17 # main_path = ":resources:images/animated_characters/robot/robot"
18
19 # Load textures for idle standing
20 self.idle_texture_pair = arcade.load_texture_pair(f"{main_path}_idle.png",
21 hit_box_algorithm=hit_box_algorithm)
22 self.jump_texture_pair = arcade.load_texture_pair(f"{main_path}_jump.png")
23 self.fall_texture_pair = arcade.load_texture_pair(f"{main_path}_fall.png")
24
25 # Load textures for walking
26 self.walk_textures = []
27 for i in range(8):
28 texture = arcade.load_texture_pair(f"{main_path}_walk{i}.png")
29 self.walk_textures.append(texture)
30
31 # Load textures for climbing
32 self.climbing_textures = []
33 texture = arcade.load_texture(f"{main_path}_climb0.png")
34 self.climbing_textures.append(texture)
35 texture = arcade.load_texture(f"{main_path}_climb1.png")
36 self.climbing_textures.append(texture)
37
38 # Set the initial texture
39 self.texture = self.idle_texture_pair[0]
40
41 # Hit box will be set based on the first image used.
42 self.hit_box = self.texture.hit_box_points
43
44 # Default to face-right
45 self.character_face_direction = RIGHT_FACING
46
47 # Index of our current texture
48 self.cur_texture = 0
49
50 # How far have we traveled horizontally since changing the texture
51 self.x_odometer = 0
52 self.y_odometer = 0
53
54 self.ladder_list = ladder_list
55 self.is_on_ladder = False
Next, in our pymunk_moved
method we need to change physics when we are
on a ladder, and to update our player texture.
When we are on a ladder, we’ll turn off gravity, turn up damping, and turn down our max vertical velocity. If we are off the ladder, reset those attributes.
When we are on a ladder, but not on the ground, we’ll alternate between a couple climbing textures.
1 def pymunk_moved(self, physics_engine, dx, dy, d_angle):
2 """ Handle being moved by the pymunk engine """
3 # Figure out if we need to face left or right
4 if dx < -DEAD_ZONE and self.character_face_direction == RIGHT_FACING:
5 self.character_face_direction = LEFT_FACING
6 elif dx > DEAD_ZONE and self.character_face_direction == LEFT_FACING:
7 self.character_face_direction = RIGHT_FACING
8
9 # Are we on the ground?
10 is_on_ground = physics_engine.is_on_ground(self)
11
12 # Are we on a ladder?
13 if len(arcade.check_for_collision_with_list(self, self.ladder_list)) > 0:
14 if not self.is_on_ladder:
15 self.is_on_ladder = True
16 self.pymunk.gravity = (0, 0)
17 self.pymunk.damping = 0.0001
18 self.pymunk.max_vertical_velocity = PLAYER_MAX_HORIZONTAL_SPEED
19 else:
20 if self.is_on_ladder:
21 self.pymunk.damping = 1.0
22 self.pymunk.max_vertical_velocity = PLAYER_MAX_VERTICAL_SPEED
23 self.is_on_ladder = False
24 self.pymunk.gravity = None
25
26 # Add to the odometer how far we've moved
27 self.x_odometer += dx
28 self.y_odometer += dy
29
30 if self.is_on_ladder and not is_on_ground:
31 # Have we moved far enough to change the texture?
32 if abs(self.y_odometer) > DISTANCE_TO_CHANGE_TEXTURE:
33
34 # Reset the odometer
35 self.y_odometer = 0
36
37 # Advance the walking animation
38 self.cur_texture += 1
39
40 if self.cur_texture > 1:
41 self.cur_texture = 0
42 self.texture = self.climbing_textures[self.cur_texture]
43 return
44
45 # Jumping animation
46 if not is_on_ground:
47 if dy > DEAD_ZONE:
48 self.texture = self.jump_texture_pair[self.character_face_direction]
49 return
50 elif dy < -DEAD_ZONE:
51 self.texture = self.fall_texture_pair[self.character_face_direction]
52 return
53
54 # Idle animation
55 if abs(dx) <= DEAD_ZONE:
56 self.texture = self.idle_texture_pair[self.character_face_direction]
57 return
58
59 # Have we moved far enough to change the texture?
60 if abs(self.x_odometer) > DISTANCE_TO_CHANGE_TEXTURE:
61
62 # Reset the odometer
63 self.x_odometer = 0
64
65 # Advance the walking animation
66 self.cur_texture += 1
67 if self.cur_texture > 7:
68 self.cur_texture = 0
69 self.texture = self.walk_textures[self.cur_texture][self.character_face_direction]
Then we just need to add a few variables to the __init__
to track ladders:
1 def __init__(self, width, height, title):
2 """ Create the variables """
3
4 # Init the parent class
5 super().__init__(width, height, title)
6
7 # Player sprite
8 self.player_sprite: Optional[PlayerSprite] = None
9
10 # Sprite lists we need
11 self.player_list: Optional[arcade.SpriteList] = None
12 self.wall_list: Optional[arcade.SpriteList] = None
13 self.bullet_list: Optional[arcade.SpriteList] = None
14 self.item_list: Optional[arcade.SpriteList] = None
15 self.moving_sprites_list: Optional[arcade.SpriteList] = None
16 self.ladder_list: Optional[arcade.SpriteList] = None
17
18 # Track the current state of what key is pressed
19 self.left_pressed: bool = False
20 self.right_pressed: bool = False
21 self.up_pressed: bool = False
22 self.down_pressed: bool = False
23
24 # Physics engine
25 self.physics_engine = Optional[arcade.PymunkPhysicsEngine]
26
27 # Set background color
28 arcade.set_background_color(arcade.color.AMAZON)
Then load the ladder layer in setup
:
self.wall_list = arcade.tilemap.process_layer(my_map,
'Platforms',
SPRITE_SCALING_TILES,
hit_box_algorithm="Detailed")
Also, pass the ladder list to the player class:
'Dynamic Items',
SPRITE_SCALING_TILES,
Then change the jump button so that we don’t jump if we are on a ladder. Also, we want to track if the up key, or down key are pressed.
1 def on_key_press(self, key, modifiers):
2 """Called whenever a key is pressed. """
3
4 if key == arcade.key.LEFT:
5 self.left_pressed = True
6 elif key == arcade.key.RIGHT:
7 self.right_pressed = True
8 elif key == arcade.key.UP:
9 self.up_pressed = True
10 # find out if player is standing on ground, and not on a ladder
11 if self.physics_engine.is_on_ground(self.player_sprite) \
12 and not self.player_sprite.is_on_ladder:
13 # She is! Go ahead and jump
14 impulse = (0, PLAYER_JUMP_IMPULSE)
15 self.physics_engine.apply_impulse(self.player_sprite, impulse)
16 elif key == arcade.key.DOWN:
17 self.down_pressed = True
Add to the key up handler tracking for which key is pressed.
1 def on_key_release(self, key, modifiers):
2 """Called when the user releases a key. """
3
4 if key == arcade.key.LEFT:
5 self.left_pressed = False
6 elif key == arcade.key.RIGHT:
7 self.right_pressed = False
8 elif key == arcade.key.UP:
9 self.up_pressed = False
10 elif key == arcade.key.DOWN:
11 self.down_pressed = False
Next, change our update with new updates for the ladder.
1 friction=0.6,
2 collision_type="bullet",
3 gravity=bullet_gravity,
4 elasticity=0.9)
5
6 # Add force to bullet
7 force = (BULLET_MOVE_FORCE, 0)
8 self.physics_engine.apply_force(bullet, force)
9
10 def on_update(self, delta_time):
11 """ Movement and game logic """
12
13 is_on_ground = self.physics_engine.is_on_ground(self.player_sprite)
14 # Update player forces based on keys pressed
15 if self.left_pressed and not self.right_pressed:
16 # Create a force to the left. Apply it.
17 if is_on_ground or self.player_sprite.is_on_ladder:
18 force = (-PLAYER_MOVE_FORCE_ON_GROUND, 0)
19 else:
20 force = (-PLAYER_MOVE_FORCE_IN_AIR, 0)
21 self.physics_engine.apply_force(self.player_sprite, force)
22 # Set friction to zero for the player while moving
23 self.physics_engine.set_friction(self.player_sprite, 0)
24 elif self.right_pressed and not self.left_pressed:
25 # Create a force to the right. Apply it.
26 if is_on_ground or self.player_sprite.is_on_ladder:
27 force = (PLAYER_MOVE_FORCE_ON_GROUND, 0)
28 else:
29 force = (PLAYER_MOVE_FORCE_IN_AIR, 0)
30 self.physics_engine.apply_force(self.player_sprite, force)
31 # Set friction to zero for the player while moving
32 self.physics_engine.set_friction(self.player_sprite, 0)
33 elif self.up_pressed and not self.down_pressed:
34 # Create a force to the right. Apply it.
35 if self.player_sprite.is_on_ladder:
36 force = (0, PLAYER_MOVE_FORCE_ON_GROUND)
37 self.physics_engine.apply_force(self.player_sprite, force)
And, of course, don’t forget to draw the ladders:
1 def on_draw(self):
2 """ Draw everything """
3 arcade.start_render()
4 self.wall_list.draw()
5 self.ladder_list.draw()
6 self.moving_sprites_list.draw()
7 self.bullet_list.draw()
8 self.item_list.draw()
9 self.player_list.draw()
Add “Hit” Ability¶
(To be done)
Add “Grab” Ability¶
(To be done)
Add “Claw-Shot” Ability¶
(To be done)
Using Views for Start/End Screens¶
Views allow you to easily switch “views” for what you are showing on the window. You can use this to support adding screens such as:
Start screens
Instruction screens
Game over screens
Pause screens
The View
class is a lot like the Window
class that you are already used
to. The View
class has methods for on_update
and on_draw
just like
Window
. We can change the current view to quickly change the code that is
managing what is drawn on the window and handling user input.
If you know ahead of time you want to use views, you can build your code around the Instruction Screens and Game Over Screens. However, typically a programmer wants to add these items to a game that already exists.
This tutorial steps you through how to do just that.
Change Main Program to Use a View¶

First, we’ll start with a simple collect coins example: 01_views.py Full Listing
Then we’ll move our game into a game view. Take the code where we define our window class:
class MyGame(arcade.Window):
Change it to derive from arcade.View
instead of arcade.Window
.
I also suggest using “View” as part of the name:
class GameView(arcade.View):
This will require a couple other updates. The View
class does not control
the size of the window, so we’ll need to take that out of the call to the
parent class. Change:
super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
to:
super().__init__()
The Window
class still controls if the mouse is visible or not, so to hide
the mouse, we’ll need to use the window
attribute that is part of the View
class. Change:
self.set_mouse_visible(False)
to:
self.window.set_mouse_visible(False)
Now in the main
function, instead of just creating a window, we’ll create
a window, a view, and then show that view.
1def main():
2 """ Main method """
3
4 window = arcade.Window(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
5 start_view = GameView()
6 window.show_view(start_view)
7 start_view.setup()
8 arcade.run()
At this point, run your game and make sure that it still operates properly. It should run just like it did before, but now we are set up to add additional views.
02_views.py Full Listing ← Full listing of where we are right now
02_views.py Diff ← What we changed to get here
Add Instruction Screen¶

Now we are ready to add in our instruction screen as a view. Create a class for it:
class InstructionView(arcade.View):
Then we need to define the on_show
method that will be run once when we
switch to this view. In this case, we don’t need to do much, just set the
background color. If the game is one that scrolls, we’ll also need to reset
the viewport so that (0, 0) is back to the lower-left coordinate.
def on_show(self):
""" This is run once when we switch to this view """
arcade.set_background_color(arcade.csscolor.DARK_SLATE_BLUE)
# Reset the viewport, necessary if we have a scrolling game and we need
# to reset the viewport back to the start so we can see what we draw.
arcade.set_viewport(0, SCREEN_WIDTH - 1, 0, SCREEN_HEIGHT - 1)
The on_draw
method works just like the window class’s method, but it will
only be called when this view is active.
In this case, we’ll just draw some text for the instruction screen. Another alternative is to make a graphic in a paint program, and show that image. We’ll do that below where we show the Game Over screen.
def on_draw(self):
""" Draw this view """
arcade.start_render()
arcade.draw_text("Instructions Screen", SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2,
arcade.color.WHITE, font_size=50, anchor_x="center")
arcade.draw_text("Click to advance", SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2-75,
arcade.color.WHITE, font_size=20, anchor_x="center")
Then we’ll put in a method to respond to a mouse click. Here we’ll create our
GameView
and call the setup method.
def on_mouse_press(self, _x, _y, _button, _modifiers):
""" If the user presses the mouse button, start the game. """
game_view = GameView()
game_view.setup()
self.window.show_view(game_view)
Now we need to go back to the main
method. Instead of creating a GameView
it needs to now create an InstructionView
.
1def main():
2 """ Main method """
3
4 window = arcade.Window(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
5 start_view = InstructionView()
6 window.show_view(start_view)
7 arcade.run()
03_views.py Full Listing ← Full listing of where we are right now
03_views.py Diff ← What we changed to get here
Game Over Screen¶

Another way of doing instruction, pause, and game over screens is with a graphic.
In this example, we’ve created a separate image with the same size as our
window (800x600) and saved it as game_over.png
. You can use the Windows “Paint”
app or get an app for your Mac to make images in order to do this yourself.
The new GameOverView
view that we are adding loads in the game over screen
image as a texture in its __init__
. The on_draw
method draws that texture
to the screen. By using an image, we can fancy up the game over screen using an
image editor as much as we want, while keeping the code simple.
When the user clicks the mouse button, we just start the game over.
1class GameOverView(arcade.View):
2 """ View to show when game is over """
3
4 def __init__(self):
5 """ This is run once when we switch to this view """
6 super().__init__()
7 self.texture = arcade.load_texture("game_over.png")
8
9 # Reset the viewport, necessary if we have a scrolling game and we need
10 # to reset the viewport back to the start so we can see what we draw.
11 arcade.set_viewport(0, SCREEN_WIDTH - 1, 0, SCREEN_HEIGHT - 1)
12
13 def on_draw(self):
14 """ Draw this view """
15 arcade.start_render()
16 self.texture.draw_sized(SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2,
17 SCREEN_WIDTH, SCREEN_HEIGHT)
18
19 def on_mouse_press(self, _x, _y, _button, _modifiers):
20 """ If the user presses the mouse button, re-start the game. """
21 game_view = GameView()
22 game_view.setup()
23 self.window.show_view(game_view)
The last thing we need, is to trigger the “Game Over” view. In our GameView.on_update
method, we can check the list length. As soon as it hits zero, we’ll change
our view.
1 def on_update(self, delta_time):
2 """ Movement and game logic """
3
4 # Call update on all sprites (The sprites don't do much in this
5 # example though.)
6 self.coin_list.update()
7
8 # Generate a list of all sprites that collided with the player.
9 coins_hit_list = arcade.check_for_collision_with_list(self.player_sprite, self.coin_list)
10
11 # Loop through each colliding sprite, remove it, and add to the score.
12 for coin in coins_hit_list:
13 coin.remove_from_sprite_lists()
14 self.score += 1
15
16 # Check length of coin list. If it is zero, flip to the
17 # game over view.
18 if len(self.coin_list) == 0:
19 view = GameOverView()
20 self.window.show_view(view)
04_views.py Full Listing ← Full listing of where we are right now
04_views.py Diff ← What we changed to get here
Solitaire¶

This solitaire tutorial takes you though the basics of creating a card game, and doing extensive drag/drop work.
Open a Window¶

To begin with, let’s start with a program that will use Arcade to open a blank window. The listing below also has stubs for methods we’ll fill in later.
Get started with this code and make sure you can run it. It should pop open a green window.
1"""
2Solitaire clone.
3"""
4import arcade
5
6# Screen title and size
7SCREEN_WIDTH = 1024
8SCREEN_HEIGHT = 768
9SCREEN_TITLE = "Drag and Drop Cards"
10
11class MyGame(arcade.Window):
12 """ Main application class. """
13
14 def __init__(self):
15 super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
16
17 arcade.set_background_color(arcade.color.AMAZON)
18
19 def setup(self):
20 """ Set up the game here. Call this function to restart the game. """
21 pass
22
23 def on_draw(self):
24 """ Render the screen. """
25 # Clear the screen
26 arcade.start_render()
27
28 def on_mouse_press(self, x, y, button, key_modifiers):
29 """ Called when the user presses a mouse button. """
30 pass
31
32 def on_mouse_release(self, x: float, y: float, button: int,
33 modifiers: int):
34 """ Called when the user presses a mouse button. """
35 pass
36
37 def on_mouse_motion(self, x: float, y: float, dx: float, dy: float):
38 """ User moves mouse """
39 pass
40
41
42def main():
43 """ Main method """
44 window = MyGame()
45 window.setup()
46 arcade.run()
47
48
49if __name__ == "__main__":
50 main()
Create Card Sprites¶
Our next step is the create a bunch of sprites, one for each card.
Constants¶
First, we’ll create some constants used in positioning the cards, and keeping track of what card is which.
We could just hard-code numbers, but I like to calculate things out. The “mat” will eventually be a square slightly larger than each card that tracks where we can put cards. (A mat where we can put a pile of cards on.)
1# Constants for sizing
2CARD_SCALE = 0.6
3
4# How big are the cards?
5CARD_WIDTH = 140 * CARD_SCALE
6CARD_HEIGHT = 190 * CARD_SCALE
7
8# How big is the mat we'll place the card on?
9MAT_PERCENT_OVERSIZE = 1.25
10MAT_HEIGHT = int(CARD_HEIGHT * MAT_PERCENT_OVERSIZE)
11MAT_WIDTH = int(CARD_WIDTH * MAT_PERCENT_OVERSIZE)
12
13# How much space do we leave as a gap between the mats?
14# Done as a percent of the mat size.
15VERTICAL_MARGIN_PERCENT = 0.10
16HORIZONTAL_MARGIN_PERCENT = 0.10
17
18# The Y of the bottom row (2 piles)
19BOTTOM_Y = MAT_HEIGHT / 2 + MAT_HEIGHT * VERTICAL_MARGIN_PERCENT
20
21# The X of where to start putting things on the left side
22START_X = MAT_WIDTH / 2 + MAT_WIDTH * HORIZONTAL_MARGIN_PERCENT
23
24# Card constants
25CARD_VALUES = ["A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K"]
26CARD_SUITS = ["Clubs", "Hearts", "Spades", "Diamonds"]
Card Class¶
Next up, we’ll create a card class. The card class is a subclass of
arcade.Sprite
. It will have attributes for the suit and value of the
card, and auto-load the image for the card based on that.
We’ll use the entire image as the hit box, so we don’t need to go through the time consuming hit box calculation. Therefore we turn that off. Otherwise loading the sprites would take a long time.
1class Card(arcade.Sprite):
2 """ Card sprite """
3
4 def __init__(self, suit, value, scale=1):
5 """ Card constructor """
6
7 # Attributes for suit and value
8 self.suit = suit
9 self.value = value
10
11 # Image to use for the sprite when face up
12 self.image_file_name = f":resources:images/cards/card{self.suit}{self.value}.png"
13
14 # Call the parent
15 super().__init__(self.image_file_name, scale, hit_box_algorithm="None")
Creating Cards¶
We’ll start by creating an attribute for the SpriteList
that will hold all
the cards in the game.
1 def __init__(self):
2 super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
3
4 # Sprite list with all the cards, no matter what pile they are in.
5 self.card_list = None
6
7 arcade.set_background_color(arcade.color.AMAZON)
In setup
we’ll create the list and the cards. We don’t do this in __init__
because by separating the creation into its own method, we can easily restart the
game by calling setup
.
1 def setup(self):
2 """ Set up the game here. Call this function to restart the game. """
3
4 # Sprite list with all the cards, no matter what pile they are in.
5 self.card_list = arcade.SpriteList()
6
7 # Create every card
8 for card_suit in CARD_SUITS:
9 for card_value in CARD_VALUES:
10 card = Card(card_suit, card_value, CARD_SCALE)
11 card.position = START_X, BOTTOM_Y
12 self.card_list.append(card)
Drawing Cards¶
Finally, draw the cards:
1 def on_draw(self):
2 """ Render the screen. """
3 # Clear the screen
4 arcade.start_render()
5
6 # Draw the cards
7 self.card_list.draw()
You should end up with all the cards stacked in the lower-left corner:

solitaire_02.py Full Listing ← Full listing of where we are right now
solitaire_02.py Diff ← What we changed to get here
Implement Drag and Drop¶
Next up, let’s add the ability to pick up, drag, and drop the cards.
Track the Cards¶
First, let’s add attributes to track what cards we are moving. Because we can move multiple cards, we’ll keep this as a list. If the user drops the card in an illegal spot, we’ll need to reset the card to its original position. So we’ll also track that.
Create the attributes:
1 def __init__(self):
2 super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
3
4 # Sprite list with all the cards, no matter what pile they are in.
5 self.card_list = None
6
7 arcade.set_background_color(arcade.color.AMAZON)
8
9 # List of cards we are dragging with the mouse
10 self.held_cards = None
11
12 # Original location of cards we are dragging with the mouse in case
13 # they have to go back.
14 self.held_cards_original_position = None
Set the initial values (an empty list):
1 def setup(self):
2 """ Set up the game here. Call this function to restart the game. """
3
4 # List of cards we are dragging with the mouse
5 self.held_cards = []
6
7 # Original location of cards we are dragging with the mouse in case
8 # they have to go back.
9 self.held_cards_original_position = []
10
11 # Sprite list with all the cards, no matter what pile they are in.
12 self.card_list = arcade.SpriteList()
13
14 # Create every card
15 for card_suit in CARD_SUITS:
16 for card_value in CARD_VALUES:
17 card = Card(card_suit, card_value, CARD_SCALE)
18 card.position = START_X, BOTTOM_Y
19 self.card_list.append(card)
Pull Card to Top of Draw Order¶
When we click on the card, we’ll want it to be the last card drawn, so it appears on top of all the other cards. Otherwise we might drag a card underneath another card, which would look odd.
1 def pull_to_top(self, card):
2 """ Pull card to top of rendering order (last to render, looks on-top) """
3 # Find the index of the card
4 index = self.card_list.index(card)
5 # Loop and pull all the other cards down towards the zero end
6 for i in range(index, len(self.card_list) - 1):
7 self.card_list[i] = self.card_list[i + 1]
8 # Put this card at the right-side/top/size of list
9 self.card_list[len(self.card_list) - 1] = card
Mouse Button Pressed¶
When the user presses the mouse button, we will:
See if they clicked on a card
If so, put that card in our held cards list
Save the original position of the card
Pull it to the top of the draw order
1 def on_mouse_press(self, x, y, button, key_modifiers):
2 """ Called when the user presses a mouse button. """
3
4 # Get list of cards we've clicked on
5 cards = arcade.get_sprites_at_point((x, y), self.card_list)
6
7 # Have we clicked on a card?
8 if len(cards) > 0:
9
10 # Might be a stack of cards, get the top one
11 primary_card = cards[-1]
12
13 # All other cases, grab the face-up card we are clicking on
14 self.held_cards = [primary_card]
15 # Save the position
16 self.held_cards_original_position = [self.held_cards[0].position]
17 # Put on top in drawing order
18 self.pull_to_top(self.held_cards[0])
Mouse Moved¶
If the user moves the mouse, we’ll move any held cards with it.
1 def on_mouse_motion(self, x: float, y: float, dx: float, dy: float):
2 """ User moves mouse """
3
4 # If we are holding cards, move them with the mouse
5 for card in self.held_cards:
6 card.center_x += dx
7 card.center_y += dy
Mouse Released¶
When the user releases the mouse button, we’ll clear the held card list.
1 def on_mouse_release(self, x: float, y: float, button: int,
2 modifiers: int):
3 """ Called when the user presses a mouse button. """
4
5 # If we don't have any cards, who cares
6 if len(self.held_cards) == 0:
7 return
8
9 # We are no longer holding cards
10 self.held_cards = []
Test the Program¶
You should now be able to pick up and move cards around the screen. Try it out!

solitaire_03.py Full Listing ← Full listing of where we are right now
solitaire_03.py Diff ← What we changed to get here
Draw Pile Mats¶
Next, we’ll create sprites that will act as guides to where the piles of cards go in our game. We’ll create these as sprites, so we can use collision detection to figure out of we are dropping a card on them or not.
Create Constants¶
First, we’ll create constants for the middle row of seven piles, and for the top row of four piles. We’ll also create a constant for how far apart each pile should be.
Again, we could hard-code numbers, but I like calculating them so I can change the scale easily.
1# The Y of the top row (4 piles)
2TOP_Y = SCREEN_HEIGHT - MAT_HEIGHT / 2 - MAT_HEIGHT * VERTICAL_MARGIN_PERCENT
3
4# The Y of the middle row (7 piles)
5MIDDLE_Y = TOP_Y - MAT_HEIGHT - MAT_HEIGHT * VERTICAL_MARGIN_PERCENT
6
7# How far apart each pile goes
8X_SPACING = MAT_WIDTH + MAT_WIDTH * HORIZONTAL_MARGIN_PERCENT
Create Mat Sprites¶
Create an attribute for the mat sprite list:
1 def __init__(self):
2 super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
3
4 # Sprite list with all the cards, no matter what pile they are in.
5 self.card_list = None
6
7 arcade.set_background_color(arcade.color.AMAZON)
8
9 # List of cards we are dragging with the mouse
10 self.held_cards = None
11
12 # Original location of cards we are dragging with the mouse in case
13 # they have to go back.
14 self.held_cards_original_position = None
15
16 # Sprite list with all the mats tha cards lay on.
17 self.pile_mat_list = None
Then create the mat sprites in the setup
method
1 def setup(self):
2 """ Set up the game here. Call this function to restart the game. """
3
4 # List of cards we are dragging with the mouse
5 self.held_cards = []
6
7 # Original location of cards we are dragging with the mouse in case
8 # they have to go back.
9 self.held_cards_original_position = []
10
11 # --- Create the mats the cards go on.
12
13 # Sprite list with all the mats tha cards lay on.
14 self.pile_mat_list: arcade.SpriteList = arcade.SpriteList()
15
16 # Create the mats for the bottom face down and face up piles
17 pile = arcade.SpriteSolidColor(MAT_WIDTH, MAT_HEIGHT, arcade.csscolor.DARK_OLIVE_GREEN)
18 pile.position = START_X, BOTTOM_Y
19 self.pile_mat_list.append(pile)
20
21 pile = arcade.SpriteSolidColor(MAT_WIDTH, MAT_HEIGHT, arcade.csscolor.DARK_OLIVE_GREEN)
22 pile.position = START_X + X_SPACING, BOTTOM_Y
23 self.pile_mat_list.append(pile)
24
25 # Create the seven middle piles
26 for i in range(7):
27 pile = arcade.SpriteSolidColor(MAT_WIDTH, MAT_HEIGHT, arcade.csscolor.DARK_OLIVE_GREEN)
28 pile.position = START_X + i * X_SPACING, MIDDLE_Y
29 self.pile_mat_list.append(pile)
30
31 # Create the top "play" piles
32 for i in range(4):
33 pile = arcade.SpriteSolidColor(MAT_WIDTH, MAT_HEIGHT, arcade.csscolor.DARK_OLIVE_GREEN)
34 pile.position = START_X + i * X_SPACING, TOP_Y
35 self.pile_mat_list.append(pile)
36
37 # Sprite list with all the cards, no matter what pile they are in.
38 self.card_list = arcade.SpriteList()
39
40 # Create every card
41 for card_suit in CARD_SUITS:
42 for card_value in CARD_VALUES:
43 card = Card(card_suit, card_value, CARD_SCALE)
44 card.position = START_X, BOTTOM_Y
45 self.card_list.append(card)
Draw Mat Sprites¶
Finally, the mats aren’t going to display if we don’t draw them:
1 def on_draw(self):
2 """ Render the screen. """
3 # Clear the screen
4 arcade.start_render()
5
6 # Draw the mats the cards go on to
7 self.pile_mat_list.draw()
8
9 # Draw the cards
10 self.card_list.draw()
Test the Program¶
Run the program, and see if the mats appear:

solitaire_04.py Full Listing ← Full listing of where we are right now
solitaire_04.py Diff ← What we changed to get here
Snap Cards to Piles¶
Right now, you can drag the cards anywhere. They don’t have to go onto a pile. Let’s add code that “snaps” the card onto a pile. If we don’t drop on a pile, let’s reset back to the original location.
1 def on_mouse_release(self, x: float, y: float, button: int,
2 modifiers: int):
3 """ Called when the user presses a mouse button. """
4
5 # If we don't have any cards, who cares
6 if len(self.held_cards) == 0:
7 return
8
9 # Find the closest pile, in case we are in contact with more than one
10 pile, distance = arcade.get_closest_sprite(self.held_cards[0], self.pile_mat_list)
11 reset_position = True
12
13 # See if we are in contact with the closest pile
14 if arcade.check_for_collision(self.held_cards[0], pile):
15
16 # For each held card, move it to the pile we dropped on
17 for i, dropped_card in enumerate(self.held_cards):
18 # Move cards to proper position
19 dropped_card.position = pile.center_x, pile.center_y
20
21 # Success, don't reset position of cards
22 reset_position = False
23
24 # Release on top play pile? And only one card held?
25 if reset_position:
26 # Where-ever we were dropped, it wasn't valid. Reset the each card's position
27 # to its original spot.
28 for pile_index, card in enumerate(self.held_cards):
29 card.position = self.held_cards_original_position[pile_index]
30
31 # We are no longer holding cards
32 self.held_cards = []
solitaire_05.py Full Listing ← Full listing of where we are right now
solitaire_05.py Diff ← What we changed to get here
Shuffle the Cards¶
Having all the cards in order is boring. Let’s shuffle them in the setup
method:
1 # Shuffle the cards
2 for pos1 in range(len(self.card_list)):
3 pos2 = random.randrange(len(self.card_list))
4 self.card_list[pos1], self.card_list[pos2] = self.card_list[pos2], self.card_list[pos1]
Don’t forget to import random
at the top.
Run your program and make sure you can move cards around.

solitaire_06.py Full Listing ← Full listing of where we are right now
solitaire_06.py Diff ← What we changed to get here
Track Card Piles¶
Right now we are moving the cards around. But it isn’t easy to figure out what card is in which pile. We could check by position, but then we start fanning the cards out, that will be very difficult.
Therefore we will keep a separate list for each pile of cards. When we move a card we need to move the position, and switch which list it is in.
Add New Constants¶
To start with, let’s add some constants for each pile:
1# If we fan out cards stacked on each other, how far apart to fan them?
2CARD_VERTICAL_OFFSET = CARD_HEIGHT * CARD_SCALE * 0.3
3
4# Constants that represent "what pile is what" for the game
5PILE_COUNT = 13
6BOTTOM_FACE_DOWN_PILE = 0
7BOTTOM_FACE_UP_PILE = 1
8PLAY_PILE_1 = 2
9PLAY_PILE_2 = 3
10PLAY_PILE_3 = 4
11PLAY_PILE_4 = 5
12PLAY_PILE_5 = 6
13PLAY_PILE_6 = 7
14PLAY_PILE_7 = 8
15TOP_PILE_1 = 9
16TOP_PILE_2 = 10
17TOP_PILE_3 = 11
18TOP_PILE_4 = 12
Create the Pile Lists¶
Then in our __init__
add a variable to track the piles:
1 # Create a list of lists, each holds a pile of cards.
2 self.piles = None
In the setup
method, create a list for each pile. Then, add all the cards
to the face-down deal pile. (Later, we’ll add support for face-down cards.
Yes, right now all the cards in the face down pile are up.)
1 # Create a list of lists, each holds a pile of cards.
2 self.piles = [[] for _ in range(PILE_COUNT)]
3
4 # Put all the cards in the bottom face-down pile
5 for card in self.card_list:
6 self.piles[BOTTOM_FACE_DOWN_PILE].append(card)
Card Pile Management Methods¶
Next, we need some convenience methods we’ll use elsewhere.
First, given a card, return the index of which pile that card belongs to:
1 def get_pile_for_card(self, card):
2 """ What pile is this card in? """
3 for index, pile in enumerate(self.piles):
4 if card in pile:
5 return index
Next, remove a card from whatever pile it happens to be in.
1 def remove_card_from_pile(self, card):
2 """ Remove card from whatever pile it was in. """
3 for pile in self.piles:
4 if card in pile:
5 pile.remove(card)
6 break
Finally, move a card from one pile to another.
1 def move_card_to_new_pile(self, card, pile_index):
2 """ Move the card to a new pile """
3 self.remove_card_from_pile(card)
4 self.piles[pile_index].append(card)
Dropping the Card¶
Next, we need to modify what happens when we release the mouse.
First, see if we release it onto the same pile it came from. If so, just reset the card back to its original location.
1 def on_mouse_release(self, x: float, y: float, button: int,
2 modifiers: int):
3 """ Called when the user presses a mouse button. """
4
5 # If we don't have any cards, who cares
6 if len(self.held_cards) == 0:
7 return
8
9 # Find the closest pile, in case we are in contact with more than one
10 pile, distance = arcade.get_closest_sprite(self.held_cards[0], self.pile_mat_list)
11 reset_position = True
12
13 # See if we are in contact with the closest pile
14 if arcade.check_for_collision(self.held_cards[0], pile):
15
16 # What pile is it?
17 pile_index = self.pile_mat_list.index(pile)
18
19 # Is it the same pile we came from?
20 if pile_index == self.get_pile_for_card(self.held_cards[0]):
21 # If so, who cares. We'll just reset our position.
22 pass
What if it is on a middle play pile? Ugh, that’s a bit complicated. If the mat is empty, we need to place it in the middle of the mat. If there are cards on the mat, we need to offset the card so we can see a spread of cards.
While we can only pick up one card at a time right now, we need to support dropping multiple cards for once we support multiple card carries.
1 # Is it on a middle play pile?
2 elif PLAY_PILE_1 <= pile_index <= PLAY_PILE_7:
3 # Are there already cards there?
4 if len(self.piles[pile_index]) > 0:
5 # Move cards to proper position
6 top_card = self.piles[pile_index][-1]
7 for i, dropped_card in enumerate(self.held_cards):
8 dropped_card.position = top_card.center_x, \
9 top_card.center_y - CARD_VERTICAL_OFFSET * (i + 1)
10 else:
11 # Are there no cards in the middle play pile?
12 for i, dropped_card in enumerate(self.held_cards):
13 # Move cards to proper position
14 dropped_card.position = pile.center_x, \
15 pile.center_y - CARD_VERTICAL_OFFSET * i
16
17 for card in self.held_cards:
18 # Cards are in the right position, but we need to move them to the right list
19 self.move_card_to_new_pile(card, pile_index)
20
21 # Success, don't reset position of cards
22 reset_position = False
What if it is released on a top play pile? Make sure that we only have one card we are holding. We don’t want to drop a stack up top. Then move the card to that pile.
1 # Release on top play pile? And only one card held?
2 elif TOP_PILE_1 <= pile_index <= TOP_PILE_4 and len(self.held_cards) == 1:
3 # Move position of card to pile
4 self.held_cards[0].position = pile.position
5 # Move card to card list
6 for card in self.held_cards:
7 self.move_card_to_new_pile(card, pile_index)
8
9 reset_position = False
If the move is invalid, we need to reset all held cards to their initial location.
1 if reset_position:
2 # Where-ever we were dropped, it wasn't valid. Reset the each card's position
3 # to its original spot.
4 for pile_index, card in enumerate(self.held_cards):
5 card.position = self.held_cards_original_position[pile_index]
6
7 # We are no longer holding cards
8 self.held_cards = []
Test¶
Test out your program, and see if the cards are being fanned out properly.
Note
The code isn’t enforcing any game rules. You can stack cards in any order. Also, with long stacks of cards, you still have to drop the card on the mat. This is counter-intuitive when the stack of cards extends downwards past the mat.
We leave the solutions to these issues as an exercise for the reader.

solitaire_07.py Full Listing ← Full listing of where we are right now
solitaire_07.py Diff ← What we changed to get here
Pick Up Card Stacks¶
How do we pick up a whole stack of cards? When the mouse is pressed, we need to figure out what pile the card is in.
Next, look at where in the pile the card is that we clicked on. If there are any cards later on on the pile, we want to pick up those cards too. Add them to the list.
1 def on_mouse_press(self, x, y, button, key_modifiers):
2 """ Called when the user presses a mouse button. """
3
4 # Get list of cards we've clicked on
5 cards = arcade.get_sprites_at_point((x, y), self.card_list)
6
7 # Have we clicked on a card?
8 if len(cards) > 0:
9
10 # Might be a stack of cards, get the top one
11 primary_card = cards[-1]
12 # Figure out what pile the card is in
13 pile_index = self.get_pile_for_card(primary_card)
14
15 # All other cases, grab the face-up card we are clicking on
16 self.held_cards = [primary_card]
17 # Save the position
18 self.held_cards_original_position = [self.held_cards[0].position]
19 # Put on top in drawing order
20 self.pull_to_top(self.held_cards[0])
21
22 # Is this a stack of cards? If so, grab the other cards too
23 card_index = self.piles[pile_index].index(primary_card)
24 for i in range(card_index + 1, len(self.piles[pile_index])):
25 card = self.piles[pile_index][i]
26 self.held_cards.append(card)
27 self.held_cards_original_position.append(card.position)
28 self.pull_to_top(card)
After this, you should be able to pick up a stack of cards from the middle piles with the mouse and move them around.
solitaire_08.py Full Listing ← Full listing of where we are right now
solitaire_08.py Diff ← What we changed to get here
Deal Out Cards¶
We can deal the cards into the seven middle piles by adding some code to the
setup
method. We need to change the list each card is part of, along with
its position.
1 # - Pull from that pile into the middle piles, all face-down
2 # Loop for each pile
3 for pile_no in range(PLAY_PILE_1, PLAY_PILE_7 + 1):
4 # Deal proper number of cards for that pile
5 for j in range(pile_no - PLAY_PILE_1 + 1):
6 # Pop the card off the deck we are dealing from
7 card = self.piles[BOTTOM_FACE_DOWN_PILE].pop()
8 # Put in the proper pile
9 self.piles[pile_no].append(card)
10 # Move card to same position as pile we just put it in
11 card.position = self.pile_mat_list[pile_no].position
12 # Put on top in draw order
13 self.pull_to_top(card)
solitaire_09.py Full Listing ← Full listing of where we are right now
solitaire_09.py Diff ← What we changed to get here
Face Down Cards¶
We don’t play solitaire with all the cards facing up, so let’s add face-down support to our game.
New Constants¶
First define a constant for what image to use when face-down.
1# Face down image
2FACE_DOWN_IMAGE = ":resources:images/cards/cardBack_red2.png"
Updates to Card Class¶
Next, default each card in the Card
class to be face up. Also, let’s add
methods to flip the card up or down.
1class Card(arcade.Sprite):
2 """ Card sprite """
3
4 def __init__(self, suit, value, scale=1):
5 """ Card constructor """
6
7 # Attributes for suit and value
8 self.suit = suit
9 self.value = value
10
11 # Image to use for the sprite when face up
12 self.image_file_name = f":resources:images/cards/card{self.suit}{self.value}.png"
13 self.is_face_up = False
14 super().__init__(FACE_DOWN_IMAGE, scale, hit_box_algorithm="None")
15
16 def face_down(self):
17 """ Turn card face-down """
18 self.texture = arcade.load_texture(FACE_DOWN_IMAGE)
19 self.is_face_up = False
20
21 def face_up(self):
22 """ Turn card face-up """
23 self.texture = arcade.load_texture(self.image_file_name)
24 self.is_face_up = True
25
26 @property
27 def is_face_down(self):
28 """ Is this card face down? """
29 return not self.is_face_up
Flip Up Cards On Middle Seven Piles¶
Right now every card is face down. Let’s update the setup
method so the
top cards in the middle seven piles are face up.
1 # Flip up the top cards
2 for i in range(PLAY_PILE_1, PLAY_PILE_7 + 1):
3 self.piles[i][-1].face_up()
Flip Up Cards When Clicked¶
When we click on a card that is face down, instead of picking it up, let’s flip it over:
1 def on_mouse_press(self, x, y, button, key_modifiers):
2 """ Called when the user presses a mouse button. """
3
4 # Get list of cards we've clicked on
5 cards = arcade.get_sprites_at_point((x, y), self.card_list)
6
7 # Have we clicked on a card?
8 if len(cards) > 0:
9
10 # Might be a stack of cards, get the top one
11 primary_card = cards[-1]
12 # Figure out what pile the card is in
13 pile_index = self.get_pile_for_card(primary_card)
14
15 if primary_card.is_face_down:
16 # Is the card face down? In one of those middle 7 piles? Then flip up
17 primary_card.face_up()
18 else:
19 # All other cases, grab the face-up card we are clicking on
20 self.held_cards = [primary_card]
21 # Save the position
22 self.held_cards_original_position = [self.held_cards[0].position]
23 # Put on top in drawing order
24 self.pull_to_top(self.held_cards[0])
25
26 # Is this a stack of cards? If so, grab the other cards too
27 card_index = self.piles[pile_index].index(primary_card)
28 for i in range(card_index + 1, len(self.piles[pile_index])):
29 card = self.piles[pile_index][i]
30 self.held_cards.append(card)
31 self.held_cards_original_position.append(card.position)
32 self.pull_to_top(card)
Test¶
Try out your program. As you move cards around, you should see face down cards as well, and be able to flip them over.

solitaire_10.py Full Listing ← Full listing of where we are right now
solitaire_10.py Diff ← What we changed to get here
Restart Game¶
We can add the ability to restart are game any type we press the ‘R’ key:
1 def on_key_press(self, symbol: int, modifiers: int):
2 """ User presses key """
3 if symbol == arcade.key.R:
4 # Restart
5 self.setup()
Flip Three From Draw Pile¶
The draw pile at the bottom of our screen doesn’t work right yet. When we click on it, we need it to flip three cards to the bottom-right pile. Also, if the have gone through all the cards in the pile, we need to reset the pile so we can go through it again.
1 def on_mouse_press(self, x, y, button, key_modifiers):
2 """ Called when the user presses a mouse button. """
3
4 # Get list of cards we've clicked on
5 cards = arcade.get_sprites_at_point((x, y), self.card_list)
6
7 # Have we clicked on a card?
8 if len(cards) > 0:
9
10 # Might be a stack of cards, get the top one
11 primary_card = cards[-1]
12 # Figure out what pile the card is in
13 pile_index = self.get_pile_for_card(primary_card)
14
15 # Are we clicking on the bottom deck, to flip three cards?
16 if pile_index == BOTTOM_FACE_DOWN_PILE:
17 # Flip three cards
18 for i in range(3):
19 # If we ran out of cards, stop
20 if len(self.piles[BOTTOM_FACE_DOWN_PILE]) == 0:
21 break
22 # Get top card
23 card = self.piles[BOTTOM_FACE_DOWN_PILE][-1]
24 # Flip face up
25 card.face_up()
26 # Move card position to bottom-right face up pile
27 card.position = self.pile_mat_list[BOTTOM_FACE_UP_PILE].position
28 # Remove card from face down pile
29 self.piles[BOTTOM_FACE_DOWN_PILE].remove(card)
30 # Move card to face up list
31 self.piles[BOTTOM_FACE_UP_PILE].append(card)
32 # Put on top draw-order wise
33 self.pull_to_top(card)
34
35 elif primary_card.is_face_down:
36 # Is the card face down? In one of those middle 7 piles? Then flip up
37 primary_card.face_up()
38 else:
39 # All other cases, grab the face-up card we are clicking on
40 self.held_cards = [primary_card]
41 # Save the position
42 self.held_cards_original_position = [self.held_cards[0].position]
43 # Put on top in drawing order
44 self.pull_to_top(self.held_cards[0])
45
46 # Is this a stack of cards? If so, grab the other cards too
47 card_index = self.piles[pile_index].index(primary_card)
48 for i in range(card_index + 1, len(self.piles[pile_index])):
49 card = self.piles[pile_index][i]
50 self.held_cards.append(card)
51 self.held_cards_original_position.append(card.position)
52 self.pull_to_top(card)
53
54 else:
55
56 # Click on a mat instead of a card?
57 mats = arcade.get_sprites_at_point((x, y), self.pile_mat_list)
58
59 if len(mats) > 0:
60 mat = mats[0]
61 mat_index = self.pile_mat_list.index(mat)
62
63 # Is it our turned over flip mat? and no cards on it?
64 if mat_index == BOTTOM_FACE_DOWN_PILE and len(self.piles[BOTTOM_FACE_DOWN_PILE]) == 0:
65 # Flip the deck back over so we can restart
66 temp_list = self.piles[BOTTOM_FACE_UP_PILE].copy()
67 for card in reversed(temp_list):
68 card.face_down()
69 self.piles[BOTTOM_FACE_UP_PILE].remove(card)
70 self.piles[BOTTOM_FACE_DOWN_PILE].append(card)
71 card.position = self.pile_mat_list[BOTTOM_FACE_DOWN_PILE].position
Test¶
Now we’ve got a basic working solitaire game! Try it out!

solitaire_11.py Full Listing ← Full listing of where we are right now
solitaire_11.py Diff ← What we changed to get here
Conclusion¶
There’s a lot more that could be added to this game, such as enforcing rules, adding animation to ‘slide’ a dropped card to its position, sound, better graphics, and more. Or this could be adapted to a different card game.
Hopefully this is enough to get you started on your own game.
Lights Tutorial¶

(To be done.)
1"""
2Show how to use lights.
3
4.. note:: This uses features from the upcoming version 2.4. The API for these
5 functions may still change. To use, you will need to install one of the
6 pre-release packages, or install via GitHub.
7
8Artwork from http://kenney.nl
9
10"""
11import arcade
12from arcade.experimental.lights import Light, LightLayer
13
14SCREEN_WIDTH = 1024
15SCREEN_HEIGHT = 768
16SCREEN_TITLE = "Lighting Demo"
17VIEWPORT_MARGIN = 200
18MOVEMENT_SPEED = 5
19
20# This is the color used for 'ambient light'. If you don't want any
21# ambient light, set it to black.
22AMBIENT_COLOR = (10, 10, 10)
23
24class MyGame(arcade.Window):
25 """ Main Game Window """
26
27 def __init__(self, width, height, title):
28 """ Set up the class. """
29 super().__init__(width, height, title, resizable=True)
30
31 # Sprite lists
32 self.background_sprite_list = None
33 self.player_list = None
34 self.wall_list = None
35 self.player_sprite = None
36
37 # Physics engine
38 self.physics_engine = None
39
40 # Used for scrolling
41 self.view_left = 0
42 self.view_bottom = 0
43
44 # --- Light related ---
45 # List of all the lights
46 self.light_layer = None
47 # Individual light we move with player, and turn on/off
48 self.player_light = None
49
50 def setup(self):
51 """ Create everything """
52
53 # Create sprite lists
54 self.background_sprite_list = arcade.SpriteList()
55 self.player_list = arcade.SpriteList()
56 self.wall_list = arcade.SpriteList()
57
58 # Create player sprite
59 self.player_sprite = arcade.Sprite(":resources:images/animated_characters/female_person/femalePerson_idle.png", 0.4)
60 self.player_sprite.center_x = 64
61 self.player_sprite.center_y = 270
62 self.player_list.append(self.player_sprite)
63
64 # --- Light related ---
65 # Lights must shine on something. If there is no background sprite or color,
66 # you will just see black. Therefore, we use a loop to create a whole bunch of brick tiles to go in the
67 # background.
68 for x in range(-128, 2000, 128):
69 for y in range(-128, 1000, 128):
70 sprite = arcade.Sprite(":resources:images/tiles/brickTextureWhite.png")
71 sprite.position = x, y
72 self.background_sprite_list.append(sprite)
73
74 # Create a light layer, used to render things to, then post-process and
75 # add lights. This must match the screen size.
76 self.light_layer = LightLayer(SCREEN_WIDTH, SCREEN_HEIGHT)
77 # We can also set the background color that will be lit by lights,
78 # but in this instance we just want a black background
79 self.light_layer.set_background_color(arcade.color.BLACK)
80
81 # Here we create a bunch of lights.
82
83 # Create a small white light
84 x = 100
85 y = 200
86 radius = 100
87 mode = 'soft'
88 color = arcade.csscolor.WHITE
89 light = Light(x, y, radius, color, mode)
90 self.light_layer.add(light)
91
92 # Create an overlapping, large white light
93 x = 300
94 y = 150
95 radius = 200
96 color = arcade.csscolor.WHITE
97 mode = 'soft'
98 light = Light(x, y, radius, color, mode)
99 self.light_layer.add(light)
100
101 # Create three, non-overlapping RGB lights
102 x = 50
103 y = 450
104 radius = 100
105 mode = 'soft'
106 color = arcade.csscolor.RED
107 light = Light(x, y, radius, color, mode)
108 self.light_layer.add(light)
109
110 x = 250
111 y = 450
112 radius = 100
113 mode = 'soft'
114 color = arcade.csscolor.GREEN
115 light = Light(x, y, radius, color, mode)
116 self.light_layer.add(light)
117
118 x = 450
119 y = 450
120 radius = 100
121 mode = 'soft'
122 color = arcade.csscolor.BLUE
123 light = Light(x, y, radius, color, mode)
124 self.light_layer.add(light)
125
126 # Create three, overlapping RGB lights
127 x = 650
128 y = 450
129 radius = 100
130 mode = 'soft'
131 color = arcade.csscolor.RED
132 light = Light(x, y, radius, color, mode)
133 self.light_layer.add(light)
134
135 x = 750
136 y = 450
137 radius = 100
138 mode = 'soft'
139 color = arcade.csscolor.GREEN
140 light = Light(x, y, radius, color, mode)
141 self.light_layer.add(light)
142
143 x = 850
144 y = 450
145 radius = 100
146 mode = 'soft'
147 color = arcade.csscolor.BLUE
148 light = Light(x, y, radius, color, mode)
149 self.light_layer.add(light)
150
151 # Create three, overlapping RGB lights
152 # But 'hard' lights that don't fade out.
153 x = 650
154 y = 150
155 radius = 100
156 mode = 'hard'
157 color = arcade.csscolor.RED
158 light = Light(x, y, radius, color, mode)
159 self.light_layer.add(light)
160
161 x = 750
162 y = 150
163 radius = 100
164 mode = 'hard'
165 color = arcade.csscolor.GREEN
166 light = Light(x, y, radius, color, mode)
167 self.light_layer.add(light)
168
169 x = 850
170 y = 150
171 radius = 100
172 mode = 'hard'
173 color = arcade.csscolor.BLUE
174 light = Light(x, y, radius, color, mode)
175 self.light_layer.add(light)
176
177 # Create a light to follow the player around.
178 # We'll position it later, when the player moves.
179 # We'll only add it to the light layer when the player turns the light
180 # on. We start with the light off.
181 radius = 150
182 mode = 'soft'
183 color = arcade.csscolor.WHITE
184 self.player_light = Light(0, 0, radius, color, mode)
185
186 # Create the physics engine
187 self.physics_engine = arcade.PhysicsEngineSimple(self.player_sprite, self.wall_list)
188
189 # Set the viewport boundaries
190 # These numbers set where we have 'scrolled' to.
191 self.view_left = 0
192 self.view_bottom = 0
193
194 def on_draw(self):
195 """ Draw everything. """
196 arcade.start_render()
197
198 # --- Light related ---
199 # Everything that should be affected by lights gets rendered inside this
200 # 'with' statement. Nothing is rendered to the screen yet, just the light
201 # layer.
202 with self.light_layer:
203 self.background_sprite_list.draw()
204 self.player_list.draw()
205
206 # Draw the light layer to the screen.
207 # This fills the entire screen with the lit version
208 # of what we drew into the light layer above.
209 self.light_layer.draw(ambient_color=AMBIENT_COLOR)
210
211 # Now draw anything that should NOT be affected by lighting.
212 arcade.draw_text("Press SPACE to turn character light on/off.",
213 10 + self.view_left, 10 + self.view_bottom,
214 arcade.color.WHITE, 20)
215
216 def on_resize(self, width, height):
217 """ User resizes the screen. """
218
219 # --- Light related ---
220 # We need to resize the light layer to
221 self.light_layer.resize(width, height)
222
223 # Scroll the screen so the user is visible
224 self.scroll_screen()
225
226 def on_key_press(self, key, _):
227 """Called whenever a key is pressed. """
228
229 if key == arcade.key.UP:
230 self.player_sprite.change_y = MOVEMENT_SPEED
231 elif key == arcade.key.DOWN:
232 self.player_sprite.change_y = -MOVEMENT_SPEED
233 elif key == arcade.key.LEFT:
234 self.player_sprite.change_x = -MOVEMENT_SPEED
235 elif key == arcade.key.RIGHT:
236 self.player_sprite.change_x = MOVEMENT_SPEED
237 elif key == arcade.key.SPACE:
238 # --- Light related ---
239 # We can add/remove lights from the light layer. If they aren't
240 # in the light layer, the light is off.
241 if self.player_light in self.light_layer:
242 self.light_layer.remove(self.player_light)
243 else:
244 self.light_layer.add(self.player_light)
245
246 def on_key_release(self, key, _):
247 """Called when the user releases a key. """
248
249 if key == arcade.key.UP or key == arcade.key.DOWN:
250 self.player_sprite.change_y = 0
251 elif key == arcade.key.LEFT or key == arcade.key.RIGHT:
252 self.player_sprite.change_x = 0
253
254 def scroll_screen(self):
255 """ Manage Scrolling """
256
257 # Scroll left
258 left_boundary = self.view_left + VIEWPORT_MARGIN
259 if self.player_sprite.left < left_boundary:
260 self.view_left -= left_boundary - self.player_sprite.left
261
262 # Scroll right
263 right_boundary = self.view_left + self.width - VIEWPORT_MARGIN
264 if self.player_sprite.right > right_boundary:
265 self.view_left += self.player_sprite.right - right_boundary
266
267 # Scroll up
268 top_boundary = self.view_bottom + self.height - VIEWPORT_MARGIN
269 if self.player_sprite.top > top_boundary:
270 self.view_bottom += self.player_sprite.top - top_boundary
271
272 # Scroll down
273 bottom_boundary = self.view_bottom + VIEWPORT_MARGIN
274 if self.player_sprite.bottom < bottom_boundary:
275 self.view_bottom -= bottom_boundary - self.player_sprite.bottom
276
277 # Make sure our boundaries are integer values. While the viewport does
278 # support floating point numbers, for this application we want every pixel
279 # in the view port to map directly onto a pixel on the screen. We don't want
280 # any rounding errors.
281 self.view_left = int(self.view_left)
282 self.view_bottom = int(self.view_bottom)
283
284 arcade.set_viewport(self.view_left,
285 self.width + self.view_left,
286 self.view_bottom,
287 self.height + self.view_bottom)
288
289 def on_update(self, delta_time):
290 """ Movement and game logic """
291
292 # Call update on all sprites (The sprites don't do much in this
293 # example though.)
294 self.physics_engine.update()
295
296 # --- Light related ---
297 # We can easily move the light by setting the position,
298 # or by center_x, center_y.
299 self.player_light.position = self.player_sprite.position
300
301 # Scroll the screen so we can see the player
302 self.scroll_screen()
303
304
305if __name__ == "__main__":
306 window = MyGame(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
307 window.setup()
308 arcade.run()
GPU Particle Burst¶

In this example, we show how to create explosions using particles. The particles are tracked by the GPU, significantly improving the performance.
Step 1: Open a Blank Window¶
First, let’s start with a blank window.
1"""
2Example showing how to create particle explosions via the GPU.
3"""
4import arcade
5
6SCREEN_WIDTH = 1024
7SCREEN_HEIGHT = 768
8SCREEN_TITLE = "GPU Particle Explosion"
9
10class MyWindow(arcade.Window):
11 """ Main window"""
12 def __init__(self):
13 super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
14
15 def on_draw(self):
16 """ Draw everything """
17 self.clear()
18
19 def on_update(self, dt):
20 """ Update everything """
21 pass
22
23 def on_mouse_press(self, x: float, y: float, button: int, modifiers: int):
24 """ User clicks mouse """
25 pass
26
27
28if __name__ == "__main__":
29 window = MyWindow()
30 window.center_window()
31 arcade.run()
Step 2: Create One Particle For Each Click¶

For this next section, we are going to draw a dot each time the user clicks her mouse on the screen.
For each click, we are going to create an instance of a “burst” that will eventually be turned into a full explosion. Each burst instance will be added to a list.
Imports¶
First, we’ll import some more items for our program:
from array import array
from dataclasses import dataclass
import arcade
import arcade.gl
Burst Dataclass¶
Next, we’ll create a dataclass to track our data for each burst. For each burst we need to track a Vertex Array Object (VAO) which stores information about our burst. Inside of that, we’ll have a Vertex Buffer Object (VBO) which will be a high-speed memory buffer where we’ll store locations, colors, velocity, etc.
@dataclass
class Burst:
""" Track for each burst. """
buffer: arcade.gl.Buffer
vao: arcade.gl.Geometry
Init method¶
Next, we’ll create an empty list attribute called burst_list
. We’ll also
create our OpenGL shader program. The program will be a collection of two
shader programs. These will be stored in separate files, saved in the same
directory.
Note
In addition to loading the program via the load_program() method of ArcadeContext shown, it is also possible to keep the GLSL programs in triple- quoted string by using program() of Context.
def __init__(self):
super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
self.burst_list = []
# Program to visualize the points
self.program = self.ctx.load_program(
vertex_shader="vertex_shader_v1.glsl",
fragment_shader="fragment_shader.glsl",
)
self.ctx.enable_only()
OpenGL Shaders¶
The OpenGL Shading Language (GLSL) is C-style language that runs on your graphics card (GPU) rather than your CPU. Unfortunately a full explanation of the language is beyond the scope of this tutorial. I hope, however, the tutorial can get you started understanding how it works.
We’ll have two shaders. A vertex shader, and a fragment shader. A vertex shader runs for each vertex point of the geometry we are rendering, and a fragment shader runs for each pixel. For example, vertex shader might run four times for each point on a rectangle, and the fragment shader would run for each pixel on the screen.
The vertex shader takes in the position of our vertex. We’ll set in_pos in our Python program, and pass that data to this shader.
The vertex shader outputs the color of our vertex. Colors are in Red-Green-Blue-Alpha (RGBA) format, with floating-point numbers ranging from 0 to 1. In our program below case, we set the color to (1, 1, 1) which is white, and the fourth 1 for completely opaque.
1#version 330
2
3// (x, y) position passed in
4in vec2 in_pos;
5
6// Output the color to the fragment shader
7out vec4 color;
8
9void main() {
10
11 // Set the RGBA color
12 color = vec4(1, 1, 1, 1);
13
14 // Set the position. (x, y, z, w)
15 gl_Position = vec4(in_pos, 0.0, 1);
16}
There’s not much to the fragment shader, it just takes in color
from the vertex
shader and passes it back out as the pixel color. We’ll use the same fragment
shader for every version in this tutorial.
1#version 330
2
3// Color passed in from the vertex shader
4in vec4 color;
5
6// The pixel we are writing to in the framebuffer
7out vec4 fragColor;
8
9void main() {
10
11 // Fill the point
12 fragColor = vec4(color);
13}
Mouse Pressed¶
Each time we press the mouse button, we are going to create a burst at that location.
The data for that burst will be stored in an instance of the Burst
class.
The Burst
class needs our data buffer. The data buffer contains
information about each particle. In this case, we just have one particle and
only need to store the x, y of that particle in the buffer. However, eventually
we’ll have hundreds of particles, each with a position, velocity, color, and
fade rate. To accommodate creating that data, we have made a generator
function _gen_initial_data
. It is totally overkill at this point, but we’ll
add on to it in this tutorial.
The buffer_description
says that each vertex has two floating data points (2f
)
and those data points will come into the shader with the reference name in_pos
which we defined above in our OpenGL Shaders
def on_mouse_press(self, x: float, y: float, button: int, modifiers: int):
""" User clicks mouse """
def _gen_initial_data(initial_x, initial_y):
""" Generate data for each particle """
yield initial_x
yield initial_y
# Recalculate the coordinates from pixels to the OpenGL system with
# 0, 0 at the center.
x2 = x / self.width * 2. - 1.
y2 = y / self.height * 2. - 1.
# Get initial particle data
initial_data = _gen_initial_data(x2, y2)
# Create a buffer with that data
buffer = self.ctx.buffer(data=array('f', initial_data))
# Create a buffer description that says how the buffer data is formatted.
buffer_description = arcade.gl.BufferDescription(buffer,
'2f',
['in_pos'])
# Create our Vertex Attribute Object
vao = self.ctx.geometry([buffer_description])
# Create the Burst object and add it to the list of bursts
burst = Burst(buffer=buffer, vao=vao)
self.burst_list.append(burst)
Drawing¶
Finally, draw it.
def on_draw(self):
""" Draw everything """
self.clear()
# Set the particle size
self.ctx.point_size = 2 * self.get_pixel_ratio()
# Loop through each burst
for burst in self.burst_list:
# Render the burst
burst.vao.render(self.program, mode=self.ctx.POINTS)
Program Listings¶
fragment_shader.glsl Full Listing ← Where we are right now
vertex_shader_v1.glsl Full Listing ← Where we are right now
gpu_particle_burst_02.py Full Listing ← Where we are right now
gpu_particle_burst_02.py Diff ← What we changed to get here
Step 3: Multiple Moving Particles¶

Next step is to have more than one particle, and to have the particles move. We’ll do this by creating the particles, and calculating where they should be based on the time since creation. This is a bit different than the way we move sprites, as they are manually repositioned bit-by-bit during each update call.
Constants¶
Then we need to create a constant that contains the number of particles to create:
PARTICLE_COUNT = 300
Burst Dataclass¶
We’ll need to add a time to our burst data. This will be a floating point number that represents the start-time of when the burst was created.
@dataclass
class Burst:
""" Track for each burst. """
buffer: arcade.gl.Buffer
vao: arcade.gl.Geometry
start_time: float
Update Burst Creation¶
Now when we create a burst, we need multiple particles, and each particle
also needs a velocity. In _gen_initial_data
we add a loop for each particle,
and also output a delta x and y.
Note: Because of how we set delta x and delta y, the particles will expand into a rectangle rather than a circle. We’ll fix that on a later step.
Because we added a velocity, our buffer now needs two pairs of floats 2f 2f
named in_pos
and in_vel
. We’ll update our shader in a bit to work with the
new values.
Finally, our burst object needs to track the time we created the burst.
1 def on_mouse_press(self, x: float, y: float, button: int, modifiers: int):
2 """ User clicks mouse """
3
4 def _gen_initial_data(initial_x, initial_y):
5 """ Generate data for each particle """
6 for i in range(PARTICLE_COUNT):
7 dx = random.uniform(-.2, .2)
8 dy = random.uniform(-.2, .2)
9 yield initial_x
10 yield initial_y
11 yield dx
12 yield dy
13
14 # Recalculate the coordinates from pixels to the OpenGL system with
15 # 0, 0 at the center.
16 x2 = x / self.width * 2. - 1.
17 y2 = y / self.height * 2. - 1.
18
19 # Get initial particle data
20 initial_data = _gen_initial_data(x2, y2)
21
22 # Create a buffer with that data
23 buffer = self.ctx.buffer(data=array('f', initial_data))
24
25 # Create a buffer description that says how the buffer data is formatted.
26 buffer_description = arcade.gl.BufferDescription(buffer,
27 '2f 2f',
28 ['in_pos', 'in_vel'])
29 # Create our Vertex Attribute Object
30 vao = self.ctx.geometry([buffer_description])
31
32 # Create the Burst object and add it to the list of bursts
33 burst = Burst(buffer=buffer, vao=vao, start_time=time.time())
34 self.burst_list.append(burst)
Set Time in on_draw¶
When we draw, we need to set “uniform data” (data that is the same for all points) that says how many seconds it has been since the burst started. The shader will use this to calculate particle position.
def on_draw(self):
""" Draw everything """
self.clear()
# Set the particle size
self.ctx.point_size = 2 * self.get_pixel_ratio()
# Loop through each burst
for burst in self.burst_list:
# Set the uniform data
self.program['time'] = time.time() - burst.start_time
# Render the burst
burst.vao.render(self.program, mode=self.ctx.POINTS)
Update Vertex Shader¶
Our vertex shader needs to be updated. We now take in a uniform float
called
time. Uniform data is set once, and each vertex in the program can use it.
In our case, we don’t need a separate copy of the burst’s start time for each
particle in the burst, therefore it is uniform data.
We also need to add another vector of two floats that will take in our velocity.
We set in_vel
in Update Burst Creation.
Then finally we calculate a new position based on the time and our particle’s
velocity. We use that new position when setting gl_Position
.
1#version 330
2
3// Time since burst start
4uniform float time;
5
6// (x, y) position passed in
7in vec2 in_pos;
8
9// Velocity of particle
10in vec2 in_vel;
11
12// Output the color to the fragment shader
13out vec4 color;
14
15void main() {
16
17 // Set the RGBA color
18 color = vec4(1, 1, 1, 1);
19
20 // Calculate a new position
21 vec2 new_pos = in_pos + (time * in_vel);
22
23 // Set the position. (x, y, z, w)
24 gl_Position = vec4(new_pos, 0.0, 1);
25}
Program Listings¶
vertex_shader_v2.glsl Full Listing ← Where we are right now
vertex_shader_v2.glsl Diff ← What we changed to get here
gpu_particle_burst_03.py Full Listing ← Where we are right now
gpu_particle_burst_03.py Diff ← What we changed to get here
Step 4: Random Angle and Speed¶

Step 3 didn’t do a good job of picking a velocity, as our particles expanded into a rectangle rather than a circle. Rather than just pick a random delta x and y, we need to pick a random direction and speed. Then calculate delta x and y from that.
Update Burst Creation¶
Now, pick a random direction from zero to 2 pi radians. Also, pick a random speed. Then use sine and cosine to calculate the delta x and y.
1 def on_mouse_press(self, x: float, y: float, button: int, modifiers: int):
2 """ User clicks mouse """
3
4 def _gen_initial_data(initial_x, initial_y):
5 """ Generate data for each particle """
6 for i in range(PARTICLE_COUNT):
7 angle = random.uniform(0, 2 * math.pi)
8 speed = random.uniform(0.0, 0.3)
9 dx = math.sin(angle) * speed
10 dy = math.cos(angle) * speed
11 yield initial_x
12 yield initial_y
13 yield dx
14 yield dy
15
Program Listings¶
gpu_particle_burst_04.py Full Listing ← Where we are right now
gpu_particle_burst_04.py Diff ← What we changed to get here
Step 5: Gaussian Distribution¶

Setting speed to a random amount makes for an expanding circle. Another option is to use a gaussian function to produce more of a ‘splat’ look:
speed = abs(random.gauss(0, 1)) * .5
Program Listings¶
gpu_particle_burst_05.py Full Listing ← Where we are right now
gpu_particle_burst_05.py Diff ← What we changed to get here
Step 6: Add Color¶

So far our particles have all been white. How do we add in color? We’ll need to generate it for each particle. Shaders take colors in the form of RGB floats, so we’ll generate a random number for red, and add in some green to get our yellows. Don’t add more green than red, or else you get a green tint.
Finally, pass in the three floats as in_color
to the shader buffer (VBO).
1 def on_mouse_press(self, x: float, y: float, button: int, modifiers: int):
2 """ User clicks mouse """
3
4 def _gen_initial_data(initial_x, initial_y):
5 """ Generate data for each particle """
6 for i in range(PARTICLE_COUNT):
7 angle = random.uniform(0, 2 * math.pi)
8 speed = abs(random.gauss(0, 1)) * .5
9 dx = math.sin(angle) * speed
10 dy = math.cos(angle) * speed
11 red = random.uniform(0.5, 1.0)
12 green = random.uniform(0, red)
13 blue = 0
14 yield initial_x
15 yield initial_y
16 yield dx
17 yield dy
18 yield red
19 yield green
20 yield blue
21
22 # Recalculate the coordinates from pixels to the OpenGL system with
23 # 0, 0 at the center.
24 x2 = x / self.width * 2. - 1.
25 y2 = y / self.height * 2. - 1.
26
27 # Get initial particle data
28 initial_data = _gen_initial_data(x2, y2)
29
30 # Create a buffer with that data
31 buffer = self.ctx.buffer(data=array('f', initial_data))
32
33 # Create a buffer description that says how the buffer data is formatted.
34 buffer_description = arcade.gl.BufferDescription(buffer,
35 '2f 2f 3f',
36 ['in_pos', 'in_vel', 'in_color'])
37 # Create our Vertex Attribute Object
38 vao = self.ctx.geometry([buffer_description])
39
40 # Create the Burst object and add it to the list of bursts
41 burst = Burst(buffer=buffer, vao=vao, start_time=time.time())
42 self.burst_list.append(burst)
Then, update the shader to use the color instead of always using white:
1#version 330
2
3// Time since burst start
4uniform float time;
5
6// (x, y) position passed in
7in vec2 in_pos;
8
9// Velocity of particle
10in vec2 in_vel;
11
12// Color of particle
13in vec3 in_color;
14
15// Output the color to the fragment shader
16out vec4 color;
17
18void main() {
19
20 // Set the RGBA color
21 color = vec4(in_color[0], in_color[1], in_color[2], 1);
22
23 // Calculate a new position
24 vec2 new_pos = in_pos + (time * in_vel);
25
26 // Set the position. (x, y, z, w)
27 gl_Position = vec4(new_pos, 0.0, 1);
28}
Program Listings¶
vertex_shader_v3.glsl Full Listing ← Where we are right now
vertex_shader_v3.glsl Diff ← What we changed to get here
gpu_particle_burst_06.py Full Listing ← Where we are right now
gpu_particle_burst_06.py Diff ← What we changed to get here
Step 7: Fade Out¶

Right now the explosion particles last forever. Let’s get them to fade out.
Once a burst has faded out, let’s remove it from burst_list
.
Constants¶
First, let’s add a couple constants to control the minimum and maximum tile to fade a particle:
MIN_FADE_TIME = 0.25
MAX_FADE_TIME = 1.5
Update Init¶
Next, we need to update our OpenGL context to support alpha blending. Go
back to the __init__
method and update the enable_only
call to:
self.ctx.enable_only(self.ctx.BLEND)
Add Fade Rate to Buffer¶
Next, add the fade rate to the VBO:
1 def on_mouse_press(self, x: float, y: float, button: int, modifiers: int):
2 """ User clicks mouse """
3
4 def _gen_initial_data(initial_x, initial_y):
5 """ Generate data for each particle """
6 for i in range(PARTICLE_COUNT):
7 angle = random.uniform(0, 2 * math.pi)
8 speed = abs(random.gauss(0, 1)) * .5
9 dx = math.sin(angle) * speed
10 dy = math.cos(angle) * speed
11 red = random.uniform(0.5, 1.0)
12 green = random.uniform(0, red)
13 blue = 0
14 fade_rate = random.uniform(1 / MAX_FADE_TIME, 1 / MIN_FADE_TIME)
15
16 yield initial_x
17 yield initial_y
18 yield dx
19 yield dy
20 yield red
21 yield green
22 yield blue
23 yield fade_rate
24
25 # Recalculate the coordinates from pixels to the OpenGL system with
26 # 0, 0 at the center.
27 x2 = x / self.width * 2. - 1.
28 y2 = y / self.height * 2. - 1.
29
30 # Get initial particle data
31 initial_data = _gen_initial_data(x2, y2)
32
33 # Create a buffer with that data
34 buffer = self.ctx.buffer(data=array('f', initial_data))
35
36 # Create a buffer description that says how the buffer data is formatted.
37 buffer_description = arcade.gl.BufferDescription(buffer,
38 '2f 2f 3f f',
39 ['in_pos',
40 'in_vel',
41 'in_color',
42 'in_fade_rate'])
43 # Create our Vertex Attribute Object
44 vao = self.ctx.geometry([buffer_description])
45
46 # Create the Burst object and add it to the list of bursts
47 burst = Burst(buffer=buffer, vao=vao, start_time=time.time())
48 self.burst_list.append(burst)
Update Shader¶
Update the shader. Calculate the alpha. If it is less that 0, just use 0.
1#version 330
2
3// Time since burst start
4uniform float time;
5
6// (x, y) position passed in
7in vec2 in_pos;
8
9// Velocity of particle
10in vec2 in_vel;
11
12// Color of particle
13in vec3 in_color;
14
15// Fade rate
16in float in_fade_rate;
17
18// Output the color to the fragment shader
19out vec4 color;
20
21void main() {
22
23 // Calculate alpha based on time and fade rate
24 float alpha = 1.0 - (in_fade_rate * time);
25 if(alpha < 0.0) alpha = 0;
26
27 // Set the RGBA color
28 color = vec4(in_color[0], in_color[1], in_color[2], alpha);
29
30 // Calculate a new position
31 vec2 new_pos = in_pos + (time * in_vel);
32
33 // Set the position. (x, y, z, w)
34 gl_Position = vec4(new_pos, 0.0, 1);
35}
Remove Faded Bursts¶
Once our burst has completely faded, no need to keep it around. So in our
on_update
remove the burst from the burst_list after it has been faded.
1 def on_update(self, dt):
2 """ Update game """
3
4 # Create a copy of our list, as we can't modify a list while iterating
5 # it. Then see if any of the items have completely faded out and need
6 # to be removed.
7 temp_list = self.burst_list.copy()
8 for burst in temp_list:
9 if time.time() - burst.start_time > MAX_FADE_TIME:
10 self.burst_list.remove(burst)
Program Listings¶
vertex_shader_v4.glsl Full Listing ← Where we are right now
vertex_shader_v4.glsl Diff ← What we changed to get here
gpu_particle_burst_07.py Full Listing ← Where we are right now
gpu_particle_burst_07.py Diff ← What we changed to get here
Step 8: Add Gravity¶
You could also add come gravity to the particles by adjusting the velocity based on a gravity constant. (In this case, 1.1.)
// Adjust velocity based on gravity
vec2 new_vel = in_vel;
new_vel[1] -= time * 1.1;
// Calculate a new position
vec2 new_pos = in_pos + (time * new_vel);
Program Listings¶
vertex_shader_v5.glsl Full Listing ← Where we are right now
vertex_shader_v5.glsl Diff ← What we changed to get here
Bundling a Game with PyInstaller¶
Note
You must have Arcade version 2.4.3 or greater and Pymunk 5.7.0 or greater for the instructions below to work.
You’ve written your game using Arcade and it is a masterpiece! Congrats! Now you want to share it with others. That usually means helping people install Python, downloading the necessary modules, copying your code, and then getting it all working. Sharing is not an easy task. Well, PyInstaller can change all that!
PyInstaller is a tool for Python that lets you bundle up an entire Python application into a one-file executable bundle that you can easily share. Thankfully, it works great with Arcade!
Bundling a Simple Arcade Script¶
To demonstrate how PyInstaller works, we will:
install PyInstaller
create a simple example application that uses Arcade
bundle the application into a one-file executable
run the application
First, make sure both Arcade and PyInstaller are installed in your Python environment with:
pip install arcade pyinstaller
Then create a file called myscript.py
that contains the following:
import arcade
arcade.open_window(400, 400, "My Script")
arcade.start_render()
arcade.draw_circle_filled(200, 200, 100, arcade.color.BLUE)
arcade.finish_render()
arcade.run()
Now, create a one-file executable bundle file by running PyInstaller:
pyinstaller myscript.py --onefile
PyInstaller generates the executable that is a bundle of your game. It puts it in the dist\
folder under your current working directory. Look for a
file named myscript.exe
in dist\
. Run this and see the example application start up!
You can copy this file wherever you want on your computer and run it. Or, share it with others. Everything your script needs is inside this executable file.
For simple games, this is all you need to know! But, if your game loads any kind of data files from disk, continue reading.
Handling Data Files¶
When creating a bundle, PyInstaller first examines your project and automatically identifies nearly everything your project needs (a Python interpreter,
installed modules, etc). But, it can’t automatically determine what data files your game is loading from disk (images, sounds,
maps). So, you must explicitly tell PyInstaller about these files and where it should put them in the bundle.
This is done with PyInstaller’s --add-data
flag:
pyinstaller myscript.py --add-data "stripes.jpg;."
The first item passed to --add-data
is the “source” file or directory (ex: stripes.jpg
) identifying what
PyInstaller should include in the bundle. The item after the semicolon is the “destination” (ex: “.
”), which
specifies where files should be placed in the bundle, relative to the bundle’s root. In the example
above, the stripes.jpg
image is copied to the root of the bundle (“.
”).
After instructing PyInstaller to include data files in a bundle, you must make sure your code loads the data files from the correct directory. When you share your game’s bundle, you have no control over what directory the user will run your bundle from. This is complicated by the fact that a one-file PyInstaller bundle is uncompressed at runtime to a random temporary directory and then executed from there. This document describes one simple approach that allows your code to execute and load files when running in a PyInstaller bundle AND also be able to run when not bundled.
You need to do two things. First, the snippet below must be placed at the beginning of your script:
if getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS'):
os.chdir(sys._MEIPASS)
This snippet uses sys.frozen
and sys._MEIPASS
, which are both set by PyInstaller. The sys.frozen
setting
indicates whether code is running from a bundle (“frozen”). If the code is “frozen”, the working
directory is changed to the root of where the bundle has been uncompressed to (sys._MEIPASS
). PyInstaller often
uncompresses its one-file bundles to a directory named something like: C:\Users\user\AppData\Local\Temp\_MEI123456
.
Second, once the code above has set the current working directory, all file paths in your code can be relative
paths (ex: resources\images\stripes.jpg
) as opposed to absolute paths (ex:
C:\projects\mygame\resources\images\stripes.jpg
). If you do these two things and add data files to
your package as demonstrated below, your code will be able to run “normally” as well as running in a bundle.
Below are some examples that show a few common patterns of how data files can be included in a PyInstaller bundle.
The examples first show a code snippet that demonstrates how data is loaded (relative path names), followed by the
PyInstaller command to copy data files into the bundle. They all assume that the os.chdir()
snippet
of code listed above is being used.
One Data File¶
If you simply have one data file in the same directory as your script, refer to the data file using a relative path like this:
sprite = arcade.Sprite("stripes.jpg")
Then, you would use a PyInstaller command like this to include the data file in the bundled executable:
pyinstaller myscript.py --add-data "stripes.jpg;."
...or...
pyinstaller myscript.py --add-data "*.jpg;."
One Data Directory¶
If you have a directory of data files (such as images
), refer to the data directory using a relative path like this:
sprite = arcade.Sprite("images/player.jpg")
sprite = arcade.Sprite("images/enemy.jpg")
Then, you would use a PyInstaller command like this to include the directory in the bundled executable:
pyinstaller myscript.py --add-data "images;images"
Multiple Data Files and Directories¶
You can use the --add-data
flag multiple times to add multiple files and directories into the bundle:
pyinstaller myscript.py --add-data "player.jpg;." --add-data "enemy.jpg;." --add-data "music;music"
One Directory for Everything¶
Although you can include every data file and directory with separate --add-data
flags, it is suggested
that you write your game so that all of your data files are under one root directory, often named resources
. You
can use subdirectories to help organize everything. An example directory tree could look like:
project/
|--- game.py
|--- resources/
|--- images/
| |--- enemy.jpg
| |--- player.jpg
|--- sound/
| |--- game_over.wav
| |--- laser.wav
|--- text/
|--- names.txt
With this approach, it becomes easy to bundle all your data with just a single --add-data
flag. Your code
would use relative pathnames to load resources, something like this:
sprite = arcade.Sprite("resources/images/player.jpg")
text = open("resources/text/names.txt").read()
And, you would include this entire directory tree into the bundle like this:
pyinstaller myscript.py --add-data "resources;resources"
It is worth spending a bit of time to plan out how you will layout and load your data files in order to keep the bundling process simple.
The technique of handling data files described above is just one approach. If you want more control and flexibility in handling data files, learn about the different path information that is available by reading the PyInstaller Run-Time Information documentation.
Now that you know how to install PyInstaller, include data files, and bundle your game into an executable, you have what you need to bundle your game and share it with your new fans!
Troubleshooting¶
Use a One-Folder Bundle for Troubleshooting¶
If you are having problems getting your bundle to work properly, it may help to temporarily
omit the --onefile
flag from the pyinstaller
command. This will bundle your
game into a one-folder bundle with an executable inside it. This allows you to inspect
the contents of the folder and make sure all of the files are where you expect them
to be. The one-file bundle produced by --onefile
is simply a
self-uncompressing archive of this one-folder bundle.
PyInstaller Not Bundling a Needed Module¶
In most cases, PyInstaller is able to analyze your project and automatically determine
what modules to place in the bundle. But, if PyInstaller happens to miss a module, you can use
the --hidden-import MODULENAME
flag to explicitly instruct PyInstaller to include a module. See the
PyInstaller documentation
for more details.
Extra Details¶
You will notice that after running
pyinstaller
, a.spec
file will appear in your directory. This file is generated by PyInstaller and does not need to be saved or checked into your source code repo.Executable one-file bundles produced by PyInstaller’s
--onefile
flag will start up slower than your original application or the one-folder bundle. This is expected because one-file bundles are ultimately just a compressed folder, so they must take time to uncompress themselves each time the bundle is run.By default, when PyInstaller creates a bundled application, the application opens a console window. You can suppress the creation of the console window by adding the
--windowed
flag to thepyinstaller
command.See the PyInstaller documentation below for more details on the topics above, and much more.
PyInstaller Documentation¶
PyInstaller is a flexible tool that can handle a wide variety of different situations. For further reading, here are links to the official PyInstaller documentation:
PyInstaller home page: http://www.pyinstaller.org/
PyInstaller Manual: https://pyinstaller.readthedocs.io/en/stable/
Diverse Coders¶
If you are female, trans, or non-binary, there are a lot of coders like you! But sometimes it can be hard to find them.
Check out the following organizations.
PyLadies holds charity auctions at PyCon every year, raising over $20,000 every year to help support and promote coding amongst women.
If you are interested in getting started coding, or contributing to the Python community, reach out to me or any of the organizations above.
Release Notes¶
Keep up-to-date with the latest changes to the Arcade library by the release notes.
Version 2.5.7¶
Fixes¶
The arcade gui should now respect the current viewport
Fixed an issue with UILabel allocating large amounts of textures over time consuming a lot of memory
Fixed an issue with the initial viewport sometimes being 1 pixel too small causing some artifacts
Fixed a race condition in
Sound.stop()
sometimes causing a crashFixed an issue in requirements causing issues for poetry
Fixed an error reporting issue when reaching maximum texture size
New Features¶
replit.com
Arcade should now work out of the box on replit.com. We detect when arcade runs in replit tweaking various settings. One important setting we disable is antialiasing since this doesn’t work well with software rendering.
Alternative Garbage Collection of OpenGL Resources
arcade.gl.Context
now supports an alternative garbage collection mode more
compatible with threaded applications and garbage collection of OpenGL resources.
OpenGL resources can only be accessed or destroyed from the same thread the
window was created. In threaded applications the python garbage collector
can in some cases try to destroy OpenGL objects possibly causing a hard crash.
This can be configured when creating the arcade.Window
passing in a new
gc_mode
parameter. By default his parameter is "auto"
providing
the default garbage collection we have in python.
Passing in "context_gc"
on the other hand will move all “dead” OpenGL
objects into Context.objects
. These can be garbage collected manually
by calling Context.gc()
in a more controlled way in the the right thread.
Version 2.5.6¶
Version 2.5.6 was released 2021-03-28
Fix issue with PyInstaller and Pymunk not allowing Arcade to work with bundling
Update some example code. Highlight PyInstaller instructions
Version 2.5.5¶
Version 2.5.5 was released 2021-02-23
Version 2.5.4¶
Version 2.5.4 was released 2021-02-19
Add fishy game on example page
Fix but around framebuffer creation not properly restoring active frame buffer
Fix for but where TextureRenderTarget creates FBO twice
Updated pinned version numbers for dependent libraries
MyPy fixes
Minor improvements around SpriteList list operations
Version 2.5.3¶
Version 2.5.3 was released 2021-01-27
Fix/improve tetris example
Fix for camera2d.scroll_x
Version 2.5.2¶
Version 2.5.2 was released 2020-12-27
Improve schedule/unschedule docstrings
Fix Sound.get_length
Raise error if there are multiple instances of a streaming source
Fix background music example to match new sound API
Update main landing page for docs
Split sprite platformer tutorial into multiple pages
Add ‘related projects’ page
Add ‘adventure’ sample game link
Add resources for top-down tank images
Add turn-and-move example
Fix name of sandCorner_left.png
Update tilemap to error out instead of continuing if we can’t find a tile
Improve view tutorial
Generate error rather than warning if we can’t find image or sound file
Specify timer resolution in Windows
Version 2.5.1¶
Version 2.5.1 was released 2020-12-14
Fix bug with sound where panning wasn’t working on Windows machines.
Fix some type-checking errors found by mypy.
Update API docs
Version 2.5¶
Version 2.5 was released 2020-12-09
(Note, libraries Arcade depends on do not work yet with Python 3.9 on Mac. Mac users will need to use Python 3.6, 3.7 or 3.8.)
General
SpriteList.draw now supports a blend_function parameter. This opens up for drawing sprites with different blend modes.
Bugfix: Sprite hit box didn’t properly update when changing width or height
GUI improvements (eruvanos needs to elaborate)
Several examples was improved
Improvements to the pyinstaller tutorial
Better pin versions of depended libraries
Fix issues with simple and platformer physics engines.
Advanced
Added support for tessellation shaders
arcade.Window
now takes agl_version
parameter so users can request a higher OpenGL version than the default(3, 3)
version. This only be used to advanced users.Bugfix: Geometry’s internal vertex count was incorrect when using an index buffer
We now support 8, 16 and 32 bit index buffers
Optimized several draw methods by omitting
tobytes()
letting the buffer protocol do the workMore advanced examples was added to
arcade/experimental/examples
Documentation
Add Conway’s Game of Life example showing how to use alpha to control display of sprites in a grid.
Improve documentation around sound API.
Improve documentation with FPS and timing stats example Display Performance Statistics.
Improve moving platform docs a bit in Simple Platformer tutorial.
Version 2.4.3¶
Version 2.4.3 was released 2020-09-30
General
Added PyInstalled hook and tutorial
ShapeLists should no longer share position between instances
GUI improvements: new UIImageToggle
Low level rendering api (arcade.gl):
ArcadeContext now has a load_texture method for creating opengl textures using Pillow.
Bug: Fixed an issue related to drawing indexed geometry with offset
Bug: Scissor box not updating when using framebuffer
Bug: Fixed an issue with pack/unpack alignment for textures
Bug: Transforming geometry into a target buffer should now work with byte offset
Bug: Duplicate sprites in ‘check_for_collision_with_list’ Issue #763
Improved docstrings in arcade.gl
Version 2.4.2¶
Version 2.4.2 was released 2020-09-08
Enhancement:
draw_hit_boxes
new method inSpriteList
.Enhancement:
draw_points
now significantly fasterAdded UIToggle, on/off switch
Add example showing how to do GPU transformations with the mouse
Create buttons with default size/position so size can be set after creation.
Allow checking if a sound is done playing Issue 728
Add an early camera mock-up
Add
finish
method toarcade.gl.context
.New example arcade.experimental.examples.3d_cube (experimental)
New example arcade.examples.camera_example
Improved UIManager.unregister_handlers(), improves multi view setup
Update
preload_textures
method ofSpriteList
to actually pre-load texturesGUI code clean-up Issue 723
Update downloadable .zip for for platformer example code to match current code in documentation.
Bug Fix:
draw_point
calculates wrong point sizeFixed draw_points calculates wrong point size
Fixed create_line_loop for thickness !=
Fixed pixel scale for offscreen framebuffers and read()
Fixed SpriteList iterator is stateful
Fix for pixel scale in offscreen framebuffers
Fix for UI tests
Fix issues with FBO binding
Cleanup Remove old examples and code
Version 2.4¶
Arcade 2.4.1 was released 2020-07-13.
Arcade version 2.4 is a major enhancement release to Arcade.









Version 2.4 Major Features¶
Support for defining your own frame buffers, shaders, and more advanced OpenGL programming. New API in arcade.gl package.
Support to render to frame buffer, then re-render. Example: Mini-Map Defender.
Use frame buffers to create a ‘glow’ or ‘bloom’ effect: Bloom-Effect Defender.
Use frame-buffers to support lights: Lighting Demo.
New support for style-able GUI elements. New API in arcade.gui package.
Example: GUI Elements
Example: GUI Custom Style
PyMunk engine for platformers. See tutorial: Pymunk Platformer.
AStar algorithm for finding paths. See
astar_calculate_path
andAStarBarrierList
.For an example of using the A-Star algorithm, see A-Star Path Finding.
Version 2.4 Minor Features¶
New functions/classes:
Added get_display_size() to get resolution of the monitor
Added Window.center_window() to center the window on the monitor.
Added has_line_of_sight() to calculate if there is line-of-sight between two points.
Added SpriteSolidColor class that makes a solid-color rectangular sprite.
Added SpriteCircle class that makes a circular sprite, either solid or with a fading gradient.
Added
get_distance
function to get the distance between two points.
New functionality:
Support for logging. See Logging.
Support volume and pan arguments in play_sound
Add ability to directly assign items in a sprite list. This is particularly useful when re-ordering sprites for drawing.
Support left/right/rotated sprites in tmx maps generated by the Tiled Map Editor.
Support getting tmx layer by path, making it less likely reading in a tmx file will have directory confusion issues.
Add in font searching code if we can’t find default font when drawing text.
Added
arcade.Sprite.draw_hit_box
method to draw a hit box outline.The
arcade.Texture
class,arcade.Sprite
class, andarcade.tilemap.process_layer
take inhit_box_algorithm
andhit_box_detail
parameters for hit box calculation.

hit_box_algorithm = “None”¶

hit_box_algorithm = “Simple”¶

hit_box_algorithm = “Detailed”¶
Version 2.4 Under-the-hood improvements¶
General
Simple Physics engine is less likely to ‘glitch’ out.
Anti-aliasing should now work on windows if
antialiasing=True
is passed in the window constructor.Major speed improvements to drawing of shape primitives, such as lines, squares, and circles by moving more of the work to the graphics processor.
Speed improvements for sprites including gpu-based sprite culling (don’t draw sprites outside the screen).
Speed improvements due to shader caching. This should be especially noticeable on Mac OS.
Speed improvements due to more efficient ways of setting rendering states such as projection.
Speed improvements due to less memory copying in the lower level rendering API.
OpenGL API
A brand new low level rendering API wrapping OpenGL 3.3 core was added in this release. It’s loosely based on the ModernGL API, so ModernGL users should be able to pick it up fast. This API is used by arcade for all the higher level drawing functionality, but can also be used by end users to really take advantage of their GPU. More guides and tutorials around this is likely to appear in the future.
A simplified list of features in the new API:
A
Context
andarcade.ArcadeContext
object was introduced and can be found through thewindow.ctx
property. This object offers methods to create opengl resources such as textures, programs/shaders, framebuffers, buffers and queries. It also has shortcuts for changing various context states. When working with OpenGL in arcade you are encouraged to usearcade.gl
instead ofpyglet.gl
. This is important as the context is doing quite a bit of bookkeeping to make our life easier.New
Texture
class supporting a wide variety of formats such as 8/16/32 bit integer, unsigned integer and float values. New convenient methods and properties was also added to change filtering, repeat mode, read and write data, building mipmaps etc.New
Buffer
class with methods for manipulating data such as simple reading/writing and copying data from other buffers. This buffer can also now be bound as a uniform buffer object.New
Framebuffer
wrapper class making us able to render any content into one more more textures. This opens up for a lot of possibilities.The
Program
has been expanded to support geometry shaders and transform feedback (rendering to a buffer instead of a screen). It also exposes a lot of new properties due to much more details introspection during creation. We also able to assign binding locations for uniform blocks.A simple glsl wrapper/parser was introduced to sanity check the glsl code, inject preprocessor values and auto detect out attributes (used in transforms).
A higher level type
Geometry
was introduced to make working with shaders/programs a lot easier. It supports using a subset of attributes defined in your buffer description by inspecting the the program’s attributes generating and caching compatible variants internally.A
Query
class was added for easy access to low level measuring of opengl rendering calls. We can get the number samples written, number of primitives processed and time elapsed in nanoseconds.Added support for the buffer protocol. When
arcade.gl
requires byte data we can also pass objects like numpy array of pythonsarray.array
directly not having to convert this data to bytes.
Version 2.4 New Documentation¶
New Tutorial: Pymunk Platformer
New Tutorial: Using Views for Start/End Screens
New Tutorial: Solitaire
New Tutorial: GPU Particle Burst
Several new and updated examples on How-To Example Code
A lot of improvements to https://learn.arcade.academy
Instructional videos added to for https://learn.arcade.academy
Version 2.4 ‘Experimental’¶
There is now an arcade.experimental
module that holds code still under
development. Any code in this module might still have API changes.
Special Thanks¶
Special thanks to Einar Forselv and Maic Siemering for their significant work in helping put this release together.
Version 2.3.15¶
Release Date: Apr-14-2020
Version 2.3.14¶
Release Date: Apr-9-2020
Bug Fix: Another attempt at fixing sprites with different dimensions added to same SpriteList didn’t display correctly Issue 630
Add lots of unit tests around Sprites and texture loading.
Version 2.3.13¶
Release Date: Apr-8-2020
Bug Fix: Sprites with different dimensions added to same SpriteList didn’t display correctly Issue 630
Version 2.3.12¶
Release Date: Apr-8-2020
Enhancement: Support more textures in a SpriteList Issue 332
Version 2.3.11¶
Release Date: Apr-5-2020
Bug Fix: Fix procedural_caves_bsp.py
Bug Fix: Improve Windows install docs Issue 623
Version 2.3.10¶
Release Date: Mar-31-2020
Bug Fix: Remove unused AudioStream and PlaysoundException from __init__
Remove attempts to load ffmpeg library
Add background music example
Bug Fix: Improve Windows install docs Issue 619
Add tutorial on edge artifacts Issue 418
Bug Fix: Can’t remove sprite from multiple lists Issue 621
Several documentation updates
Version 2.3.9¶
Release Date: Mar-25-2020
Bug Fix: Fix for calling SpriteList.remove Issue 613
Bug Fix: get_image not working correctly on hi-res macs Issue 594
Bug Fix: Fix for “shiver” in simple physics engine Issue 614
Bug Fix: Fix for create_line_strip Issue 616
Bug Fix: Fix for volume control Issue 610
Bug Fix: Fix for loading SoLoud under Win64 Issue 615
Fix jumping/falling texture in platformer example
Add tests for gui.theme Issue 605
Fix bad link to arcade.color docs
Version 2.3.8¶
Release Date: Mar-09-2020
Major enhancement to sound. Uses SoLoud cross-platform library. New features include support for sound volume, sound stop, and pan left/right.
Version 2.3.7¶
Release Date: Feb-27-2020
Version 2.3.6¶
Release Date: Feb-17-2020
Version 2.3.5¶
Release Date: Feb-12-2020
Bug Fix: Calling sprite.draw wasn’t drawing the sprite if scale was 1 Issue 575
Add unit test for Issue 575
Bug Fix: Changing sprite scale didn’t cause sprite to redraw in new scale Issue 588
Add unit test for Issue 588
Enhancement: Simplify using built-in resources Issue 576
Fix for failure on on_resize(), which pyglet was quietly ignoring
Update
rotate_point
function to make it more obvious it takes degrees
Version 2.3.3¶
Release Date: Feb-08-2020
Bug Fix: set_scale checks height rather than scale Issue 578
Bug Fix: Window flickers for drawing when not derived from Window class Issue 579
Enhancement: Allow joystick selection in dual-stick shooter Issue 571
Test coverage reporting now working correctly with TravisCI
Improved test coverage
Improved documentation and typing with Texture class
Improve minimal View example
Version 2.3.2¶
Release Date: Feb-01-2020
Remove scale as a parameter to load_textures because it is not unused
Improve documentation
Add example for acceleration/friction
Version 2.3.1¶
Release Date: Jan-30-2020
Don’t auto-update sprite hit box with animated sprite
Fix issues with sprite.draw
Improve error message given when trying to do a collision check and there’s no hit box set on the sprite.
Version 2.3.0¶
Release Date: Jan-30-2020
Backwards Incompatability: arcade.Texture no longer has a scale property. This property made things confusing as Sprites had their own scale attribute. This seemingly small change required a lot of rework around sprites, sprite lists, hit boxes, and drawing of textured rectangles.
Include all the things that were part of 2.2.8, but hopefully working now.
Bug Fix: Error when calling Sprite.draw() Issue 570
Enhancement: Added Sprite.draw_hit_box to visually draw the hit box. (Kind of slow, but useful for debugging.)
Version 2.2.9¶
Release Date: Jan-28-2020
Roll back to 2.2.7 because bug fixes in 2.2.8 messed up scaling
Version 2.2.8¶
Release Date: Jan-27-2020
Version number now contained in one file, rather than three.
Enhancement: Move several GitHub-listed enhancements to the .rst enhancement list
Bug Fix: Texture scale not accounted for when getting height Issue 516
Bug Fix: Issue with text cut off if it goes below baseline Issue 515
Enhancement: Allow non-cached texture creation, fixing issue with resizing Issue 506
Enhancement: Physics engine supports rotation
Bug Fix: Need to better resolve collisions so sprite doesn’t get hyper-spaces to new weird spot Issue 569
Bug Fix: Hit box not getting properly created when working with multi-texture player sprite. Issue 568
Bug Fix: Issue with text_sprite and anchor y of top Issue 567
Bug Fix: Issues with documentation
Version 2.2.7¶
Release Date: Jan-25-2020
Enhancement: Have draw_text return a sprite Issue 565
Enhancement: Improve speed when changing alpha of text Issue 563
Enhancement: Add dual-stick shooter example Issue 301
Bug Fix: Fix for Pyglet 2.0dev incompatability Issue 560
Bug Fix: Fix broken particle_systems.py example Issue 558
Enhancement: Added mypy check to TravisCI build Issue 557
Enhancement: Fix typing issues Issue 537
Enhancement: Optimize load font in draw_text Issue 525
Enhancement: Reorganize examples
Bug Fix: get_pixel not working on MacOS Issue 539
Version 2.2.6¶
Release Date: Jan-20-2020
Bug Fix: particle_fireworks example is not running with 2.2.5 Issue 555
Bug Fix: Sprite.pop isn’t reliable Issue 531
Enhancement: Raise error if default font not found on system Issue 432
Enhancement: Add space invaders clone to example list Issue 526
Enhancement: Add sitemap to website
Enhancement: Improve performance, error handling around setting a sprite’s color
Enhancement: Implement optional filtering parameter to SpriteList.draw Issue 405
Enhancement: Return list of items hit during physics engine update Issue 401
Enhancement: Update resources documentation Issue 549
Enhancement: Add on_update to sprites, which includes delta_time Issue 266
Enhancement: Close enhancement-related github issues and reference them in the new Enhancement List.
Version 2.2.5¶
Release Date: Jan-17-2020
Version 2.2.3¶
Release Date: Jan-12-2020
Bug fix: Hit boxes not getting updated with rotation and scaling. Issue 548 This update depricates Sprite.points and instead uses Sprint.hit_box and Sprint.get_adjusted_hit_box
Major internal change around not having
__init__
doimport *
but specifically name everything. Issue 537 This rearranded a lot of files and also reworked the quickindex in documentation.
Version 2.2.2¶
Release Date: Jan-09-2020
Bug fix: Arcade assumes tiles in tileset are same sized Issue 550
Version 2.2.1¶
Release Date: Dec-22-2019
Bug fix: Resource folder not included in distribution Issue 541
Version 2.2.0¶
Release Date: Dec-19-2019*
Major Enhancement: Add built-in resources support Issue 209 This also required many changes to the code samples, but they can be run now without downloading separate images.
Major Enhancement: Auto-calculate hit box points by trimming out the transparency
Major Enhancement: Sprite sheet support for the tiled map editor works now
Enhancement: Added
load_spritesheet
for loading images from a sprite sheetEnhancement: Updates to physics engine to better handle non-rectangular sprites
Enhancement: Add SpriteSolidColor class, for creating a single-color rectangular sprite
Enhancement: Expose type hints to modules that depend on arcade via PEP 561 Issue 533 and Issue 534
Enhancement: Add font_color to gui.TextButton init Issue 521
Enhancement: Improve error messages around loading tilemaps
Bug fix: Turn on vsync as it sometimes was limiting FPS to 30.
Bug fix: get_tile_by_gid() incorrectly assumes tile GID cannot exceed tileset length Issue 527
Bug fix: Tiles in object layers not placed properly Issue 536
Bug fix: Typo when loading font Issue 518
Updated
requirements.txt
fileAdd robots.txt to documentation
Please also update pyglet, pyglet_ffmpeg2, and pytiled_parser libraries.
Special tanks to Jon Fincher, Mr. Gallo, SirGnip, lubie0kasztanki, and EvgeniyKrysanoc for their contributions to this release.
Version 2.1.7¶
Version 2.1.6¶
Version 2.1.5¶
Fix: Default font sometimes not pulling on mac Issue 488
Documentation updates, particularly around examples for animated characters on platformers
Fix to Sprite class to better support character animation around ladders
Version 2.1.4¶
Fix: Error when importing arcade on Raspberry Pi 4 Issue 485
Fix: Transparency not working in draw functions Issue 489
Fix: Order of parameters in draw_ellipse documentation Issue 490
Raise better error on data classes missing
Lots of code cleanup from SirGnip Issue 484
New code for buttons and dialog boxes from wamiqurrehman093 Issue 476
Version 2.1.3¶
Version 2.1.1¶
Added pytiled-parser as a dependency in setup.py
Version 2.1.0¶
New file reader for tmx files http://arcade.academy/arcade.html#module-arcade.tilemap
Add new view switching framework http://arcade.academy/examples/index.html#view-management
Fix and Re-enable TravisCI builds https://travis-ci.org/pvcraven/arcade/builds
New: Collision methods to Sprite Issue 434
Fix: make_circle_texture Issue 431
Fix: Points drawn as triangles rather than rects Issue 429
Fix: Fix screen update rate issue Issue 424
Fix: Typo Issue 422
Put in exampel Kayzee game
Fix: Add links to PyCon video Issue 414
Fix: Docstring Issue 409
Fix: Typo Issue 403
Thanks to SirGnip, Mr. Gallow, and Christian Clauss for their contributions.
Version 2.0.9¶
Fix: Unable to specify path to .tsx file for tiled spritesheet Issue 360
Fix: TypeError: __init__() takes from 3 to 11 positional arguments but 12 were given in text.py Issue 373
Fix: Test create_line_strip Issue 379
Fix: TypeError: draw_rectangle_filled() got an unexpected keyword argument ‘border_width’ Issue 385
Fix: See about creating a localization/internationalization example Issue 391
Fix: Glitch when you die in the lava in 09_endgame.py Issue 392
Fix: No default font found on ArchLinux and no error message (includes patch) Issue 402
Fix: Update docs around batch drawing and array_backed_grid.py example Issue 403
Version 2.0.8¶
Add example code from lixingque
Fix: Drawing primitives example broke in prior release Issue 365
Update: Improve automated testing of all code examples Issue 326
Update: raspberry pi instructions, although it still doesn’t work yet
Fix: Some buffered draw commands not working Issue 368
Remove yml files for build environments that don’t work because of OpenGL
Update requirement.txt files
Fix mountain examples
Better error handling when playing sounds
Remove a few unused example code files
Version 2.0.7¶
Version 2.0.6¶
Version 2.0.5¶
Version 2.0.3¶
Version 2.0.2 was compiled off the wrong branch, so it had a bunch of untested code. 2.0.3 is what 2.0.2 was supposed to be.
Version 2.0.2¶
Version 2.0.0¶
Released 2019-03-10
Lots of improvements in 2.0.0. Too many to list, but the two main improvements:
Using shaders for sprites, making drawing sprites incredibly fast.
Using ffmpeg for sound.
Version 1.3.6¶
Released 2018-10-10
Bux fix for spatial hashing
Implement commands for getting a pixel, and image from screen
Version 1.3.4¶
Released 28-May-2018
New Features¶
Bug Fixes¶
Issue 159: Fix array backed grid buffer example
Issue 177: Kind of fix issue with gi sound library
Issue 180: Fix up API docs with sound
Issue 198: Add start of isometric tile support
Issue 210: Fix bug in MacOS sound handling
Issue 213: Update code with gi streamer
Issue 214: Fix issue with missing images in animated sprites
Issue 216: Fix bug with venv
Issue 222: Fix get_window when using a Window class
Version 1.3.3¶
Released 2018-May-05
New Features¶
Issue 184: For sound, wav, mp3, and ogg should work on Linux and Windows. wav and mp3 should work on Mac.
Updated Examples¶
Add happy face drawing example
Version 1.3.2¶
Released 2018-Apr-20
New Features¶
Updated Documentation¶
Link the class methods in the quick index to class method documentation
Add mountain midpoint displacement example
Improve CSS
Add “Two Worlds” example game
Version 1.3.1¶
Released 2018-Mar-31
New Features¶
Updated Documentation¶
Update quick-links on homepage of http://arcade.academy
Update Sprite class documentation
Update copyright date to 2018
Updated Examples¶
Update PyMunk example code to use keyboard constants rather than hard-coded values
New sample code showing how to avoid placing coins on walls when randomly placing them
Improve listing/organization of sample code
Work at improving sample code, specifically try to avoid using
all_sprites_list
Add PyMunk platformer sample code
Unsuccessful work at getting TravisCI builds to work
Add new sample for using shape lists
Create sample code showing difference in speed when using ShapeLists.
Issue 182: Use explicit imports in sample PyMunk code
Improve sample code for using a graphic background
Improve collect coins example
New sample code for creating caves using cellular automata
New sample code for creating caves using Binary Space Partitioning
New sample code for explosions
Version 1.3.0¶
Released 2018-February-11.
Version 1.2.5¶
Released 2017-December-29.
Version 1.2.3¶
Released 2017-December-20.
Bug Fixes¶
Enhancements¶
Issue 84: Allow quick running via -m
Issue 149: Need better error message with check_for_collision
Issue 151: Need example showing how to go between rooms
Issue 152: Standardize name of main class in examples
Issue 154: Improve GitHub compatibility
Issue 155: Improve readme documentation
Issue 156: Clean up root folder
Issue 162: Add documentation with performance tips
Issue 164: Create option for a static sprite list where we don’t check to see if things moved.
Issue 165: Improve error message with physics engine
Version 1.2.2¶
Released 2017-December-02.
Bug Fixes¶
Enhancements¶
Issue 147: Fix bug building documentation where two image files where specified incorrectly
Issue 146: Add release notes to documentation
Issue 144: Add code to get window and viewport dimensions
Issue 139: Add documentation on what
collision_radius
isIssue 131: Add example code on how to do full-screen games
Issue 113: Add example code showing enemy turning around when hitting a wall
Issue 67: Improved support and documentation for joystick/game controllers
How to Contribute¶
We would love to have you contribute to the project! There are several ways that you can do so.
How to contribute without coding¶
Community - Post your projects, code, screen-shots, and discuss the Arcade library on the Python Arcade SubReddit.
Try coding your own animations and games. Write down notes on anything that is difficult to implement or understand about the library.
Suggest improvements - Post bugs and enhancement requests at the Github Issue List.
How to contribute code¶
First, take some time to understand the project layout:
Then you can improve these parts of the project:
Document - Edit the reStructuredText and docstrings to make the Arcade documentation better.
Test - Improve the unit testing.
Code - Contribute bug fixes and enhancements to the code.
A list of enhancements people have requested is available on the Enhancement List page.
Arcade Performance Information¶
Drawing primitive performance: Drawing lines, rectangles, and circles can be slow. If you are encountering this, you can speed things up by using ShapeElement lists where you batch together the drawing commands.
Sprite drawing performance: If your sprites don’t move, when creating the
sprite list set is_static
to True. This allows Arcade to load the sprites to
the graphics card and just keep them there.
Collision detection performance: If it is taking too long to detect collisions
between sprites, make sure that the SpriteList with all the sprites has
use_spatial_hasing
turned on.
Directory Structure¶
Directory |
Description |
---|---|
\arcade |
Source code for the arcade library. |
\arcade\color |
Submodule with predefined colors. |
\arcade\key |
Submodule with keyboard id codes. |
\build |
All built code from the compile script goes here. |
\dist |
Distributable Python wheels go here after the build script has run. |
\doc |
Arcade documentation. Note that API documentation is in docstrings along with the source. |
\doc\source |
Source rst files for the documentation. |
\doc\source\examples |
All the example code |
\doc\source\images |
Images used in the documentation. |
\doc\build\html |
After making the documentation, all the HTML code goes here. Look at this in a web browser to see what the documentation will look like. |
\examples |
Example code showing how to use Arcade. |
\tests |
Unit tests. Most unit tests are part of the docstrings. |
Also see How to Compile.
How to Compile¶
Windows¶
Prep your system by getting the needed Python packages, listed in the
requirements.txt
file.
Create your own fork of the repository, and then clone it on your computer.
From the base directory, there is a “make” batch file that can be run with a number of different arguments, some of them listed here:
make full
- This compiles the source, installs the package on the local computer, compiles the documentation, and runs all the unit tests.make doc
- This compiles the documentation. Resulting documentation will be indoc/build/html
.make fast
- This compiles and installs the source code. It does not create the documentation or run any unit tests.make test
- This runs the tests.make
- Displays all the arguments “make” supports.
Note: Placing test programs in the root of the project folder will pull from the source code in the arcade library, rather than the library installed in the Python interpreter. This is helpful because you can avoid the compile step. Just make sure not to check in your test code.
Linux¶
Create your own fork of the repository, and then clone it on your computer.
Prep your system by downloading the needed packages:
sudo apt-get install python-dev
How to Submit Changes¶
First, you should open up an issue or enhancement request on the Github Issue List.
Next, create your own fork of the Arcade library. The Arcade library is at:
https://github.com/pvcraven/arcade
Follow the How to Compile on how to build the code.
You can submit changes with a “pull request.” With a pull request you ask that another repository (in this case the Arcade library) “pull” your changes into the main code base.
If you aren’t familiar with how to do pull requests, the Stack Overflow discussion on pull requests is good.
There are two Continuous Integration machines building changes to the code base. Windows builds are done on AppVeyor. Linux build testing is done on TravisCI.
Enhancement List¶
This is a list of possible enhancements opened in GitHub, but not being actively worked on. These are all good ideas. If you are thinking of helping the Arcade library by working on one of these, please re-open the issue.
Drawing¶
Issue 283 Add ability to draw overlapping polygons with same transparency.
Issue 421 Add support for drawing rounded rectangles.
Issue 433 Add support for bitmapped fonts.
Issue 595 Optimize drawing functions by caching them
After updating to Pyglet 2.x, update text drawing to use Pyglet’s text drawing rather than the Pillow text drawing.
Explore using frame buffers to overlay non-moving text and icons on a scrolling screen.
Add support for Pyglets ImageMouseCursors
GUI¶
Explore using frame buffers to create windows
Sprites¶
Issue 291 Load a spritesheet with a texture_region map.
Issue 377 Support basic sprite animations like rotate, flicker, disappear, etc.
Issue 380 Add ability to specify a point the sprite rotates around.
Issue 419 Create function to get sprites at a particular point.
Issue 498 Add lighting effects.
Add bloom effects.
Issue 523 Add sprite trigger/example for onenter / onexit.
Issue 289 Be able to get Sprite position and velocity as vectors.
Be able to load an animated gif as an animated time-based sprite.
Be able to load an Aesprite image directly (Piggy-back of Pyglet support)
Physics Engine¶
Tilemaps¶
Documentation¶
Issue 452 Documentation Request - explain how delta_time works to help learners fully understand both how and why.
Examples¶
Issue 345 Create example showing how to manage multiple windows.
Issue 371 Create example showing how to bounce items off walls.
Issue 397 Add example code showing how to do parallax.
Issue 446 Add more procedural generation examples.
Issue 464 Add example for checkers-like game.
Add example showing how to use lerp or smoothstep for smooth camera panning.
Edge Artifacts¶
When working with images, particularly ones with transparency, graphics cards can create graphic artifacts on their edges. Images can have ‘borders’ where they aren’t wanted. For example, here there’s a line on the top and left:

Why does this happen? How do we fix it?
Why Edge Artifacts Appear¶
This happens when the edge of an image does not fall cleanly onto an image.
Edge Mis-Alignment¶
Typically edge artifacts happen when the edge of an image doesn’t land on an exact pixel boundary. Below in Figure 1, the left image is 128 pixels square and drawn at (100, 100), and looks fine. The image on the right is drawn with a center of (100, 300.5) and has an artifact that shows up as a line on the left edge. That artifact will not appear if the sprite is drawn at (100, 300) instead of (100, 300.5)

Figure 1: Edge artifacts caused by images that aren’t on integer pixel boundaries.¶
The left edge falls on a coordinate of 300.5 - (128/2) = 236.5. The computer tries to select a color that’s an average between 236 and 237, but since there is no 237 we get a dark color. Typically this only happens if the edge is transparent.
A shape that has a height or width that is not evenly divisible by two can also cause artifacts. If the shape is 15 pixels wide, then the center will fall between the 7th and 8th pixel making it harder to line up the pixels to the screen.
Scaling¶
Scaling an image can also cause artifacts. In Figure 2, the second sprite is scaled down by two-thirds. Since 128 pixels doesn’t evenly scale down by two-thirds, we end up with edge artifacts. If we had scaled down by one-half, that is possible to do with 128 pixels (to 64), so there would be no artifacts.
The third image in Figure 2 is scaled up by a factor of two. The edge spans two pixels and we end up with a line artifact as well. (Scaling down by two usually works if the image is divisible by four. Scaling up typically doesn’t.)

Figure 2: Edge artifacts caused by scaling.¶
Rotating¶
With rotation, it can be very difficult to get pixels lined up, and edge artifacts are common.
Solutions¶
Keeping sprite sizes to a power of
two will help. For pixel-art types of games, using the GL_NEAREST
filter can
also help.
Use The Power of Two¶
Keep all sprite dimensions a power of two. Such as 2, 4, 8, 16, 32, 64, 128, 256, etc. It is ok if they aren’t square, such as a 32x64 pixel is fine.
Don’t scale up, only scale down. Also, only scale down in powers of two.
Aligning to the Nearest Pixel¶
By default, Arcade draws sprites with a filter called “linear” which makes for smoother scaling and lines. If instead you want a pixel-look, you can use a different filter called “nearest.” This filter also reduces issues with edge artifacts. First, import the filters at the top of your program:
from pyglet.gl import GL_NEAREST
from pyglet.gl import GL_LINEAR
Then, in your on_draw
update the drawing of your sprites with the filter:
def on_draw(self):
self.my_sprite_list.draw(filter=GL_NEAREST)
Double-Check Viewport Code¶
Double-check your viewport code to make sure the edges are only set to integers and the size of the window matches up exactly, without any off-by-one errors.
Logging¶
Arcade has a few options to log additional information around timings and how things are working internally. The two major ways to do this by turning on logging, and by querying the OpenGL context.
Turn on logging¶
The quickest way to turn on logging is to add this to the start of your main program file:
arcade.configure_logging()
This will cause the Arcade library to output some basic debugging information:
2409.0003967285156 arcade.sprite_list DEBUG - [386411600] Creating SpriteList use_spatial_hash=True is_static=False
2413.9978885650635 arcade.gl.context INFO - Arcade version : 2.4a5
2413.9978885650635 arcade.gl.context INFO - OpenGL version : 3.3
2413.9978885650635 arcade.gl.context INFO - Vendor : NVIDIA Corporation
2413.9978885650635 arcade.gl.context INFO - Renderer : GeForce GTX 980 Ti/PCIe/SSE2
2413.9978885650635 arcade.gl.context INFO - Python : 3.7.4 (tags/v3.7.4:e09359112e, Jul 8 2019, 19:29:22) [MSC v.1916 32 bit (Intel)]
2413.9978885650635 arcade.gl.context INFO - Platform : win32
3193.9964294433594 arcade.sprite_list DEBUG - [386411600] _calculate_sprite_buffer: 0.013532099999999936 sec
Custom Log Configurations¶
If you want to add your own logging, or change the information printed in the log, you can do it with just a bit more code.
First, in your program import the logging library:
import logging
The code to turn on logging looks like this:
logging.basicConfig(level=logging.DEBUG)
You can get even more information by using a formatter to add time, file name, and even line number information to your output:
format = '%(asctime)s,%(msecs)03d %(levelname)-8s [%(filename)s:%(lineno)d %(funcName)s()] %(message)s'
logging.basicConfig(format=format,
datefmt='%H:%M:%S',
level=logging.DEBUG)
…which changes the output to look like:
13:40:50,226 DEBUG [sprite_list.py:720 _calculate_sprite_buffer()] [365177904] _calculate_sprite_buffer: 0.00849660000000041 sec
13:40:50,398 DEBUG [ui_element.py:58 on_mouse_over()] UIElement mouse over
You can add logging to your own programs by putting one of these lines at the top of your program:
# Get your own logger
LOG = logging.getLogger(__name__)
# or get Arcade's logger
LOG = logging.getLogger('arcade')
Then, any time you want to print, just use:
LOG.debug("This is my debug statement.")
Getting OpenGL Stats Using Query Objects¶
If you’d like more information on the time it takes to draw, you can query
the OpenGL context arcade.Window.ctx
as this example shows:
def on_draw(self):
""" Render the screen. """
arcade.start_render()
query = self.ctx.query()
with query:
# Put the drawing commands you want to get info on here:
self.my_sprite_list.draw()
print()
print(f"Time elapsed : {query.time_elapsed:,} ns")
print(f"Samples passed : {query.samples_passed:,}")
print(f"Primitives created : {query.primitives_generated:,}")
The output from this looks like the following:
Time elapsed : 7,136 ns
Samples passed : 390,142
Primitives created : 232
Social Media¶
Discord - The most active spot
Reddit /r/pythonarcade The next most active spot
Twitter @ArcadeLibrary Good for announcements
Instagram @PythonArcadeLibrary
Facebook