Introduction to Game Development in Python (part 2)

Arpit Omprakash
Byte-Sized-Code
Published in
11 min readMay 25, 2020

In the previous article, we took a look at a simple Pygame program that displays “Hello World”. We got to know some of the basic terms in game development like Game Loops, Events, and Event Handling. Today’s read covers the Objects and Drawing part of Pygame. Without further ado, here is the simplest guide to Pygame Graphics and Objects.

Pixel Coordinates

The window of the “Hello World” program is just composed of little square dots on your screen called pixels. Each pixel starts off as black but can be set to a different color. The screen looked like this:

A simple Pygame window

If you remember, the Surface object was about 400 pixels wide and 300 pixels tall. Instead, if we had a Surface object that was 8 pixels by 8 pixels, an enlarged picture of this tiny surface would look like this:

The Surface (or window) has 64 pixels arranged in a grid with 8 rows and 8 columns. The image above adds two axes and a zero-indexed numbering to make the representation look better.
A specific pixel can be located using the Cartesian Coordinate system. Each column of the X-axis and each row of the Y-axis will have an “address” that is an integer from 0 to 7 so that we can locate any pixel by specifying the X and Y axis integers.

For example, in the above image, we can see that the pixels at XY coordinates (4,0), (2,2), (0,5), and (5,6) have been painted black, the pixel at (2,4) has been painted gray, while all the other pixels are white. XY coordinates are also called points. Someone who knows the Cartesian Coordinates can easily notice that the Y-axis starts at 0 at the top and increases going down, rather than how it normally decreases going down. This is just how Coordinates work in Pygame (and almost every programming language).

The Pygame framework often represents Cartesian Coordinates as a tuple of two integers, such as (3,0). The first integer represents the X coordinate and the second is the Y coordinate.

Surface Objects and The Window

Surface objects are objects that represent a rectangular 2D image. The pixels of the Surface object can be changed by the Pygame drawing functions (described later) and then displayed on the screen. The window border, title bar, and buttons are not part of the display Surface object.

In particular, the Surface object returned by pygame.display.set_mode() is called the display Surface. Anything that is drawn on the display Surface object will be displayed on the window when the pygame.diaplay.update() function is called.

Your program will draw several different things to a Surface object. Once you’re done drawing everything on the display Surface object for this iteration of the game loop (called a frame, just like a still image in a paused video) on a Surface object, it can be drawn to the screen. The computer can draw frames very quickly, and our programs will often run around 30 frames per second (that is, 30 FPS). This is called the “frame rate” and is explained in the next post.

Colors

There are three primary colors of light: red, green, and blue. By combining different amounts of these three colors you can form any other color. In Pygame, we represent colors with tuples of three integers. The first value indicates how much red is in the color. An integer of 0 means there is no red in the color, and a value of 255 means there is the maximum amount of red in the color. The second and third values denote the amount of green and blue respectively. These tuples of three integers used to represent a color are often called RGB values.

As the values can vary from 0 to 255 for each primary color, this means Pygame supports 16,777,216 different colors (256 x 256 x 256). However, any value greater than 255 or less than 0 results in an error that looks like this: ValueError: invalid color argument .

Some playing around with the numbers yields different colors. For example, (0, 0, 0) indicates the absence of any color. The result is the color black. (255, 255, 255) means all the primary colors are present in their maximum amount. This results in white. Similarly, (255, 0, 0), (0, 255, 0), and (0, 0, 255) represent red, green, and blue respectively.

Here is a table for the values of a few common colors:

╔═══════════╦═════════════════╗
║ Color ║ RGB Values ║
╠═══════════╬═════════════════╣
║ Aqua ║ (0, 255, 255) ║
║ Black ║ (0, 0, 0) ║
║ Blue ║ (0, 0, 255) ║
║ Fuchsia ║ (255, 0, 255) ║
║ Gray ║ (128, 128, 128) ║
║ Green ║ (0, 128, 0) ║
║ Lime ║ (0, 255, 0) ║
║ Maroon ║ (128, 0, 0) ║
║ Navy Blue ║ (0, 0, 128) ║
║ Olive ║ (128, 128, 0) ║
║ Purple ║ (128, 0, 128) ║
║ Red ║ (255, 0, 0) ║
║ Silver ║ (192, 192, 192) ║
║ Teal ║ (0, 128, 128) ║
║ White ║ (255, 255, 255) ║
║ Yellow ║ (255, 255, 0) ║
╚═══════════╩═════════════════╝

Note: (0, 255, 0) is actually Lime green, and (0, 128, 0) is the lighter version we see as green.

Transparent Colors

Not only can you control the color of a pixel, but you can also control the opacity of the color. This can be done by adding a fourth value to the tuple of color.
This value is known as the alpha value. It is a measure of how opaque (that is, not transparent) a color is. Normally when you draw a color on a pixel, it is completely opaque and thus, the color replaces any other color that was present below it. But with colors that have an alpha value, the upper color (the color you add last) will just add a colored tint over the previous color.

For example, the tuple of three integers for red is: (255, 0, 0). But if we add a fourth integer for the alpha value, we can make this a half-transparent red color: (255, 0, 0, 128). An alpha value of 255 denotes an opaque color and alpha value of 0 means the color is completely transparent (and thus, invisible), i.e., (255, 0, 0) is the same as (255, 0, 0, 255), and (0, 0, 0) is same as (255, 0, 0, 0).

Tints and Colours

In order to draw using transparent colors, you must create a Surface object with the convert_alpha() method. For example, the following code creates a Surface object that transparent colors can be drawn on:

surfaceObject  = DISPLAYSURF.convert_alpha()

It’s important to note that you cannot use transparent colors on Surface objects not returned from a convert_alpha() call, including the display Surface that was returned from pygame.display.set_mode() .

pygame.Color Objects

Pygame provides objects to denote colors. You can create color objects by calling the pygame.Color() constructor function and passing a tuple for the desired color. You can store this object in tuples just like you can store tuples in variables. Try typing the following in the interactive shell:

>>> import pygame
>>> pygame.Color(255, 0, 0)
(255, 0, 0, 255)
>>> myColor = pygame.Color(255, 0, 0, 128)
>>> myColor == (255, 0, 0, 255)
True
>>>

It is just a matter of choice and personal preference as any drawing function in Pygame that has a parameter for color can have either the tuple form or Color object form of color passed for it. Now that you know all about colors and coordinates, let’s learn about pygame.Rect objects so we can start using Pygame’s drawing functions.

Rect Objects

Pygame has two ways to represent rectangular areas. The first is a tuple of four integers:

  • The X coordinate of the top left corner
  • The Y coordinate of the top left corner
  • The width (in pixels) of the rectangle
  • The height (in pixels) of the rectangle

The second way is as a pygame.Rect object, which we will call Rect objects for short. For example, the code below creates a Rect object with a top left corner at (10, 20) that is 200 pixels wide and 300 pixels tall:

>>> import pygame
>>> spamRect = pygame.Rect(10, 20, 200, 300)
>>> spamRect == (10, 20, 200, 300)
True

The benefit of using Rect object is that it automatically calculates and manipulates stuff for you. For example, the Rect.right property can be used to know the X coordinate of the right edge of the Rect object we just created. Also, we can reassign the location of the right edge, and the other object attributes are recalculated automatically.

>>> spamRect.right
210
>>> spamRect.right = 350
>>> spamRect.left
150

Here is a list of all the attributes that pygame.Rect objects provide:

Primitive Drawing Functions

Pygame provides several different functions for drawing different shapes onto a Surface object. These shapes such as rectangles, circles, ellipses, lines, or individual pixels are often called drawing primitives. Save the following code in a file and execute it:

Drawing in Pygame

When the program is run, the following window appears:

The drawn image

Notice how we make constant variables for the colors that we use. This improves code readability and also makes sure that we don’t have to deal with the nefarious RGB values again and again while changing color for a given part of the code.

The drawing functions are named after the shapes they draw. The parameters you pass these functions tell them which surface to draw on, where to draw the shape (and what size), in what color, and how wide to make the lines. You can see how these functions are called in the pygame_drawing.py program. Here is a short description of each function:

  • fill(color) — The fill() method is not a function but a method of pygame.Surface objects. It will completely fill the entire Surface object with whatever color value you pass as for the color parameter.
  • pygame.draw.polygon(surface, color, pointlist, width) — This function draws a polygon. The surface and color parameters tell the functions on what surface to draw and in which color.
    The pointlist parameter is a tuple or list of points. The polygon is drawn by drawing lines between each point and the point that comes after it in the tuple. Then a line is drawn from the last point to the first point. You can also pass a list of points instead of a tuple of points.
    The width parameter is optional. If you leave it out, the polygon drawn will be filled with color (as is the case with our pentagon). If you do pass the parameter, an outline of the shape with the given width is drawn. A width of “0” fills the polygon.
    All of the pygame.draw drawing functions have the optional width parameter at the end.
  • pygame.draw.line(surface, color, start_point, end_point, width) — This function draws a line between the start_point and end_point parameters.
  • pygame.draw.lines(surface, color closed, pointlist, width) — This function draws a series of lines from one point to the next, much like the polygon function above. The only difference is that if you pass False for the closed parameter, there will not be a line form the last point to the first point.
  • pygame.draw.circle(surface, color, center_point, radius, width) — This function draws a circle. The center of the circle is at the center_point parameter. The integer passed for the radius parameter sets the size of the circle.
  • pygame.draw.ellipse(surface, color, bounding_rectangle, width) — This function draws an ellipse (oval shape). This function has all the usual parameter, but in order to tell the function of how large and where to draw the ellipse, you must specify the bounding rectangle of the ellipse. A bounding rectangle is the smallest rectangle that can be drawn around the shape. Here’s an example of a bounding rectangle:
Bounding rectangle of an ellipse
  • pygame.draw.rect(surface, color, rectangel_tuple, width) — This function draws a rectangle. The rectangle_tuple is either a tuple of four integers or a pygame.Rect object can be passed instead.

pygame.PixelArray Objects

Unfortunately, there isn’t a single function you can call that will set a single pixel to a color (unless you call a line drawing function with the same start and endpoint). The Pygame framework needs to run some code behind the scenes before and after drawing to a Surface object. If it had to do this for every single pixel you wanted to set, your program would run much slower.

Instead, you should create a pygame.PixelArray object (we’ll call them PixelArray objects from now on). Creating a PixelArray object of a Surface object will “lock” the Surface object. While a Surface object is locked, the drawing functions can still be called on it, but it cannot have images like PNG or JPG images drawn on it with the blit() method (I’ll discuss this and animations in a later article).

If you want to see if a Surface object is locked, the get_locked() Surface method will return True if it is locked and False if it is not.

The PixelArray object that is returned from pygame.PixelArray() can have individual pixels set by accessing them with two indexes. For example, line 28’s pixObj[480][380] = BLACK will set the pixel at X coordinate 480 and Y coordinate 380 to black.

To tell Pygame that you are finished drawing individual pixels, delete the PixelArray object with a del statement. This is what line 33 does. Deleting the PixelArray object will “unlock” the Surface object. If you forget to delete the PixelArray object, the blit() command will raise an error that looks like this: pygame.error: Surfces must not be locked during blit .

pygame.display.update() Function

After you are done calling the drawing functions to make the display Surface object look the way you want, you must call pygame.diaplay.update() to make the display Surface actually appear on the user’s monitor.

The one thing that you must remember is that this function will only make the display Surface (that is, the Surface object that was returned from the call to pygame.display.set_mode() ) appear on the screen.

Epilogue

We learned about the following in this article:

  • The coordinate system of Pygame
  • Colors and Color objects
  • Surfaces and Windows in Pygame
  • Rectangle objects in Pygame
  • Drawing in Pygame

Now you are ready to draw your own shapes and images on the canvas of Pygame. Here are a few images for you to try out using pygame:

A simple building
A chessboard in Pygame
Santa Claus in Pygame

This article served as a continuation of the previous one to enrich your knowledge about the Pygame framework. Soon, I’ll post another article that dives deeper into the parts of the animation of the Pygame library. Follow to get notified of the next article on Pygame. If you found this article interesting or helpful, do clap, share, and recommend it!

--

--

Arpit Omprakash
Byte-Sized-Code

I'm a Programming and Statistics enthusiast studying Biology. To find out if we have common interests, have a look at my thoughts: https://aceking007.github.io/