Introduction to Pygame
What is Pygame?
Pygame is a cross-platform set of Python modules designed for creating video games and multimedia applications. It provides low-level access to audio, keyboard, mouse, and display functions, enabling developers to create fully featured games and multimedia programs in Python. One of Pygame's key advantages is its simplicity, making it highly popular among beginner and experienced developers alike.
Pygame is free and open-source, which means anyone can use, modify, and distribute it. The library offers hundreds of pre-built commands that handle complex tasks like rendering graphics, managing game states, and playing sounds. This extensive set of commands makes it easy for programmers to create even complex games without diving deeply into hardware-level programming.
Despite its extensive features, Pygame requires only a small amount of code to get started. Developers can quickly prototype and build games with just a few lines of code, allowing for rapid development and iteration. Pygame is also highly portable, working seamlessly on almost all operating systems, including Windows, macOS, and Linux.
Getting started
How to install Pygame?
To use Pygame, Python must first be installed on your system. Pygame requires Python 3.x or higher. You can check if Python is installed by opening the terminal or command prompt and entering the command python --version
.
Note: We recommend using virtual environments for this.
If a version such as Python 3.10.11 is displayed, you are all set!
Installing Pygame using pip
Note: If you are using virtual environments (as suggested and supported by PyCharm and other IDEs), you will need to separately install Pygame for every new project like described here.
Pygame can be installed using Python’s package manager pip. Open a terminal or command prompt and enter the following command: pip install pygame
. This command downloads and installs Pygame on your system.
To verify the installation, you can run a built-in Pygame example. Open a terminal and run
python -m pygame.examples.aliens
. If a window opens with a sample game, the installation was successful. Otherwise, an error message will indicate what went wrong.
Installing Pygame in PyCharm
If you are using PyCharm as your integrated development environment (IDE), you can easily install Pygame directly from within PyCharm.
Follow these steps:
- Open PyCharm and either create a new Python project or open an existing one.
- Click on the button Python Packages on the left corner of PyCharm.
- Type "pygame" into the search box.
- Click on the first result and then select install. After that, click on the newest version to install.
- You can now close the Packages window.
- Verify Installation: After installation, you can verify that Pygame is working correctly. Open a new Python file in PyCharm and enter the following code:
import pygame
pygame.init()
print("Pygame successfully installed!")
- Run the code by right-clicking in the editor and selecting Run. If no errors occur and you see the message "Pygame successfully installed!" in the console, the installation was successful.
Initialization & Setup
Before using any Pygame functions, the module must be imported into your Python script. This is done with the simple command import pygame
at the top of your code. After importing, all necessary Pygame modules must be initialized before starting the main game routine. This is accomplished using the pygame.init()
function, which sets up all imported Pygame modules for use. If you only need specific modules, you can initialize them individually, such as pygame.font.init()
or pygame.mixer.init()
to initialize fonts or the mixer, respectively.
To display graphics, the next step is to create a window. This is achieved using the command pygame.display.set_mode()
. For instance, screen = pygame.display.set_mode((800, 600))
creates a window with a width of 800 pixels and a height of 600 pixels. The returned object screen represents the surface where all images, shapes, and text can be drawn.
To set a title for the window, use the pygame.display.set_caption()
function. For example, the command pygame.display.set_caption("My First Game")
sets the window’s title to "My First Game".
Here is a simple example that demonstrates the basic initialization of Pygame:
import pygame
# Initialize Pygame modules
pygame.init()
# Set up the game window
screen = pygame.display.set_mode((800, 600))
pygame.display.set_caption("My first Pygame window")
# Main game loop
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
# Quit Pygame
pygame.quit()
In this code example, pygame.init()
is called to initialize all Pygame modules. The pygame.display.set_mode()
function creates a game window, and pygame.display.set_caption()
gives the window a title. The game’s main loop continuously monitors events in the game and specifically checks if the window was closed.
Remember to call pygame.quit() at the end of your script to ensure all Pygame resources are correctly released.
Also, in Pygame it’s essential to set a framerate for controlling the speed of the game and ensuring smooth animations. To set up a framerate you need to use the clock object, that Pygame provides: clock = pygame.time.Clock()
.
To set your desired framerate, use the clock.tick(fps)
method in your Main Game Loop to limit the loop to a certain number of frames per second. For most games, 60 fps will do a good job. For example: clock.tick(60)
Introduction to Main Game Loop
What is the Main Game Loop?
Introduction
- A game loop runs continuously during gameplay, acting as the "heartbeat". Each turn of the loop processes user input, updates the game state, and renders the game on the screen. by tracking the passage of time it controls the game's pace and maintains a smooth experience.
- Every game, from simple to complex, runs on a loop that keeps the game alive by Handling Events and player input, Updating Game Elements (like character movement or game score) and Rendering on the screen. Then, the game repeats this cycle as quickly as possible, creating the effect of a moving, interactive world.
Framework
Creating a main loop
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
- while running:
- This is the flag that keeps the game loop running. As long as running is True, the game will keep updating and listening for events.
- pygame.event.get():
- the
event
module is used to handle all user inputs from keyboard presses, mouse clicks, and system events closing a window for example. This allows the game to know what kind of action the user is taking, enabling responses to actions like closing the game or pressing keys. - the
get()
function collects all events that occurred since the last cycle of the main game loop. - pygame.event.type:
- this is meant to check which type of event is taking action.
Event Handling
Event handling involves monitoring for actions triggered by the user, such as pressing a key, moving the mouse, or clicking a button. Each loop cycle listens for these events and responds accordingly.
Main roles of user input handling
- Player Control: Allows players to control characters or elements within the game, facilitating actions like movement, jumping, attacking, or using items.
- Interactivity: Enables interaction with the game environment, such as opening doors, picking up objects, or triggering events, making the game world feel responsive.
- Feedback and Immersion: Provides immediate feedback to players, enhancing immersion and engagement.
- Game Mechanics: User input influences gameplay mechanics, such as puzzle solving, combat systems, or navigation, ensuring that the game functions as intended.
- Accessibility: Implementing diverse input methods (keyboard, mouse, gamepad, touch) ensures that a wider range of players can engage with the game comfortably.
- Customization: Allows players to remap controls or adjust sensitivity, enhancing the gaming experience by catering to individual preferences.
Example of handling keyboard input
for event in pygame.event.get():
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE:
print("Space bar pressed")
Example of handling mouse input
mouse_x, mouse_y = pygame.mouse.get_pos()
if event.type == pygame.MOUSEBUTTONDOWN:
if event.button == 1: # Left mouse button
print(f"Mouse clicked at ({mouse_x}, {mouse_y})")
Common Functions and modules
pygame.event
- Purpose: as already mentioned this module is used to handel all types of user input.
- Usage example:
for event in pygame.event.get():
if event.type == pygame.QUIT: # Detects window close event
running = False # Ends the game loop
pygame.key
- Purpose: this module is similar to the
event
module, however here it focusses only on keyboard events, it involves some helpful functions likeget_pressed()
. It checks the state of all keys on the keyboard, returning a boolean list indicating which keys are pressed. - Usage:
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]: # If the left arrow key is pressed
player_x -= 5 # Move the player to the left
pygame.mouse
- Purpose: another input-handling module, the mouse module is meant to handle mouse events using functions like
get_pos()
returns mouse position coordinates andget_pressed()[int]
returns the mouse button state - Usage:
mouse_x, mouse_y = pygame.mouse.get_pos()
if pygame.mouse.get_pressed()[0]: # If the left mouse button is pressed
print("Mouse clicked at:", mouse_x, mouse_y)
Mouse Events
Mouse Events are handling user interactions like mouse button clicks, mouse movements and releases of the mouse buttons, which are a essential part of interactive elements such as menus or drag-and-drop actions.
Like every other Event, mouse events are part of the event handler.
Basic Event handler in PyGame:
run = True
while run:
for event in pygame.event.get(): #EVENT HANDLER
if event.type == pygame.QUIT:
run = False
To detect the most clicks with the event handler, PyGame offers you the MOUSEBUTTONDOWN
and MOUSEBUTTONUP
events.
run = True
while run:
for event in pygame.event.get()
if event.type == pygame.QUIT:
run = False
if event.type == pygame.MOUSEBUTTONUP:
#YourCode
if event.type == pygame.MOUSEBUTTONDOWN:
#YourCode
- MOUSEBUTTONDOWN for detecting the push of the mouse button.
- MOUSEBUTTONUP for detecting the release of the mouse button.
To head on a specific mouse button, add another if-branch to your MOUSEBUTTONUP
or MOUSEBUTTONDOWN
Event as if event.button == 1:
…
Add another variable to take control of the length of the mouse button push:
clicked = False
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
if event.type == pygame.MOUSEBUTTONUP:
clicked = True
if event.type == pygame.MOUSEBUTTONDOWN:
clicked = False
Alternatively implement the function pygame.mouse.get_pressed()
to get the state of the mouse buttons. This returns a sequence of Booleans representing the state of all mouse buttons. To check for a specific mouse button, add the index at the end of the function:
if pygame.mouse.get_pressed()[0] == True:
#Code
if pygame.mouse.get_pressed()[1] == True:
#Code
if pygame.mouse.get_pressed()[2] == True:
#Code
To get the position of the mouse, use the pygame.mouse.get_pos()
function. This will return the coordinates of the mouse in your game window. Together with the click detection, this function will allow you to add more features to your games.
Updating Game Objects
Main roles of updating game objects
- Real-time Behavior: Allows game objects to respond to player actions, environmental changes, and game logic.
- Animation and Movement: Enables smooth animations and movements, contributing to the overall visual appeal.
- Physics Simulation: Essential for physics calculations, including collisions, gravity, and forces.
- Game Logic: Allows objects to check for conditions or triggers that influence gameplay.
- AI Behavior: For non-player characters (NPCs), updating is crucial for implementing AI decisions, pathfinding, and reactions to player actions, which makes the game world feel alive.
- Resource Management: Helps manage resources, such as timers for power-ups.
- Visual Effects and State Changes: Allows for visual effects and state changes, enhancing player feedback. (like damage indicators or environmental changes)
Example of updating game objects
if event.type == pygame.KEYDOWN: # Function to trigger events
if event.key == pygame.K_RETURN: # Check for Enter key
if is_facing_npc(player_pos, npc_pos):
print("It's a nice day")
Common Functions for Interacting with Game Objects
pygame.time.Clock()
- Purpose: Creates a clock object to control the game’s frame rate.
- Usage:
clock = pygame.time.Clock()
clock.tick(fps)
- Purpose: Controls the game loop’s frame rate to keep the game running at a consistent speed.
- Usage:
clock.tick(60) # Set the frame rate to 60 FPS
- Updating Object Positions and Properties
- Purpose: In games, characters or objects often have properties like position and speed, which can be updated each frame to create movement.
- Usage:
player_x += player_speed_x # Update player's x position
Draw to the screen (Rendering)
Main roles of rendering
- Visual Representation: Converts game data into visuals.
- Performance Optimization: Efficient rendering techniques, like level of detail (LOD), to help maintain smooth frame rates.
- Realism and Immersion: Enhances realism through shadows, reflections, etc.
- Dynamic Updates: Updates visuals based on gameplay changes.
- User Interface (UI): Renders UI elements.
- Artistic Style: Tailors visuals to specific artistic styles.
Example of rendering
screen.fill((0, 0, 0)) # Clear the screen
pygame.display.flip() # Update the screen
Common Functions
screen.fill(color)
- Purpose: Fills the background color to clear previous frame content.
- Usage:
screen.fill((0, 0, 0)) # Fill the screen with a black background
pygame.draw.rect()
andpygame.draw.circle()
- Purpose: Draws basic shapes like rectangles and circles.
- Usage:
pygame.draw.rect(screen, (255, 0, 0), (50, 50, 100, 100)) # Red rectangle
pygame.draw.circle(screen, (0, 0, 255), (400, 300), 50) # Blue circle
screen.blit(surface, position)
- Purpose: Draws an image or text at a specified position.
- Usage:
screen.blit(player_image, (player_x, player_y)) # Draw the player's image
pygame.font.Font()
andfont.render()
- Purpose: Creates and renders text.
- Usage:
font = pygame.font.Font(None, 36) # Set font size to 36
text_surface = font.render("Hello, Pygame!", True, (255, 255, 255))
screen.blit(text_surface, (100, 100)) # Display the text
pygame.display.flip()
orpygame.display.update()
- Purpose: Refreshes the screen.
- Usage:
pygame.display.flip() # Refreshes the entire screen
Sprites
A sprite is a fundamental concept in Pygame that simplifies the control and grouping of game objects.
A sprite can be an image, a character, or any 2D game object. Since we are discussing game objects, we will define sprites using classes. Let’s create a Player
class, for example, to handle all player-controlled characters. In this example, our characters will be simple colored squares, and we will define this class as a sprite:
#player.py
import pygame
class Player(pygame.sprite.Sprite):
def __init__(self, color, starting_position):
super().__init__() # Initialize the sprite
self.image = pygame.Surface((50, 50)) # Create a 50x50 square
self.image.fill(color) # Fill it with the desired color
self.rect = self.image.get_rect() # Get the rectangle area of the sprite, useful for collision detection later
self.rect.center = starting_position # Define the starting position
Grouping Sprites
Imagine a situation where you want to draw many player squares (characters) all at once or apply the same change to all of them. Doing this individually for each sprite would be cumbersome and inefficient. For this reason, Pygame allows us to group sprites together, enabling us to manage them as a single entity.
Let's create a simple sprite group using the built-in Group()
function from the pygame.sprite
module, lets also use our Player
sprite class to create two new sprites and add them to our group then draw them to our screen.
1) Set-Up stage
#main.py
import pygame
from player import Player
###------ exisiting code(initialisation and set up)-------
screen = pygame.display.set_mode((800,600))
all_players = pygame.sprite.Group() # create a sprite group
player_red = Player((255,0,0),(100,100)) # a player filled with red color starting position at x = 100, y = 100.
all_players.add(player_red) # add the red character to the sprite group
player_blue = Player((0,0,255),(50,100)) # analog but blue and x= 50, y = 100
all_players.add(player_blue) # add the blue player to the sprite group
2) Main Loop stage
#main.py
#-----inside the game loop-----
all_players.draw(screen) # Draw all players in the group
pygame.display.flip() # update the display
Game Logic
Game Logic is the “Brains” of the game, determining everything from how characters move and interact to how scoring, levels, and events unfold. Normally we’ll use a mixture of some key concepts we discussed in event handling and basic logic to implement the desired game logic. That being said lets jump to some basic key concepts in the implementation of our game logic.
State Management
State management is the process of tracking the different states the game or the player at any given moment.
- Game state: is something like "Paused", "Playing", "Menu", "Game_Over"
- Player state: on the other hand, could be “Dead”, “Speed_Boosted”
So, while game state refers to high-level phases of the game that dictate the overall gameplay flow, player state manages specific conditions or effects only regarding the players character.
Example Implementation
In the following we will implement a simple game state management system, which starts in the main menu and starts the game when the player presses the start button. We will check if the player’s health drops to 0, the players character will die, and the game is finished for now, however can be restarted.
### Define game states as constants for simplicity ###
MENU = 'menu'
PLAYING = 'playing'
PAUSED = 'paused'
GAME_OVER = 'game_over'
### Define Player States as constants for simplicity ###
DEAD = 'dead'
ALIVE = 'alive'
game_state = MENU # Our Minimal "Game" starts on the main menu
###Logic to switch between states###
if game_state == MENU:
# ....... code to display main menu .......
# ........................
if start_button_pressed:
game_state = PLAYING # change game state to PLAYING when user presses start
player_state = ALIVE # change player state to ALIVE when user presses start
elif game_state == PLAYING:
# ........code to run main gameplay.......
# .......................
if player_health <= 0:
game_state = GAME_OVER # if health drops to zero the game ends
player_state = DEAD # if health drops to zero the player dies
elif game_state == GAME_OVER:
# .......code to show Game Over screen......
# .............................
if restart_button_pressed:
game_state = MENU # restarts everything from main menu
There are many ways to manage states, you can also look up concepts like State Machines, Hierarchical State Structure, and Event Driven State Machines for more complex state management.
Player Movement
this involves handling how the player character moves around the game world.
To move a character we will need to define a few variables for representation:
x = 50 # represents the starting position on the x axis
y = 50 # represents the starting position on the y axis
width = 10 # represents the width of the character
height = 10 # represents the hight of the character
velocity = 10 # represents the moving velocity of our character
As a next step lets start checking for events in the main loop to define the logic which moves our character:
if keys[pygame.K_UP] or keys[pygame.K_w]: # move up when 'w' or 'UP-Arrow' are pressed
y =- velocity
if keys[pygame.K_DOWN] or keys[pygame.K_s]: # analog for down movement
y =+ velocity
if keys[pygame.K_LEFT] or keys[pygame.K_a]: # analog for left movement
x =- velocity
if keys[pygame.K_RIGHT] or keys[pygame.K_d]: # analog for right movement
x =+ velocity
Scoring
Near to every game has a score. In Pygame it involves of keeping track of a variable that increases or decreases based on player actions, then displaying that score on the screen.
Now that we understand the basic idea lets implement a simple scoring system:
Set Up Stage
Define a score variable and set it to a value. (e.g. score = 0):
###------existing setup and initialization code----###
score = 0
Main Loop Stage
for simplicity lets increase the score only depending on time, so our score will increase one point every game loop cycle
###----inside the game loop----###
score += 1
Now lets render our score, to display it on the top left side of the screen:
###----inside the game loop----###
score += 1
###---other game logic code in the game loop---###
score_text = font.render(f"Score: {score}",True, white) # render the score
score_rect = score_text.get_rect(topleft = (10,10)) # position the score
###---continuation of rendering code to draw everything and decide the fps---###
pygame.quit() # exit the game loop and close resources
Physics
Physics in PyGame usually refers to implementing basic principles like gravity, collision detection or friction to create realistic movement and interactions in a 2D environment. Pygame doesn’t have built-in physics, so developers need to code these elements manually or use additional libraries like PyMunk
.
Gravity
Just like in real life gravity in PyGame works by applying a constant downward force on an object, which gradually increases its downward speed. This makes the object move down the screen, simulating the effect of gravity pulling it to the ground.
For a simple implementation lets define three variables: ground_level
, gravity
, and velocity_y
.
1) Set Up Stage
###------existing setup and initialization code----###
gravity = 0.2
velocity_y = 0 # represents vertical moving speed
ground_level = 450
2) Main Loop Stage
we will add the gravity value to the velocity each frame to simulate faster falling if an object hits the ground it will stop falling and collide with the ground
###----inside the game loop----###
velocity_y += gravity
y += vellocity_y
if y > ground_level:
y = ground_level
velocity = 0
Collision Detection
Collision Detection is another core concept in game logic as it allows for managing Interactions between game objects.
That being said we can define Collision Detection as the process of determining when two or more objects in a game space intersect or “Collide”, this gives us the power to respond to various in-game events or changes.
Sprites and Collision detection
As already mentioned, a sprite is essentially any 2D image(game object), which we can display, move or interact with in our game world. Sprites are very useful for collision detection as PyGames provides built-in Tools for handling sprite collisions.
Sprites often use rectangular or circular boundaries, making it easy to use PyGames built-in collision detection functions.
Basic Types of Collision Detection
1) Rectangle Collision
This is mainly used with objects with rectangular bounds, like the squared character shapes from the sprite example implemented earlier.
The main idea here is to use those bounds to detect when they overlap, lets implement an example function to understand this logic:
def check_collision_rect(obj1, obj2):
return (
obj1.x < obj2.x + obj2.width and # ensure obj1 is not to the right of obj2
obj1.x + obj1.width > obj2.x and # ensure obj1 is not entirely to the left of obj2
obj1.y < obj2.y + obj2.height and # ensure obj1 is not above obj2
obj1.y + obj1.height > obj2.y # ensure obj1 is not entirely below obj2
) # return true if obj1 and obj2 collide
This would do the trick, there is however another much easier and more effective way to achieve our goal and that would be by using sprites, and it's built-in functions.
Let's imagine a situation where we need to check for a collision between player_red
and player_blue
from the sprite example earlier and if they collide they will both die => disappear.
###---inside the gameloop---###
if pygame.sprite.collide_rect(player_red,player_blue): # check for collision
player_red.kill() # remove player
player_blue.kill() # remove player
pygame.display.flip() # update the display
We can notice how helpful sprites' built-in functions like collide_rect()
and kill()
can be.
2) Circle Collision
This approach is suitable for circular objects or approximating rounded shapes. This time we are calculating the distance between the centers of two circles and comparing it to the sum of their radius'. The radius sum represents the minimum distance required between the two circles to touch, if the distance between the centers is less than this sum, the circles overlap and are considered to be colliding. The formula to calculate the distance between two points ((x_1, y_1)) and ((x_2, y_2)) is:
Let's put this information to a python function to solidify our understanding:
import math # we need this library to use the square root function
def check_collision_circle(obj1, obj2):
distance = math.sqrt((obj1.x - obj2.x) ** 2 + (obj1.y - obj2.y) ** 2) # distance between the centers
return distance < (obj1.radius + obj2.radius) # if the distance is less than the radius sum => collision
The more effective way would be using Sprites again:
##--------inside the game loop----##
# Handle circular collision between sprite1 and sprite2
if pygame.sprite.collide_circle(sprite1, sprite2):
sprite1.kill()
sprite2.kill()
pygame.display.flip()
3) Pixel-Perfect Collision Detection
Pixel-Perfect collision detection is a more precise method of detecting collisions, it is however more computationally intensive than rectangle, and circle detection, as it checks if actual, non-transparent pixels of two game objects overlap giving the power to deal with more complex shapes.
Due to this approach's weight it’s best used selectively, typically in specific critical interactions like player-enemy contact.
Implementation
Implementing Pixel-Perfect collision usually involves using masks
.
A mask
is nothing but a binary representation of an image where a pixel Is either “On” (visible) or “Off” (transparent).
PyGame provides a mask
module making pixel-perfect detection straightforward.
Pixel-Perfect Collision detection implementation steps
First we will have to create masks for each sprite, and then we will simply use the built-in sprite function collide_mask()
:
##--------inside the game loop----##
sprite1.mask = pygame.mask.from_surface(sprite1.image)
sprite2.mask = pygame.mask.from_surface(sprite2.image)
if pygame.sprite.collide_mask(sprite1, sprite2):
sprite1.kill()
sprite2.kill()
pygame.display.flip()
There are many ways to handle collision detection, it is always suggested to use Sprites and Sprite groups, as they make the process more manageable and effective for handling complex interactions.
Collision between a single sprite and a group
pygame.sprite.spritecollide(player, enemy_group, True) # True removes colliding enemies
Collision between two groups
pygame.sprite.groupcollide(group1, group2, True, False) # This checks for collisions between each sprite in group1 and group2.
# The True and False arguments control whether to remove sprites from group1 or group2 upon collision
##Comments