Objectives
Working with multiple files
- Store configuration values in a separate file
- Explain the benefits of storing configuration values in a separate file
Program structure
- Create a
main()subprogram to control the flow of the whole program - Decompose the responsibilities of
main()into separate subprograms
Object-oriented design
- Construct objects using keyword arguments to make code more readable
- Understand how objects can be composed of Pygame components
- Apply encapsulation and data hiding: distinguishing between public behaviour and private, internal methods
- Use getters/setters to safely access and modify private attributes
Game loop and update cycle
- Read continuous input using
pygame.key.get_pressed() - Map key presses to object behaviour (e.g., velocity, direction)
- Describe the structure of a frame update:
- event handling
- input processing
- updating objects
- drawing
Movement and velocity
- Update position by modifying the Rect each frame
- Understand how different selection logic (if/elif) affects movement
Files
Make local copies of the following files before you start.
main.py
import sys
import pygame
import constants
from models import Player
def setup():
pygame.init()
screen = pygame.display.set_mode((constants.WIDTH, constants.HEIGHT))
pygame.display.set_caption(constants.WINDOW_TITLE)
clock = pygame.time.Clock()
return screen, clock
def main():
screen, clock = setup()
player = Player(
p_initial_x=constants.WIDTH // 2,
p_initial_y=constants.HEIGHT // 2,
p_width=10,
p_height=50,
p_speed=1,
)
running = True
while running:
# Event handling
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
# Input handling
keys = pygame.key.get_pressed()
vx = 0
vy = 0
if keys[pygame.K_LEFT]:
vx = -player.get_speed()
elif keys[pygame.K_RIGHT]:
vx = player.get_speed()
elif keys[pygame.K_UP]:
vy = -player.get_speed()
elif keys[pygame.K_DOWN]:
vy = player.get_speed()
player.set_velocity(vx, vy)
# Drawing
screen.fill(constants.BG_COLOR)
player.update(screen)
pygame.display.flip()
clock.tick(constants.FPS)
pygame.quit()
sys.exit()
main()constants.py
WINDOW_TITLE = "A rectangle that moves"
WIDTH = 600
HEIGHT = 400
FPS = 60
BG_COLOR = (255, 255, 255)models.py
import pygame
class Player:
def __init__(
self,
p_initial_x,
p_initial_y,
p_width,
p_height,
p_speed,
p_color=(0, 255, 0),
p_vx=0,
p_vy=0,
):
self.rect = pygame.Rect(p_initial_x, p_initial_y, p_width, p_height)
self.__speed = p_speed
self.__max_speed = 10
self.__color = p_color
self.__vx = p_vx
self.__vy = p_vy
def __move(self):
"""Move the Player by vx and vy"""
self.rect.x = self.rect.x + self.__vx
self.rect.y = self.rect.y + self.__vy
def __draw(self, screen):
"""Draw the Player on the screen"""
pygame.draw.rect(screen, self.__color, self.rect)
def get_speed(self):
return self.__speed
def update(self, screen):
"""Move and draw the Player"""
self.__move()
self.__draw(screen)
def set_velocity(self, p_vx, p_vy):
"""Change velocities"""
self.__vx = p_vx
self.__vy = p_vy
def reset_position(self):
"""Reset player to its starting position"""
# TODO: implement this for the modify task
pass
def increase_speed(self, amount):
"""Increase speed by given amount, capped at max speed"""
# TODO: implement this for the modify task
passPredict
Predict answers to the following questions without running the program.
-
What does
setup()do inmain.py? -
What does this line do in
main.py?screen = pygame.display.set_mode((constants.WIDTH, constants.HEIGHT)) -
What will the the initial x-coordinate of
playerbe? -
What colour will the player be?
-
What will happen to
playerwhen the right arrow key is pressed?- Predict how the player’s movement will change
-
What will happen to
playerwhen the right and up arrow keys are pressed simultaneously?- Will the player move in a straight line, diagonally, or not at all?
Run
Run the program. Compare your predictions to the actual behaviour.
Investigate
Answer these questions by examining the code.
- Why is
setup()separate frommain()?- What responsibilities does
setup()have? - What effect does separating these responsibilities have on readability and organisation?
- Identify at least one other part of
main()that could be separated into its own subprogram. Justify your answer.
- What responsibilities does
- How does the program know what
WIDTHandHEIGHTare?- Which file defines them?
- How does
main.pygain access to them? - Does the
Playerclass know anything aboutWIDTHandHEIGHT? Why or why not?
- Look at how the
Playerobject is created inmain().- Which parameters are passed to the constructor?
- What is unusual about the way the parameters are passed?
- What effect does this have on the code’s readability?
- Look at the
Playerclass constructor.- Which attributes are public?
- Which attributes are private?
- Why might private attributes be useful here?
- Why does it not store x, y, width, and height? Where is this data stored instead?
- What does the
set_velocity()method do?- How does this relate to key presses in
main()?
- How does this relate to key presses in
- What does
player.update(screen)do each frame?- Why does the order of method calls inside
update()matter? - Why is this method public, but
__move()and__draw()are private?
- Why does the order of method calls inside
Modify
Make changes to explore how the code works.
- Change which keys move the player.
- Rather than arrow keys, control the player with WASD
- Enable diagonal movement.
- Hint: how does
ifdiffer fromelifin this context?
- Hint: how does
- Implement
reset_position().- Pressing the R key should reset the player to their starting position (this should be controlled in
main()) - Hint: does
Playerneed to remember its initial position when created?
- Pressing the R key should reset the player to their starting position (this should be controlled in
- Make the player move continuously even after releasing a key.
- Pressing a key should set the velocity
- The player should continue moving after the key is released
- Hint: which line currently overwrites the velocity each frame?
- Stop the player leaving the window.
- Modify
__move()so the player cannot move off-screen. - Use
rect.x,rect.y,rect.width, andrect.height - The player should stop at the boundary, they should not bounce.
- Modify
- Implement
increase_speed().- Pressing the spacebar should increase the player’s speed (this should be controlled in
main()) - The player’s speed should not be able to exceed its
max_speed.
- Pressing the spacebar should increase the player’s speed (this should be controlled in
Make
Create a new class called Enemy that automatically moves toward the player each frame.
Requirements
Your Enemy class must:
- Use a
pygame.Rectto represent position and size. - Store private attributes for:
- colour
- velocity (x and y)
- speed
- Implement:
- a public
update(screen, player)method - a private
__move(player)method - a private
__draw(screen)method
- a public
- Move toward the player’s position each frame.
- Hint: compare the enemy’s x/y coordinates to the player’s.
- Respect window boundaries and stop at the edges.
- Appear on screen alongside the player.
Options
Choose two or more optional enhancements:
- Fast and slow modes
- Press a key to toggle between fast and slow chasing speeds
- Only chase when close
- The enemy only starts moving if the player is within a certain distance (import
mathand usedistance = math.dist((x1, y1), (x2, y2)))
- The enemy only starts moving if the player is within a certain distance (import
- Enemy changes colour depending on distance:
- Red when close
- Blue when far
- Multiple enemies
- Create a list of enemies and update them all
- Enemy freezes when the player isn’t moving
- Implement a superclass that both
PlayerandEnemyinherit from
Hints
You can get the player’s centre using:
px = player.rect.centerx
py = player.rect.centeryTo decide which way to move, imagine the enemy asking if the player is to their right or to their left:
if px > self.rect.centerx:
# player is to the right
elif px < self.rect.centerx:
# player is to the leftWhen the enemy knows the player’s direction, they just need to move! The enemy doesn’t teleport, they just take small steps towards the player. Each small step should be the enemy’s speed value.
You will need to extend this to work with the y-axis too.