Get started with PyGame to Create Video Games

Michael Gold
8 min readDec 6, 2023

--

Archer in a Field generated by Dall-E

Introduction

In an era where video games are becoming increasingly sophisticated, there’s a unique charm in simpler, more focused games. One such game is the Archer Game, developed using Pygame, a set of Python modules designed for writing video games. This game combines basic graphics with engaging mechanics to provide an enjoyable and challenging experience.

The Essence of the Game

The Archer Game is a skill-based game where players control an archer aiming to hit a target. The game’s mechanics are straightforward yet captivating: an archer shoots arrows at a target, and the player must time their shots with a moving bar to determine the arrow’s accuracy. After the Archer has fired 10 arrows, the game ends and displays the final score. After the player runs out of arrows, they have the option of trying again or quitting the game.

Key Components of the Game

1. Setting Up the Environment

The game is developed in Python, utilizing the Pygame library. Pygame offers the necessary tools for creating game windows, handling user inputs, and managing game graphics. If you haven’t already, you’ll need to download and install the latest python from the python.org site. Then you’ll want to install pygame using pip.

pip install pygame

The Framework

The skeleton framework of a Pygame game, such as the Archer Game, consists of several key components that work together to create a smooth and responsive gaming experience. These components include initializing Pygame, setting up the game window, handling events, updating game states, rendering graphics, and managing the frame rate. Let’s break down each part:

1. Initializing Pygame

At the start of the game, Pygame needs to be initialized. This step prepares the Pygame library to be used and typically involves setting up the game window and other necessary resources.

import pygame

pygame.init() # Initialize Pygame
width, height = 800, 600
screen = pygame.display.set_mode((width, height)) # Set up the display window
pygame.display.set_caption('Archery Game') # Set the window title

2. Setting Up the Game Clock

The game clock is crucial for controlling the frame rate, ensuring that the game runs at a consistent speed across different hardware.

clock = pygame.time.Clock()
fps = 60 # Frames per second

3. The Basic Event Loop

Every Pygame application has an event loop that continuously checks for events (like keyboard inputs or the window closing) and responds accordingly. This loop is the heart of the game’s interactivity.

running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
# Handle other events like key presses, mouse movement, etc.
pygame.display.flip() # Refresh the frame
clock.tick(fps) # specifies the maximum number of times the loop runs per second

4. Updating Game State

Within the loop, the game’s state is updated. This can include moving game characters, calculating physics, checking for collisions, and other game logic.

    # Update game states
arrow.update() # update the arrow as it moves across the screen
score.update() # update the score when the arrow hits the target
# For example, move arrow, check for collisions with target, update scores, etc.

5. Rendering

In the rendering step of the game loop, the visual representation of the game’s current state is drawn onto the screen. This is an essential part of the loop, as it visually communicates the state of the game to the player. Here’s how it typically works in a Pygame game like the Archer Game:

a. Clearing the Screen: At the beginning of each frame, it’s common to clear the screen to prepare for the new frame’s drawing. This is often done by filling the entire screen with a solid color. In the Archer Game, this color is a greyish tone.

   # Fill the screen with a grey color
screen.fill(BACKGROUND_COLOR)

b. Drawing Game Components: After clearing the screen, you draw the game components in their current state. This includes characters, targets, UI elements, and any other visual elements. The order of drawing matters — objects drawn later will appear on top of those drawn earlier.

# Example: Drawing game elements
target.draw(screen)
archer.draw(screen)
arrow.draw(screen)
score.draw(screen)
# More drawing code for other elements...

c. Refreshing the Frame: Once all the game components have been drawn onto the screen, the display needs to be updated to reflect these changes. In Pygame, this is typically done using pygame.display.flip(). This function updates the entire surface of the display. Alternatively, pygame.display.update() can be used to update only a portion of the screen, but flip() is more common when the entire frame changes (which is usually the case in games).

pygame.display.flip()  # Refresh the frame

6. Controlling the Frame Rate

Finally, the game clock is used to ensure that the loop runs at the desired frame rate. This keeps the game running smoothly and consistently.

    # Control the frame rate
clock.tick(fps)

Sprites

To enhance the organization of your code, it’s best to leverage Pygame’s Sprite classes. These classes provide a straightforward path to adopting an object-oriented approach to game design, which is particularly fitting since games inherently revolve around objects. Take the Archer Game as an example: it features a myriad of objects including an archer, an arrow, a target, a score display, an arrow count display, and a moving slider. Even the marks left on the target can be represented as Sprites.

Sprites follow a basic structure comprising a constructor, an update method, and a draw method. While additional methods can be added to enrich their functionality, these three core methods are generally all you need. This structure not only simplifies the game development process but also aligns well with the object-centric nature of games.

As an example, here is what our target sprite looks like:

class Target(pygame.sprite.Sprite):
def __init__(self, position, radius):
super().__init__()
colors = [WHITE, BLACK, BLUE, RED, YELLOW]
self.position = position
self.radius = radius
self.image = pygame.Surface((radius*2, radius*2), pygame.SRCALPHA)
self.rect = self.image.get_rect(center=position)
for i, color in enumerate(colors):
pygame.draw.circle(self.image, color, (self.radius, self.radius), self.radius - i * 10)

def draw(self, screen):
screen.blit(self.image, self.rect)

The Target sprite is designed with functionality that hinges on its constructor, which accepts position and radius as parameters. Utilizing these parameters, the constructor crafts a target image by rendering concentric circles in varying colors. This process effectively creates a visual representation of a target. The sprite's draw method is then responsible for displaying this internal image on the screen. It accomplishes this by blitting the image to the screen surface, which is provided by the main game event loop. This method ensures that the target is properly rendered and visible in the game, reflecting its current state and position within the game world.

Target Sprite

The Archer Sprite and Animation

The Archer sprite in the game is designed to display an animation of shooting an arrow from a bow. To achieve this fluid motion, we employ an image strip (also known as a sprite sheet) that contains a sequence of images. Each image in this strip represents a different frame of the archer’s shooting action. The frames collectively depict the archer going through the motions of drawing, aiming, and finally releasing an arrow.

To animate the Archer sprite effectively, we utilize Pygame’s capability to extract and display individual frames from an image strip sequentially. This approach enables us to showcase the archer in action, as follows:

  1. Image Strip Processing: The image strip, a single image file, contains multiple frames of the archer’s shooting motion laid out in a row. Each frame captures a different stage of the action.
  2. Extracting Frames: We use Pygame to divide this strip into separate frames. Each frame is essentially a smaller image that represents a specific moment in the archer’s shooting sequence.
  3. Sequential Display: During the game, these extracted frames are displayed one after the other at regular intervals. This creates the illusion of motion, similar to how traditional animation works.
  4. Timing the Frames: The timing of frame changes is crucial. By controlling the duration each frame is displayed, we can make the archer’s motion appear faster or slower, thereby achieving a realistic animation speed.
  5. Integration in the Game Loop: This animation process is integrated into the game loop, ensuring that the archer’s motion is updated and rendered in each iteration of the loop in which the archer is firing an arrow.

Here is the method in the Archer Sprite for extracting an array of all the frames from a sprite strip:

    def get_frames(self, image, frame_width, frame_height, num_frames):
frames = []
for i in range(num_frames):
frame = image.subsurface(pygame.Rect(i * frame_width, 0, frame_width, frame_height))
frames.append(frame)
return frames

To animate the Archer, we’ll create an animate method in the Archer Sprite. This method will use an animation_counter, to control the speed in which the next frame is displayed in the game loop.

    def animate(self):        
if self.animation_counter >= self.animation_speed:
self.current_frame += 1
self.animation_counter = 0 # Reset counter after updating frame
self.animation_counter += 1

Conclusion

Developing the Archer Game has been a learning experience that nicely illustrates the blending of programming skill and creative game design using Python and Pygame. Throughout our discussion, we have touched upon various integral aspects of game development, each contributing to the creation of a cohesive and engaging gaming experience.

Key takeaways from our exploration so far include:

  1. Pygame Skeleton Framework: We discussed the skeleton framework of a Pygame application, highlighting the importance of initializing Pygame, setting up the game window, handling events in the game loop, updating game states, rendering graphics, and managing frame rate. This framework forms the backbone of the game’s operation, ensuring smooth and responsive gameplay.
  2. Rendering Process and Game Loop Structure: The game loop, with its structured approach to event handling, state updating, and rendering, is crucial in the game’s functionality. The rendering process ensures that each game state is visually depicted on the screen, contributing to an immersive gaming experience.
  3. Frame Rate Management: Employing a game clock to regulate the frame rate is vital for the game’s consistency and performance across different hardware platforms.
  4. Effective Use of Pygame’s Sprite Class: The game makes strategic use of Pygame’s Sprite classes to represent key elements like the archer, arrow, and target. This approach not only streamlines code management but also enhances the game’s structural integrity and scalability.
  5. Animating the Archer Sprite: We delved into the animation process of the archer using an image strip, showcasing how sequential frame rendering can create dynamic and fluid motion. This animation is pivotal in providing an interactive and visually appealing experience for the player.

In conclusion, the development of the Archer Game stands as a testament to the effectiveness of combining solid programming techniques with imaginative game design. This project not only serves as a practical showcase of Pygame’s capabilities but also as an encouraging example for budding game developers to start their own ventures into the realm of game creation. Stay tuned as we dig deeper into other aspects of pyGame programming.

If you are interested in creating your own video games and want to explore PyGame some more, check out my book: Creating Video Games Using PyGame

--

--