HEROES Academy: Object Oriented Programming, Fall 2016¶
Course Description¶
Video games, phone applications, and many other kinds of programs use the Object Oriented Programming (OOP) design paradigm. Objects are discrete components defined by specific syntax in programming languages. They reflect real world distinctions between types of objects. Object Oriented Programming is more than just syntax, however. It is also a design philosophy that promotes computational thinking, efficient programming, and the reuse of code.
Building upon the Introduction to Python course, we will use Pygame and Python to simultaneously learn about objects and building video games.
The class will tour through the major OOP concepts:
- encapsulation (packaging of code)
- polymorphism (code reuse)
- inheritence (syntactically efficient code structure)
- composition (functionally efficient code structure).
While learning about these major concepts, students will learn about game engines, the need for objects in games, and how to turn their creative ideas into tangible products. By the end of the course, the students will have a working game and be able to make progress on furthering it on their own.
How to Browse This Document¶
This document is intended to be a companion to the Object Oriented Programming course taught at HEROES Academy. For more information about HEROES Academy, please visit it here.
This document is still in the works. The Spring session does not begin until the second weekend in April.
Contents:
Course Information¶
What is HEROES Academy?¶
HEROES Academy is an intellectually stimulating environment where students’ personal growth is maximized by accelerated learning and critical thinking. Our students enjoy the opportunity to study advanced topics in classrooms that move at an accelerated pace.
When does this course meet?¶
The Object Oriented Programming course will meet from 1:40 to 3:40 starting October 2nd.
How do I register for this course?¶
The list of courses are listed on the HEROES website. If you have any questions about the process, you can check out the HEROES Frequently Asked Questions.
What are the expectations of this course?¶
I expect that...
- You will ask questions when you do not get something.
- You will keep up with the work.
- You will fail fast:
- Failing is good
- We learn when we fail
- We only find bugs when code fails; we rarely hunt for bugs when code is working
- You will not copy and paste code from the internet
- You are only cheating yourself.
- It won’t bother me if you do it, but you will not learn the material.
- You will try the homework at least once and email me with solutions or questions by Wednesday
How do I contact you?¶
You can reach me anytime at teacher@njgifted.org
Installing Python¶
Python Distribution¶
There are several ways to get Python. My recommended way is the Anaconda distribution. It includes both Python and a bunch of other things packaged with it that make it super useful.
Instructions for downloading Anaconda Python:
- Click the link above.
- If you use a Mac, look at the section titled “Anaconda for OS X,” and click on “MAC OS X 64-BIT GRAPHICAL INSTALLER” under the “Python 3.5” section.
- If you use a Windows computer, in the section titled “Anaconda for Windows,” click either “WINDOWS 64-BIT GRAPHICAL INSTALLER” or “WINDOWS 32-BIT GRAPHICAL INSTALLER” under the “Python 3.5” section.
- On most Windows machines, you can tell if it’s a 64-bit or 32-bit system by right-clicking on the Windows logo and selecting “System.” The line labeled “System Type” should say either 64-bit or 32-bit. If you’re having trouble with this, simply email me and I’ll help you out!
- Once you click the button, an installer file will be downloaded to your computer. When it finishes downloading, run the installer file.
- Follow along with the prompts, and select “Register Anaconda as my default Python 3.5” if you’re using the Windows installer.
- At the end of the installation wizard, you’re done! Anaconda, and Python, are installed.
An Editor¶
There are many good editors and IDEs (Integrated Development Environments). As you’re just beginning to learn how to use Python, it’s a good idea to use a simplistic, lightweight development environment. PyCharm and Sublime Text are both good choices for starting out. They have nice, clean appearances, highlight your code to make it easier to read, and are easy to jump in and start coding right away.
Instructions for downloading PyCharm:
- Click the link above.
- Click “Download” under the “Community” section.
- An installer file will be downloaded to your computer. When it finishes downloading, run the installer file.
- Follow along with the installer, and select “add .py extension” if you see the option
- At the end of the installation wizard, you’re done! PyCharm is now installed.
Other than those two, GitHub has an editor that is very comparable to Sublime Text. It is called Atom.
Installing PyGame¶
PyGame is a library that creates graphical interfaces for games. There is sometimes some difficultly in installing it, so below I have listed information to help you out.
Where to get it¶
There are a couple of good directions on the internet:
Common Issues¶
- I installed Pygame, but when I use python, it says it can’t find it.
- this is usually caused by having two versions of python installed
- Email me and we will talk through the situation. It usually involves a couple things that need to be check to verify this is the situation.
- When installing Pygame, at the part where it says “Select Python Installation”, it is showing no python installaion
- this is can be an issue sometimes with the way Python was installed.
- I have had this happen to me with Anaconda
- Try the following:
[Week 1] Hello PyGame¶
Welcome to the first week of Object Oriented Programming with Python and Pygame!
Summary¶
We went over the basics of Python to see what parts of Python would need to be practiced.
After the assessment, we started playing with PyGame, beginning with a tour of the game loop. We spent the rest of the classing playing with the basic objects of Pygame.
Review¶
We will review material today.
Slides¶
[Week 2] Introducing State¶
Summary¶
Today, we will cover the PyGame loop again and step through in class how to make things move. This requires understanding how to represent state.
We will practice using functions today, so that we can avoid messiness, enable reusability, and improve the readability. This will require understanding scope, encapsulation, and polymorphism (which is a fancy word for code being able to be used in multiple ways).
The first projects are also selected today. They will require the following:
- a moving object through changing the position state
- interaction through the event loop that changes a property of the state
- a demonstration of reusable code with functions
Homework¶
- Come up with a project
- see slides for a couple ideas
Put the out of bounds checker into a function
Make a wall and have the objects bounce off of it
Slides¶
[Week 3] Encapsulating code¶
Summary¶
Now that we’ve started to practice mastering the state of objects (keeping track of their properties), we will work on making our code more efficient.
In-class code¶
Important Concepts¶
- Functions
- A code block which packages the code and provides a shortcut to executing the code
- The code below shows how to pass information in, how to get information out
- important: remember that scope means what variables can be “seen” inside and outside the function
Example:
def hello_x(x):
y = "hello {}".format(x)
return y
y = hello_x("world")
print(y)
- Dictionaries
- A python variable type that allows you to map keys to values
Example
bob = dict()
bob['name'] = 'bob'
bob['species'] = 'turtle'
- Encapsulation.
The packaging of code to be reused later
- Example: if we have multiple objects, and we want to make them bounce off walls,
then we could either write the wall bouncing code for each object, or write the code once and use a function to apply it to each object.
Homework¶
See the slides for more information. The basic gist: practice dictionaries and functions.
Slides¶
[Week 4] Our First Object¶
Class Summary¶
Last week we looked at how to encapsulate code. There were some hiccups so we will covered this a bit more.
Part 1: Encapsulation Exercise¶
On the following page, you will find a set of exercises. They are to help you understand what it means to encapsulate code and why it is useful.
Part 2: Creating an Object¶
Objects are a way to group either variables or functionality into useful chunks. Like functions, they let us have cleaner code, and repeat ourselves less.
Objects being with “class” definitions. These definitions are like the blueprints or the recipe. Using these blueprints, we can create an object. Let’s look at a simple one:
class Point:
x = 0
y = 0
p1 = Point()
p1.x = 50
p1.y = 50
The first 3 lines define our class. This is the blue print for our object. We create our object by using the class like a function. This is usually referred to as “construction” or “instantiation”.
For the rest of this part, you will make your own objects to represent boxes.
[Week 5] Multiple Objects¶
Last week we saw how to create a single object and then add extra properties to it. This time, we are going to use multiple objects.
The exercise page can be found by clicking right here!
You homework for the week is to makes ure you get through all of these exercises.
[Week 6] Designing Objects¶
This week we will be working on designing objects before we use them.
[Week 7] Object Ecosystems¶
Last week we talked about designing objects. This week, we will be talking about how you can make an ecosystem of objects to work together.
Topics:
Inheritance and how to use it
- Re-capping classes
- Designing an ecosystem of objects in the game
In designing the game, you should identify:
- an entity that will be used in many places
- examples include bricks in brickbreak games
- monsters in other games
- projectiles
- etc
identify the basic functionality
- plan out the flow of the game
- what will the hero do
- what will trigger what
- what will be the goal
- what will be the inputs
[Week 8] Object Practice and Projects¶
Today’s Task¶
- Outline your project using the rubric below
- Using the outline, talk about your project
- Work on your project
Project Rubric¶
Game Title
- Overall game goal
- What is the player trying to accomplish
- Objects that exist in the game
List all objects that you want to use
List all objects currently implemented
- For each object, identify the common parts and specialized parts
- Specify which properties are inherited and which are new
For each object, describe when it is drawn (it can be always drawn or conditionally drawn)
- Interaction
- What interaction will the user have?
- Specifically, what keys will be used and what effect will they have
What is the minimal set of things you need to have to have your game working?
- List three 1-step additions you could make
- It has to add only 1 additional piece of complexity
Cookbooks¶
You should be referencing at least some of these!
- Simple PyGame cookbook
- covers pygame examples without using classes
- PyGame with Classes cookbook
- covers pygame using classes
- has a lot of functionality explained!
- Python Cookbook
- This has examples of most of Python’s syntax!
- Classes cookbook
- This has examples of the basics of classes!
- A giant color list!
- You can use this to get tons of colors into your game!
[Week 9] Project Discussions¶
This week will be looking at your projects to see how far you’ve come and what you have left. You should have a working demo!
You should fill out the rubric again, this time only with things you have done.
Project Rubric¶
Game Title
- Overall game goal
- What is the player trying to accomplish
- Objects that exist in the game
List all objects that you want to use
List all objects currently implemented
- For each object, identify the common parts and specialized parts
- Specify which properties are inherited and which are new
For each object, describe when it is drawn (it can be always drawn or conditionally drawn)
- Interaction
- What interaction will the user have?
- Specifically, what keys will be used and what effect will they have
What is the minimal set of things you need to have to have your game working?
- List three 1-step additions you could make
- It has to add only 1 additional piece of complexity
Presentation Link¶
You will give a presentation to your parents when we meet again in 2 weeks. You will have time at the beginning of class to finish things up, but your presentation is due to me that Friday (December 16th).
Here is the presentation template:
Exercises¶
Classes Recap¶
This is a set of exercises for refreshing your knowledge of classes.
The basic class¶
1 2 3 4 5 6 7 8 9 | class Dog:
name = ''
age = 0
# Dog is called like a function to **instantiate** the object
fido = Dog()
# have to set the variables manually
fido.name = "Fido"
fido.age = 7
|
Class with an initial function¶
1 2 3 4 5 6 7 | class Dog:
def __init__(self, name, age):
self.name = name
self.age = age
# now we can accept arguments when we **instantiate** the object!
fido = Dog("Fido", 7)
|
Thinking about how self works¶
If you need to understand self better, try this out.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | class Dog:
def __init__(self, name, age):
self.name = name
self.age = age
print("-2--")
print("Inside the init function")
print(self)
# now we can accept arguments when we **instantiate** the object!
print("-1--")
print("Before creating fido")
fido = Dog("Fido", 7)
print("-3--")
print("After creating fido")
print(fido)
|
Look at the code above and run it in PyCharm.
Notice what is printed by print(self)
inside the init function and print(fido)
outside the class. They are both pointing at the same object! self
is
just a way of getting access to the object while inside the object.
Clean Environment¶
We are going to start keeping our constants and useful variables in a separate file.
- Create a separate file, call it “settings.py”
- Put in the following variables
1 2 3 4 5 6 7 8 | BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
GREEN = (0, 255, 0)
RED = (255, 0, 0)
WINDOW_SIZE = (700, 500)
SPEEDX = 5
SPEEDY = 5
|
- Anytime you have new constant variables, they should be put into here.
- Inside your game file, you should put the following:
1 2 3 | from settings import *
# if the above breaks, type:
# from .settings import *
|
Designing Objects¶
Now that we are starting to write our own objects, we will start designing our own objects!
By the end we will have the following file structure:
We will start with the game loop. Create a new file called “game.py” and start putting these into there:
The top¶
The top of the file is pretty standard. I have added extra imports to handle different files.
1 2 | import pygame
from settings import *
|
The settings.py file is:
Game __init__
function¶
Creating a class
for our game loop means we can organize all of the functionality easier!
1 2 3 4 5 6 7 8 9 10 11 | class Game:
def __init__(self):
self.walls = []
self.hero = None
self.done = False
pygame.init()
self.screen = pygame.display.set_mode(WINDOW_SIZE)
self.clock = pygame.time.Clock()
pygame.display.set_caption(TITLE)
|
Checkpoint questions:
- What would instantiating this class look like?
- What kinds of things could you add into the initial function?
Game Loop¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | def run(self):
while not self.done:
#### EVENT CHECK SECTION
for event in pygame.event.get():
if event.type == pygame.QUIT:
self.done = True
## extra stuff will go here
### clear the screen
self.screen.fill(WHITE)
## extra stuff will go here
#### update the display and move forward 1 frame
pygame.display.flip()
# --- Limit to 60 frames per second
self.clock.tick(FPS)
|
Checkpoint questions:
- Given that you already answered how the class could be instantiated, how would you run this function?
- Can you predict what it will do? Try and run it now.
The Hero¶
Let’s create the hero class. You can use the one you wrote from last week.
Put it by itself into a hero.py file and change the top of “game.py” to the following:
1 2 3 | import pygame
from settings import *
from hero import *
|
Now, you should have a hero from last week! It should go into the hero file. I’m going to show the bare bones here:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | class Hero:
def __init__(self, x, y, w, h):
''' The hero constructor function '''
self.rect = Rect(x, y, w, h)
## other things could/should go here
def move_right(self, step_size=SPEEDX):
''' Move the hero to the right '''
pass
def move_left(self, step_size=SPEEDX):
''' Move the hero to the left '''
pass
def move_up(self, step_size=SPEEDY):
''' Move the hero up '''
pass
def move_down(self, step_size=SPEEDY):
''' Move the hero down '''
pass
def drift(self):
''' drift across the screen
Note: the implementation should drift x and drift y separately
After the drift in x, it should check for x collisions
After the drift in y, it should check for y collisions
'''
pass
def drift_x(self):
''' Handle the drift in x '''
pass
def drift_y(self):
''' Handle the drift in y '''
pass
def collides_with(self, other_rect):
''' return true if there is a collision '''
pass
def handle_xcollision(self, other_rect):
''' handle collisions going left and right '''
pass
def handle_ycollision(self, other_rect):
''' handle collisions going up and dowon '''
pass
|
We are going to add two new functions to the Hero
class:
update
and draw
.
I will show the functions under the class header below.
Assumption: When update
is called, the hero will be passed a list of walls.
This is so it can check for collisions.
Assumption: When draw
is called, the hero will be passed the screen
.
1 2 3 4 5 6 7 8 9 10 | class Hero:
### all other things here
def update(self, walls):
''' move and check for collisions '''
pass
def draw(self, screen)
''' draw the hero '''
pass
|
Adding the hero into the game¶
Into the Game
class, we will add a new function which will setup everything.
Then, inside the main loop, we will have it run the hero’s functions!
This will also change how the game is instantiated and run.
Updated code is below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | class Game:
def __init__(self):
self.walls = []
self.hero = None
self.done = False
pygame.init()
self.screen = pygame.display.set_mode(WINDOW_SIZE)
self.clock = pygame.time.Clock()
pygame.display.set_caption(TITLE)
def setup(self):
self.hero = Hero(___) ### fill in the underline
def run(self):
while not self.done:
#### EVENT CHECK SECTION
for event in pygame.event.get():
if event.type == pygame.QUIT:
self.done = True
## extra stuff will go here
### clear the screen
self.screen.fill(WHITE)
if self.hero is not None:
self.hero.update(self.walls)
self.hero.draw()
#### update the display and move forward 1 frame
pygame.display.flip()
# --- Limit to 60 frames per second
self.clock.tick(FPS)
### this changes the running to:
game = Game()
game.setup()
game.run()
|
Adding Walls¶
We are going to create a wall class. This will let us manage walls better. We should put this in “walls.py”. I have written some code below to make this easier.
Important notes:
- You have to write the
draw
function - The class is able to parse a series of strings into wall placements (see
parse_level
)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | class Walls:
def __init__(self):
''' keep track of the walls
you could maybe pass in a COLOR here'''
self.walls = []
def add_wall(self, x, y, w, h):
''' add a single wall'''
self.walls.append(Rect(x,y,w,h))
def parse_level(self, level):
'''Parse a level string into a set of walls. I've made this for you'''
level_width = len(level[0])
wall_width = WIDTH / level_width
level_height = len(level)
wall_height = HEIGHT / level_height
for row_index in range(level_height):
for col_index in range(level_width):
cell = level[row_index][col_index]
if "cell" == "W":
x = wall_width * col_index
y = wall_height * row_index
self.add_wall(x, y, wall_width, wall_height)
def set_example_level(self):
level = [
"WWWWWWWWWWWWW",
"W W W",
"W W W W",
"W W W W",
"W W W",
"WWWWWWWWWWWWW"
]
self.parse_level(level)
def draw(self, screen):
for wall in self.walls:
### fill in the pygame draw code here.
|
In order to get this into the game, we have to do two things:
- Add an import statement
from walls import *
into the game.py - Add this to the
setup
so that the game will make the walls - Add into the game loop a call which draws the walls.
Adding Keyboard Input¶
To get keyboard input, we need to add some extra stuff into the event loop. Specifically, the event loop should handle more complex checks. Also, optionally, we could have the HERO check for game events.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | class Game:
## code was here
def run(self):
while not self.done:
#### EVENT CHECK SECTION
for event in pygame.event.get():
if event.type == pygame.QUIT:
self.done = True
else:
self.handle_event(event)
### the rest of the game loop here
def handle_event(self, event):
## do various checks for events here.
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_LEFT:
## code here
elif event.key == pygame.K_RIGHT:
## code here
elif event.key == pygame.K_DOWN:
## code here
elif event.key == pygame.K_UP:
## code here
|
Jumping¶
If you’d like to make your hero jump and land on platforms, there are a couple different things that need to happen.
The hero can not respond to the up/down keys anymore
The hero is always moving down
- Inside the moving down, the hero has two speeds:
gravity
, which is the default speed- this number show moving the hero DOWN (so, a positive number if adding to position)
up_energy
, which gets set to some number when a key like SPACE is pressed- then, whenever the hero moves, the
up_energy
decays, for example:up_energy = up_energy * 0.9
- then, whenever the hero moves, the
Encapsulation Exercises¶
These exercises go through and make the encapsulation more and more complete for moving a square around the screen.
STEP ONE
Use the template. These examples assume that you are using the basic pygame template.
The exercises have two parts: defining the information for the square and then using that information.
Anatomy of the Pygame loop¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | ##### INIT SECTION
# import pygame
# any functions you want to use should be defined right away
# create pygame variables
# create variables you want to use inside the game loop
##### WHILE LOOP SECTION
while not done:
# check for events
# fill the screen with white
##### ACTION CODE
# do any actions that we want to do
# this could be moving the box, etc
##### FINISHING CODE
# end of while loop code, mostly the clock.tick()
#### POST WHILE LOOP SECTION
# once the code hits here, we can assume that the while loop is over and game is done
# do any last finishing code things here
# the important one is to tell pygame shut down
|
Exercise 1¶
Inside the INIT section:
1 2 3 4 | origin_x = 50
origin_y = 50
square_width = 100
square_height = 100
|
Inside the ACTION CODE section:
1 2 3 | # the syntax for rect is (display surface, color, rectangle_info)
# and the rectangle_info is (x, y, width, height)
pygame.draw.rect(surface, BLACK, [origin_x, origin_y, square_width, square_height])
|
Your task:
- Create a second rectangle and that has different starting x and y variables.
- In other words, create two new variables and use them to draw a new rectangle.
- Use the same height and width as the first rectangle.
Exercise 2¶
Inside the INIT section:
1 | box_info = {'x': 50, 'y': 50, 'width': 100, 'height': 100}
|
Inside the ACTION CODE section:
1 2 3 | # the syntax for rect is (display surface, color, rectangle_info)
# and the rectangle_info is (x, y, width, height)
pygame.draw.rect(surface, BLACK, [box_info['x'], box_info['y'], box_info['width'], box_info['height']])
|
Your task:
- Create a second rectangle that is made up of a second dictionary.
- It should be drawn in the exact same way as the first one, but using the second dictionary.
Exercise 3¶
Inside the INIT section:
1 2 3 4 5 | def make_box(x, y, width, height):
new_box_info = {'x': x, 'y': y, 'width': width, 'height': height}
return new_box_info
box_info = make_box(50, 50, 100, 100)
|
Inside the ACTION CODE section:
1 2 3 | # the syntax for rect is (display surface, color, rectangle_info)
# and the rectangle_info is (x, y, width, height)
pygame.draw.rect(surface, BLACK, [box_info['x'], box_info['y'], box_info['width'], box_info['height']])
|
Your task:
- Create a second rectangle using the function. Draw this rectangle as you did in exercise 2.
Exercise 4¶
Inside the INIT section:
1 2 3 4 5 6 7 8 | def make_box(x, y, width, height):
new_box_info = {'x': x, 'y': y, 'width': width, 'height': height}
return new_box_info
def draw_box(surf, color, info):
pygame.draw.rect(surf, color, [info['x'], info['y'], info['width'], info['height']])
box_info = make_box(50, 50, 100, 100)
|
Inside the ACTION CODE section:
1 2 3 | # the syntax for rect is (display surface, color, rectangle_info)
# and the rectangle_info is (x, y, width, height)
draw_box(surface, BLACK, box_info)
|
Your task:
- Create a second rectangle as you have in the past couple of exercises. Draw it in the same way.
Final Task¶
You will create two new functions:
make_circle
- use a dictionary to represent the necessary variables for a circle
- it needs x, y, and radius.
draw_circle
function- in the same way
draw_box
is written, write adraw_circle
function - the syntax for drawing a circle is
pygame.draw.circle(surface_object, some_color, center_point, radius)
- the center point is just
(x,y)
or[x,y]
- in the same way
Intro Object Exercises¶
In these exercises, you will go through making objects and using them for different things.
These examples assume that you are using the basic pygame template. If you don’t have it, find it here
Anatomy of the Pygame loop¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | ##### INIT SECTION
# import pygame
# any functions you want to use should be defined right away
# create pygame variables
# create variables you want to use inside the game loop
##### WHILE LOOP SECTION
while not done:
# check for events
# fill the screen with white
##### ACTION CODE
# do any actions that we want to do
# this could be moving the box, etc
##### FINISHING CODE
# end of while loop code, mostly the clock.tick()
#### POST WHILE LOOP SECTION
# once the code hits here, we can assume that the while loop is over and game is done
# do any last finishing code things here
# the important one is to tell pygame shut down
|
Exercises¶
Exercise 1¶
Inside the INIT section:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | # set up the class and the variables
class Box:
x = 0
y = 0
w = 0
h = 0
speedx = 0
speedy = 0
box_info = Box()
box_info.x = 50
box_info.y = 50
box_info.w = 100
box_info.h = 100
box_info.speedx = 10
box_info.speedy = 10
|
Similar to the other exercises, use this to make the rectangle inside the ACTION CODE section:
1 2 3 | # Use the box_info object to draw!
pygame.draw.rect(surface, BLACK, [box_info.x, box_info.y, box_info.w, box_info.h])
|
Compare this code to the earlier exercises. Write the “make_box” function which uses the class instead. Also, rewrite the “draw_box” using this class.
Exercise 2¶
The code for getting the width and height of the screen are the following:
1 2 3 4 | # get the screen width and height
screen = pygame.display.get_surface()
W, H = screen.get_size()
|
When testing to see if the box is beyond the sides of the screen, use the correct side:
1 2 3 4 5 6 | # calculate special variables
right_side = box_info.x + box_info.w
left_side = box_info.x
top_side = box_info.y
bottom_side = box_info.y + box_info.h
|
Also, remember W is the width, and so is the right side of the screen. H is the height and is the bottom side of the screen. So, if the left_side is below 0, it is out of bounds. If the right side is larger than W, it is out of bounds. If the top_side is smaller than 0, it is out of bounds. Finally, if the bottom_side is larger than H, it is out of bounds.
Write the code for the update position function:
1 2 3 4 5 6 7 8 9 10 11 | # Compute the new position using the box_info object
def update_position(box_info):
### test if the box is out of bounds
### if it is,
### the speed should negative for
### the corresponding side that is out of bounds
###
### then update the position by the speed
### so, the x changes by speed
### the y changes by speed
|
The function should be used inside the while loop to update the position before it is drawn.
Exercise 3¶
Let’s add a function into our class so that it can draw itself.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | # Compute the new position using the box_info object
class Box:
x = 0
y = 0
w = 0
h = 0
speedx = 0
speedy = 0
def update_position(self):
### everything stays the same, except you can get access to the variables using "self" now
### test if the box is out of bounds
### if it is,
### the speed should negative for
### the corresponding side that is out of bounds
###
### then update the position by the speed
### so, the x changes by speed
### the y changes by speed
## assume we do
## box = Box()
## then, later, you can use it with
## box.update_position()
|
Exercise 4¶
Let’s make this more interactive! For each of the following key tests, you can change some variable inside your object. For instance, you could have left and right increase or decrease the speedx. You could also have your box jump with space. Note that this last one requires thinking about gravity a bit more.
1 2 3 4 5 6 7 8 9 10 11 12 | ### inside WHILE LOOP section
for event in pygame.event.get():
## standard quit
if event.type == pygame.QUIT:
done = True
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE:
print("Do something here!")
elif event.key == pygame.K_LEFT:
print("do something here!")
elif event.key == pygame.K_RIGHT:
print("do something here!")
|
Extra stuff¶
Class __init__ method¶
Using the __init__
method lets you pass variables into the creation of the object.
This is also called a “constructor” method.
1 2 3 4 5 6 7 8 | class Box:
def __init__(self, x, y, w, h):
self.x = x
self.y = y
self.w = w
self.h = h
box = Box(50, 50, 100, 100)
|
Gravity and Jumping 1¶
For fun, we are going to add gravity. Gravity is just a way of updating the y speed. Add the following code into update_position. Your Box class will need a new variable: mass.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | # recall that
# x += y
# is the same as
# x = x + y
### for physics
upforce = 0 # should be some number, maybe from bouncing or jumping
gravity = 9.8 # is positive because 0 is the top and we want it to fall down
downforce = gravity * box.mass
totalforce = downforce+upforce
acceleration = totalforce / box.mass
### update speeds and locations
box.speedy += acceleration
box.y += box.speedy
|
Play with different values of gravity.
Gravity+Jumping 2¶
(For a great discussion of this, see this stackoverflow answer)
Gravity is a force that acts on the y direction of an object. Specifically, if your object has a speed, then it is accelerating downwards by gravity. If you jump, you are accelerating upwards.
Force is equal to mass times acceleration—. So, to get acceleration from two forces (gravity and jumping), we do . Jumping is our force upwards, gravity is our force downwards.
Use the following function to compute the acceleration. In the event loop, you could set a boolean variable which tells you whether a space bar press happened or not. Then, you can pass it to this function to enable the upward force. It would be good to have upward force be a couple times more than gravity. Also, the boolean for the space bar press should only be True once, because it’s a burst of energy, not a sustained force.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | def compute_acceleration(box, did_jump=False):
gravity_force = 9.8 * box.mass
downforce = gravity_force # + any other downward forces
if did_jump:
jump_force = somenumber * box.mass
else:
jump_force = 0
upforce = jump_force # + any other upward forces; maybe bouncing
total_force = downforce + upforce
acceleration = total_force / box.mass # f = m*a
return acceleration
|
Property Decorator¶
Using the property decorator for a class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | class Box:
x = 0
y = 0
w = 10
h = 10
def print_info(self):
pass
@property
def right_side(self):
return self.x + self.w
box = Box()
box.x = 50
print(box.print_info, type(box.print_info))
print(box.right_side, type(box.right_side))
|
Up-Down triggers¶
Let’s say you want somethign constant to be happening while a button is pressed. You could do the following:
1 2 3 4 5 6 7 8 9 10 11 | ### inside WHILE LOOP section
for event in pygame.event.get():
## standard quit
if event.type == pygame.QUIT:
done = True
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE:
spacedown = True
elif event.type == pygame.KEYUP:
if event.key == pygame.K_SPACE:
spacedown = False
|
Multiple Objects Exercises¶
In these exercises, you will go through making objects and using them for different things.
These examples assume that you are using the basic pygame template. If you don’t have it, find it here
Anatomy of the Pygame loop¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | ##### INIT SECTION
# import pygame
# any functions you want to use should be defined right away
# create pygame variables
# create variables you want to use inside the game loop
##### WHILE LOOP SECTION
while not done:
# check for events
# fill the screen with white
##### ACTION CODE
# do any actions that we want to do
# this could be moving the box, etc
##### FINISHING CODE
# end of while loop code, mostly the clock.tick()
#### POST WHILE LOOP SECTION
# once the code hits here, we can assume that the while loop is over and game is done
# do any last finishing code things here
# the important one is to tell pygame shut down
|
Exercises¶
Exercise 0¶
Clean your environment (click here)!
Exercise 1¶
This week, we will start making the Hero of a game.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | class Hero:
def __init__(self, x, y, w, h):
''' this is a constructor function
when you create a new Hero, it requires these 4 variables
'''
# Remember that classes act like factories
# when we are inside this function, we are inside a single Hero instance
# and we need to be able to reference the current hero
# so, a self variable is a way of referencing the current Hero
self.x = x
self.y = y
self.w = w
self.h = h
hero1 = Hero() # this will break.
hero1 = Hero(10, 10, 50, 50)
print(hero1.x)
|
Add the following
- A variable into the constructor (
__init__
function) for the hero’s name - don’t forget to “save” it to the hero using
self.name = name
- don’t forget to “save” it to the hero using
- A variable into the constructor (
Exercise 2¶
This week, we will start making the Hero of a game.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | class Hero:
def __init__(self, x, y, w, h):
''' this is a constructor function
when you create a new Hero, it requires these 4 variables
'''
# Remember that classes act like factories
# when we are inside this function, we are inside a single Hero instance
# and we need to be able to reference the current hero
# so, a self variable is a way of referencing the current Hero
self.rect = Rect(x, y, w, h)
def say_hi(self):
print("Hello, my name is {}".format(self.name))
def move_right(self, step_size=0):
self.rect.x += step_size
hero1 = Hero(10, 10, 50, 50)
print(hero1.x) # this will break
print(hero1.rect)
hero1.say_hi()
|
We are going to save the coordinate information into Pygame’s Rect
class.
They offer some really neat functions if we do this.
Also, Rect
has the following variables:
x,y
top, left, bottom, right
topleft, bottomleft, topright, bottomright
midtop, midleft, midbottom, midright
center, centerx, centery
size, width, height
w,h
Add the following:
Add the
name
code from the first exercise into this class.- The three other functions that move the hero:
def move_left(self, step_size=0)
def move_down(self, step_size=0)
def move_up(self, step_size=0)
Code that does the following:
1 2 3 4 | hero1.move_right(100)
hero1.move_down(100)
hero1.move_left(100)
hero1.move_up(100)
|
Exercise 3¶
Let’s make the hero move on their own!
Note: this code assumes you have done the Clean Environment exercise
because it assumes the SPEEDX
and SPEEDY
variables.
NOTE: here we will pass in SPEEDX
and SPEEDY
explicitly into the move functions.
However, you could (and should) change the defaults inside those functions to SPEEDX
and SPEEDY
.
Add the following code into the Hero.__init__
function:
1 2 | self.going_right = True
self.going_down = True
|
And now, a new function inside the Hero
class:
1 2 3 4 5 6 7 8 9 10 | def drift(self):
if self.going_right:
self.move_right(SPEEDX)
else:
self.move_left(SPEEDX)
if self.going_down:
self.move_down(SPEEDY)
else:
self.move_up(SPEEDY)
|
Add the following:
1. Inside def drift(self)
, after the code which moves the hero,
check to see if self.rect
is outside of the screen. I have done the first one for you.
# this will assume WINDOW_SIZE
WIDTH = WINDOW_SIZE[0]
HEIGHT = WINDOW_SIZE[1]
# you can also do "unpacking"
# WIDTH, HEIGHT = WINDOW_SIZE
if self.rect.right > WIDTH:
# we will now switch directions
self.going_right = False
# we will also set the left side to be equal to the window side
# this means we won't go off screen and bug out
self.rect.right = WIDTH
elif self.rect.left < 0:
print("you should write code here!")
elif self.rect.top < 0:
print("you should write code here!")
elif self.rect.bottom > HEIGHT:
print("you should write code here!")
Exercise 4¶
Now, we will give our hero a wall to bump into!
This will demonstrate why we use Rect
.
Check out this documentation: PyGame Rect Docs for Colliding
This code should go into the INIT SECTION part of the code:
1 2 3 4 5 6 | # this will assume WINDOW_SIZE
WIDTH = WINDOW_SIZE[0]
HEIGHT = WINDOW_SIZE[1]
# you can also do "unpacking"
# WIDTH, HEIGHT = WINDOW_SIZE
wall1 = Rect(WIDTH // 2, 0, WIDTH // 10, HEIGHT)
|
Then, after having moved, inside the while
loop ACTION CODE:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | ### assume hero moves here in some way
### could be calling hero1.move()
if hero1.rect.colliderect(wall1):
print("The hero has collided with the wall!")
print("You should be adding code here!")
if hero1.rect.right > wall1.left:
hero1.rect.right = wall1.left
hero1.going_right = False
elif hero1.rect.left < wall1.right:
print("Add code here!")
elif hero1.rect.bottom > wall1.top:
print("Add code here!")
elif hero1.rect.top < wall1.bottom:
print("Add code here!")
|
You should finish the code inside the if
statements.
Exercise 5¶
Move the above code into the Hero
class.
1 2 3 | def handle_collision(self, other_rect):
if self.rect.colliderect(other_rect):
print("The code is basically the same from Exercise 4!")
|
The major differences will be that hero1
is used to refer to the hero OUTSIDE of itself,
but when the code is INSIDE itself, you use the self
variable to reference it.
What should the code look like now inside the while
loop?
Exercise 6¶
Now you will add multiple walls.
1 2 3 4 5 6 7 8 9 10 | two_thirds_height = 2 * HEIGHT//3
one_tenth_width = WIDTH // 10
one_third_width = WIDTH // 3
### Rects want x, y, w, h
### x and y are for the TOP LEFT corners.
wall1 = Rect(one_third_width, 0, one_tenth_width, two_thirds_height)
wall2 = Rect(2 * one_third_width, HEIGHT - two_thirds_height, one_tenth_width, two_thirds_height)
walls = [wall1, wall2]
|
Inside the loop:
1 2 | for wall in walls:
hero1.handle_collision(wall)
|
You should draw out a maze and plan the x, y, w, and h coordinates. You should be using at least 5 walls.
Bonus Exercise¶
If you want to add human movement to the hero, you can do the following:
1 2 3 4 5 6 7 8 | ### inside WHILE LOOP section
for event in pygame.event.get():
## standard quit
if event.type == pygame.QUIT:
done = True
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_LEFT:
hero1.move_left()
|
Object Ecosystem Exercises¶
For this, you will be creating a custom, but basic pygame sprite class. You will use this as the base for all of the sprites you draw in the game. Then, you will extend it to make the Hero and some object type that will be used a lot. I am going to use it to make blocks that the Hero is going to try to get.
Basic Pygame Sprite Class¶
When you have a pre-defined class, you can inherit from it and get all of its functionality. This lets you implement common things once, and then reuse them in different ways.
We are going to start with blocks and then we will work our ways towards a fuller implementation.
1 2 3 4 5 6 7 8 9 10 11 12 13 | class Block(pygame.sprite.Sprite):
def __init__(self, color, width, height):
# Call the parent class (Sprite) constructor
super(Block, self).__init__(self)
# Create an image of the block, and fill it with a color.
# This could also be an image loaded from the disk.
self.image = pygame.Surface([width, height])
self.image.fill(color)
# Fetch the rectangle object that has the dimensions of the image
# Update the position of this object by setting the values of rect.x and rect.y
self.rect = self.image.get_rect()
|
Converting Walls to use sprites¶
1 2 3 4 5 6 7 8 9 10 11 12 | class Walls:
def __init__(self):
''' keep track of the walls
you could maybe pass in a COLOR here'''
self.walls = pygame.sprite.Group()
def add_wall(self, x, y, w, h):
''' add a single wall'''
new_block = Block(BLACK, w, h)
new_block.x = x
new_block.y = y
self.walls.add(new_block)
|
If you are having trouble with the game, start here¶
We are going to debug misunderstandings. Debugging means removing all the complex stuff and starting with simple baselines so we can know where the issue is at.
First, start with the class recap. If there is any confusion with that, then come to me.
Then, we will move onto the basic pygame setup. Go to either the code you wrote, or the code I wrote.
Using a series of English sentences, describe the flow of the PyGame game. You don’t have to describe every object creation, but you should describe which functions get called and in what order. If something is created, you should describe that.
Plan your own ecosystem of objects¶
You can choose to start fresh now if you want, but you should make a plan with the following things:
- What are going to be the objects in your game
- Walls, hero, monsters, projectiles, etc
How the objects are going to interact?
- What is the smallest component in your game?
- You should plan on making this into a base class
- Then, you can create classes that subclass it and use it as its ancestor
- For example, you could have:
Entity
, which then is subclassed byMonster
If you should get to this point, you should start working on your game. Please let me know when you get here.
Cookbooks¶
Classes Cookbook¶
Design patterns and examples for classes! Use these to help you solve problems.
Defining a class¶
1 2 3 4 | class Dog:
def __init__(self, name, age):
self.name = name
self.age = age
|
Instantiating an object¶
1 2 | # create the object!
fido = Dog("Fido", 7)
|
Writing a method¶
A method is the name of a function when it is part of a class.
You always have to include self
as a part of the method arguments.
1 2 3 4 5 6 7 8 9 10 11 | class Dog:
def __init__(self, name, age):
self.name = name
self.age = age
def bark(self):
print("Bow wow!")
fido = Dog("Fido", 7)
fido.bark()
|
Using the self variable¶
You can access object variables through the self
variable.
Think of it like a storage system!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | class Dog:
def __init__(self, name, age):
self.name = name
self.age = age
def bark(self):
print("{}: Bow Wow!".format(self.name))
fido = Dog("Fido", 7)
fido.bark()
odie = Dog("Odie", 20)
odie.bark()
|
Using the property decorator¶
You can have complex properties that compute like methods but act like properties. Properties cannot accept arguments.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | class Dog:
def __init__(self, name, age):
self.name = name
self.age = age
def bark(self):
print("{}: Bow Wow!".format(self.name))
@property
def human_age(self):
return self.age * 7
fido = Dog("Fido", 7)
fido.bark()
print("Fido is {} in human years".format(fido.human_age))
|
Inheriting properties and methods¶
You can inherit properties and methods from the ancestors! For example, the initial function below is inherited.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | class Animal:
def __init__(self, name, age):
self.name = name
self.age = age
class Dog(Animal):
def bark(self):
print("{}: Bow Wow!".format(self.name))
@property
def human_age(self):
return self.age * 7
class Cat(Animal):
def meow(self):
print("{}: Meow!".format(self.name))
fido = Dog("Fido", 7)
fido.bark()
print("Fido is {} in human years".format(fido.human_age))
|
You can also override certain things and call the methods of the ancestor!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | class Animal:
def __init__(self, name, age, number_legs, animal_type):
self.name = name
self.age = age
self.number_legs = number_legs
self.animal_type = animal_type
def make_noise(self):
print("Rumble rumble")
class Dog(Animal):
def __init__(self, name, age):
super(Dog, self).__init__(name, age, 4, "dog")
def make_noise(self):
self.bark()
def bark(self):
print("{}: Bow Wow!".format(self.name))
@property
def human_age(self):
return self.age * 7
class Cat(Animal):
def __init__(self, name, age):
super(Dog, self).__init__(name, age, 4, "cat")
def make_noise(self):
self.meow()
def meow(self):
print("{}: Meow!".format(self.name))
fido = Dog("Fido", 7)
fido.make_noise()
print("Fido is {} in human years".format(fido.human_age))
garfield = Cat("Garfield", 5, 4, "cat")
garfield.make_noise()
|
Using the classmethod decorator¶
There is a nice Python syntax which lets you define custom creations for your objects.
For example, if you wanted certain types of dogs, you could do this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | class Animal:
def __init__(self, name, age, number_legs, animal_type):
self.name = name
self.age = age
self.number_legs = number_legs
self.animal_type = animal_type
def make_noise(self):
print("Rumble rumble")
class Dog(Animal):
def __init__(self, name, age, breed):
super(Dog, self).__init__(name, age, 4, "dog")
self.breed = breed
fido = Dog("Fido", 5, "Labrador")
|
But you could also do this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | class Animal:
def __init__(self, name, age, number_legs, animal_type):
self.name = name
self.age = age
self.number_legs = number_legs
self.animal_type = animal_type
def make_noise(self):
print("Rumble rumble")
class Dog(Animal):
def __init__(self, name, age, breed):
super(Dog, self).__init__(name, age, 4, "dog")
self.breed = breed
@classmethod
def labrador(cls, name, age):
return cls(name, age, "Labrador")
fido = Dog.labrador("Fido", 5)
|
Important parts:
- Instead
self
, it hascls
as its first argument. - This is a variable which points to the class being called.
- Instead
@classmethod
is right above the definition of the class.- It absolutely has to be exactly like this
- No spaces in between, just sitting on top of the class definition
- It’s called a decorator.
- It returns
cls(name, age, "Labrador")
. - This is exactly the same as
Dog("Fido", 5, "Labrador")
in this instance - Overall, it is letting you shortcut having to put in the labrador string.
- This is exactly the same as
- It returns
This is a simple example, but it is useful for more complex classes
Colors!¶
Get a list of colors into your game easily.
Create a file and put the following colors into it. Then, wherever you want to use them, you can just import them!
# assuming file is all_colors.py
from all_colors import *
The colors:
# some colors
# gray
BLACK = (0, 0, 0)
GRAY80 = (51, 51, 51)
GRAY60 = (102, 102, 102)
GRAY40 = (153, 153, 153)
GRAY20 = (204, 204, 204)
WHITE = (255, 255, 255)
# pink
LIGHT_PINK = (255, 150, 235)
PINK = (255, 100, 255)
MAGENTA = (255, 0, 255)
CORAL = (255, 160, 160)
# red
RED = (255, 0, 0)
BRICK_RED = (175, 25, 0)
SCARLET = (255, 75, 0)
# orange
ORANGE = (255, 110, 0)
LIGHT_ORANGE = (250, 160, 0)
# yellow
GOLD = (255, 215, 0)
YELLOW = (255, 255, 0)
# green
NEON_GREEN = (160, 255, 0)
LIGHT_GREEN = (0, 255, 0)
GREEN = (10, 150, 10)
FOREST_GREEN = (10, 75, 0)
# blue
CYAN = (0, 255, 255)
PERIWINKLE = (150, 150, 210)
LIGHT_BLUE = (0, 150, 255)
BLUE = (0, 0, 255)
NAVY_BLUE = (0, 0, 100)
# violet
ROYAL_BLUE = (100, 0, 255)
VIOLET = (150, 0, 255)
LAVENDER = (225, 150, 255)
# brown
BROWN = (120, 60, 0)
BEIGE = (255, 200, 150)
Heroes Cookbook¶
This is a set of recipes that you should use while solving problems!
Numbers¶
Integers¶
1 2 3 4 5 6 7 8 9 10 11 12 13 | # create an integer
x = 5
# convert an integer string
x = str('5')
# convert a float to an integer
## note: don't depend on this for rounding, it rounds in weird ways
x = int(5.5)
# convert a string of any number base
# for example, binary
x = int('1010101', base=2)
|
Floats¶
1 2 3 4 5 6 7 8 | # create a float
x = 5.5
# convert a float string
x = float("5.5")
# convert an integer to a float
x = float(5)
|
Basic math operations¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | x = 100
# 1. Add
x = x + 5
x += 5
# 2. Subtract
x = x - 5
x -= 5
# 3. Multiply
x = x * 5
x *= 5
# 4. Divide
x = x / 5
x /= 5
# 5. Power
x = x ** 2
x **= 2
|
Advanced math operations¶
1 2 3 4 5 6 7 8 | # 1. Integer Division
x = x // 5
x //= 5
# 2. Modulo
x = 84
x = x % 5
x %= 5
|
Use the math library¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | import math
x = 10
# pow is power, same as x ** 2
x = math.pow(x, 2)
# ceil rounds up and floor rounds down
x = 5.5
y = math.ceil(x) # y is 6.0
z = math.floor(x) # z in 5.0
# some other useful ones:
math.sqrt(x)
math.cos(x)
math.sin(x)
math.tan(x)
# this will give you pi:
math.pi
|
Strings¶
Add two strings together¶
1 2 3 4 | first_name = "euclid "
space = " "
last_name = "von rabbitstein"
full_name = first_name + space + last_name
|
Repeat a string¶
1 2 3 4 5 6 7 | message = "Repeat me!"
repeated10 = message * 10
# I like to use it for pretty printing code results
line = "-" * 12
print(" Title! ")
print(line)
|
Index into a string¶
1 2 3 4 5 | first_name = "Euclid"
last_name = "Von Rabbitstein"
first_initial = first_name[0]
last_initial = last_name[0]
initials = first_initial + last_initial
|
Slice a string¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | # the syntax is
# my_string[start:stop]
# this includes the start position but goes UP TO the stop
# you can leave either empty to go to the front or end
target = "door"
last_three = target[1:]
first_three = target[:3]
middle_two = target[1:3]
# you can use negatives to slice off the end!
all_but_last = target[:-1]
pig_latin = target[1:] + target[0] + "ay"
|
String’s inner functions¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | full_name = "euclid von Rabbitstein"
# all caps
full_name_uppered = full_name.upper()
# all lower
full_name_lowered = full_name.lower()
# use lower to make sure something is lower before you compare it
user_command = "Exit"
if user_command.lower() == "exit":
print("now I can exit!")
# first letter capitalized
full_name_capitalized = full_name.capitalize()
# split into a list
full_name_list = full_name.split(" ")
# strip off any extra spaces
test_string = " extra spaces everywhere "
stripped_string = test_string.strip()
# replace things in a string
full_name_replaced = full_name.replace("von", "rabbiticus")
# use replace to delete things from a string!
test_string = "annoying \t tabs in \t the string"
fixed_string = test_string.replace("\t","")
|
Built-in Functions¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | print("This prints to the console/terminal!")
# notice the space at the end!
# it helps so that what you type isn't right next to the ?
name = input("What is your name? ")
# use input to get an integer
age = input("How old are you?")
# but it's still a string!
# convert it
age = int(age)
# test the length of a list or string
name_length = len(name)
# get the absolute value of a number
positive_number = abs(5 - 100)
# get the max and min of two or more numbers
num1 = 10**3
num2 = 2**5
num3 = 100003
biggest_one = max(num1, num2, num3)
smallest_one = min(num1, num2, num3)
# can do any number of variables here
# max(num1, num2) works
# and max(num1, num2, num3, num4)
## max/min with a list
ages = [12, 15, 13, 10]
min_age = min(age)
max_age = max(age)
# sum over the items in a list
# more list stuff is below
ages = [12, 15, 13, 10]
sum_of_ages = sum(ages)
number_of_ages = len(ages)
average_age = sum_of_ages / number_of_ages
|
Boolean algebra¶
Create a literal boolean variable¶
1 2 | literal_boolean = True
other_one = False
|
Create a boolean variable from comparisons¶
1 2 3 4 5 | x = 9
y = 3
x_is_bigger = x > y # True
x_is_even = x % 2 == 0 # False
x_is_multiple_of_y = x % y == 0 # True
|
Combine two boolean variables with ‘and’ and ‘or’¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | # example data
card_suit = "Hearts"
card_number = 7
# save the results from comparisons!
card_is_hearts = card_suit == "Hearts"
card_is_diamond = card_suit == "Diamond"
card_is_big = card_number > 8
# only 1 of them needs to be true
card_is_red = card_is_hearts or card_is_diamond
# both need to be true
card_is_good = card_is_red and card_is_big
# creates the opposite!
card_is_bad = not card_is_good
|
If, elif, and else¶
Use an if to test for something¶
1 2 3 4 5 6 7 8 9 10 | power_level = 1000
min_power_level = 500
max_power_level = 1000
# one thing is larger than another
if power_level > minimum_power_level:
print("We have enough power!")
if power_level == max_power_level:
print("You have max power!")
|
Create conditional logic¶
1 2 3 4 5 6 7 8 9 10 | selected_option = 2
if selected_option == 1:
print("Doing option 1")
elif selected_option == 2:
print("Doing option 2")
elif selected_option == 3:
print("doing option 3")
else:
print("Doing the default option!")
|
Nest one if inside another if¶
1 2 3 4 5 6 7 8 9 10 11 12 | name = "euclid"
animal = "bunny"
if animal == "bunny":
if name == "euclid":
print("Euclid is my bunny")
elif name == "leta":
print("Leta is my bunny")
else:
print("this is not my bunny..")
else:
print("Not my animal!")
|
Lists¶
Create an empty list¶
1 2 3 | new_list = list()
# or
new_list = []
|
Create a list with items¶
1 | my_pets = ['euclid', 'leta']
|
Add onto a list¶
1 | my_pets.append('socrates')
|
Index into a list¶
1 2 3 | first_pet = my_pets[0]
second_pet = my_pets[1]
third_pet = my_pets[2]
|
Slice a list into a new list¶
1 2 3 4 5 6 7 | # the syntax is
# my_list[start:stop]
# this includes the start position but goes UP TO the stop
# you can leave either empty to go to the front or end
first_two_pets = my_pets[:2]
last_two_pets = my_pets[1:]
|
Test if a value is inside a list¶
1 2 3 4 5 6 | ## with any collection, you can test if an item is inside the collection
## it is with the "in" keyword
my_pets = ['euclid', 'leta']
if 'euclid' in my_pets:
print("Euclid is a pet!")
|
Sets¶
Create a set or convert a list to a set¶
1 2 3 4 5 6 7 8 9 10 | my_pet_list = ['euclid', 'leta']
# you can convert lists to sets using the set keyword
my_pet_set = set(my_pet_list)
# sets are like lists but you can't index into them or slice them
# they are used for fast membership testing
# you can create a new set by:
my_pet_set = set(['euclid', 'leta'])
|
Add an item to a set¶
1 2 3 4 | my_new_set = set()
# instead of append, like a list, you use 'add'
my_new_set.add("Potatoes")
|
Using sets to enforce uniqueness¶
1 2 3 4 5 6 | my_grocery_list = ['potatoes', 'cucumbers', 'potatoes']
# now if you want to make sure items only appear once, you can convert it to a set
# it will automatically do this for you, because items are only allowed to be in sets one time
my_grocery_set = set(my_grocery_list)
|
For Loops¶
Write a for loop¶
1 2 | for i in range(10):
print("do stuff here")
|
Use the for loop’s loop variable¶
1 2 3 4 | for i in range(10):
new_number = i * 100
print("The loop variable is i. It equals {}".format(i))
print("I used it to make a new number. That number is {}".format(new_number))
|
Use range inside a for loop¶
1 2 3 4 5 6 7 8 9 10 11 12 | start = 3
stop = 10
step = 2
for i in range(stop):
print(i)
for i in range(start, stop):
print(i)
for i in range(start, stop, step):
print(i)
|
Use a list inside a for loop¶
1 2 3 4 | my_pets = ['euclid', 'leta']
for pet in my_pets:
print("One of my pets: {}".format(pet))
|
Nest one for loop inside another for loop¶
1 2 3 4 | for i in range(4):
for j in range(4):
result = i * j
print("{} times {} is {}".format(i, j, result))
|
While Loops¶
Use a comparison¶
1 2 3 4 5 | response = ""
while response != "exit":
print("Inside the loop!")
response = input("Please provide input: ")
|
Use a boolean variable¶
1 2 3 4 5 6 7 | done = False
while not done:
print("Inside the loop!")
response = input("Please provide input: ")
if response == "exit":
done = True
|
Loop forever¶
1 2 | while True:
print("Don't do this! It is a bad idea.")
|
Special Loop Commands¶
Skip the rest of the current cycle in the loop¶
1 2 3 4 5 | for i in range(100):
if i < 90:
continue
else:
print("At number {}".format(i))
|
Break out of the loop entirely¶
1 2 3 4 | while True:
response = input("Give me input: ")
if response == "exit":
break
|
Functions¶
No arguments and returns nothing¶
1 2 | def say_hello():
print("hello!")
|
Takes one argument¶
1 2 | def say_something(the_thing):
print(the_thing)
|
Returns a value¶
1 2 | def double(x):
return 2*x
|
Takes two arguments¶
1 2 3 4 5 | def exp_func(x, y):
result = x ** y
return result
final_number = exp_func(10, 3)
|
Takes keyword arguments¶
1 2 3 4 5 6 | def say_many_times(message, n=10):
for i in range(n):
print(message)
say_many_times("Hi!", 2)
say_many_times("Yay!", 10)
|
Time module¶
Using time.time() to count how long something takes¶
1 2 3 4 5 6 7 8 9 10 | import time
start = time.time()
for i in range(10000):
continue
new_time = time.time()
total_time = new_time - start
print(total_time)
|
Using time.sleep(n) to wait for n seconds¶
1 2 3 4 5 6 7 8 9 | import time
start = time.time()
time.sleep(10)
end = time.time()
print(start - end)
|
Random Module¶
Generate a random number between 0 and 1¶
1 2 3 4 | import random
num = random.random()
print("the random number is {}".format(num))
|
Generate a random number between two integers¶
1 2 3 4 | import random
num = random.randint(5, 100)
print("the random integer between 5 and 100 is {}".format(num))
|
Select a random item from a list¶
1 2 3 4 5 | import random
my_pets = ['euclid', 'leta']
fav_pet = random.choice(my_pets)
print("My randomly chosen favorite pet is {}".format(fav_pet))
|
Cookbook¶
A set of common recipes and design patterns for pygame with classes
Game Loop¶
The main game logic can be divided into two parts:
Initialize the variables
- Run the game loop which does the following steps:
- Handle Events
- Update objects
- Draw
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | import pygame
class Game:
def initialize(self):
## start pygame's engines
pygame.init()
## get a screen
self.screen = pygame.display.set_mode(WINDOW_SIZE)
## get a clock used for FPS control
self.clock = pygame.time.Clock()
self.example_box = pygame.Rect(0, 0, 100, 100)
def run(self):
## a simple flag variable for the loop
done = False
## the main game loop
while not done:
### 1. Events
## the event loop; used to check for events that occurred since the last time around
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
### 2. Updates
## update the example box with whatever you want
self.example_box.x += 1
## 3. Drawing
pygame.draw.rect(self.screen, BLACK, self.example_box)
#### update the display and move forward 1 frame
pygame.display.flip()
# --- Limit to 60 frames per second
self.clock.tick(FPS)
|
Basic Sprites¶
There are several ways to include objects, monsters, obstacles, etc in your pygame code. The best way is to define your own classes that inherit from pygame’s Sprite class.
You should think of this as defining recipes for different objects in your game. In this section, there are the following recipes:
- A basic sprite
- the core components of a sprite and how to use them
- Adding the drawing function to the basic sprite
- You can put the logic for the sprites inside the class, so it makes the game logic cleaner
- Your game shouldn’t have to worry about how sprites get drawn!
- Colliding with one other sprite
- Colliding with another sprite is handled just like in the simple case
- The trick is to correctly identify how the collision happened so you can fix it!
- Using Groups of sprites
- Group is a special pygame object that gives us extra shortcuts!
- Colliding with many sprites
- Using a Group, we can easily get the list of sprites our main sprite is colliding with
- Adding an image to your sprite
- Usually you will want to draw more than basic shapes. This will show you how!
- Adding event handling to your sprite
- If you want your sprite to do things, it should handle its own event logic!
- This means that the game just gives the events to the sprite and the sprite does what it needs to do.
- Making an animated sprite
- This will show you how the basic animation happens
Basic Sprite¶
For our basic sprite, we will subclass pygame’s sprite class. Subclassing means that we will tell python that our new class is the exact same as pygame’s sprite class. Then, whatever we can specialize any parts we want.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | class BasicSprite(pygame.sprite.Sprite):
# by defining this function, we are overriding the parent class's function
def __init__(self, color, width, height):
# this is a special command which tells python to execute the parent's function
# the pattern is
# super(ThisClassName, self).func_to_call()
super(BasicSprite, self).__init__()
### When you sublcass the sprite, you need two things
# 1. self.image
self.image = pygame.Surface([width, height])
self.image.fill(color)
# 2. self.rect
self.rect = self.image.get_rect()
# self.rect starts out at 0,0. if you want to change the location, you have to update these coordinates
# this hard codes the BasicSprite to start at the coordinates 50,50
self.rect.x = 50
self.rect.y = 50
|
You can use this class in the same places you would before:
- Instantiate (create) the object at the beginning of the game
- Update the coordinates inside the game loop
- Draw the coordinates inside the game loop
One of the nice features about using sprites is that we only have to draw the
sprite’s self.image
property. We do this with the following:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | class Game:
def initialize(self):
# other code was here
# just remember that our screen is made here
self.screen = pygame.display.set_mode(WINDOW_SIZE)
self.example_object = BasicSprite(BLACK, 100, 100)
def run(self):
done = False
## the main game loop
while not done:
# other code was here
## the way to read this dot notation is:
## inside this Game object access (using "self") a variable called example_object
## inside example_object is the property "image" (which we defined just above)
## inside image is a function called blit
## blit takes two arguments:
## 1. the surface it should draw on, this is our screen.
## 2. the coordinates of where to draw it. this is the rect inside example_object
## overall, the syntax is:
## surface_variable.blit(screen_variable, rect_variable)
self.example_object.image.blit(self.screen, self.example_object.rect)
## then don't forget the rest of the code here
|
So, to summarize:
Subclass pygame’s
Sprite
class and define theself.image
andself.rect
.- Inside the
Game
object’s initialize function, use the class to make a new object - save this object to the
self
variable so we can access it later
- save this object to the
- Inside the
- Inside the
Game
object’s run function, use the saved object to draw - the syntax for drawing a sprite is showing above.
- You are calling
blit
to draw the sprite’s surface onto the main surface.
- Inside the
Adding the drawing function to the basic sprite¶
Doing that drawing logic inside the game loop is a bit messy. Also, maybe we want to change how we draw the object based on some situation. We don’t want to have the main game loop get all messy with that code.
To solve this problem, we put a draw
function inside the BasicSprite
class
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | class BasicSprite(pygame.sprite.Sprite):
# by defining this function, we are overriding the parent class's function
def __init__(self, color=BLACK, width=100, height=100):
# notice it has default values for its paremeters!
# this is a special command which tells python to execute the parent's function
# the pattern is
# super(ThisClassName, self).func_to_call()
super(BasicSprite, self).__init__()
### When you sublcass the sprite, you need two things
# 1. self.image
self.image = pygame.Surface([width, height])
self.image.fill(color)
# 2. self.rect
self.rect = self.image.get_rect()
# self.rect starts out at 0,0. if you want to change the location, you have to update these coordinates
# this hard codes the BasicSprite to start at the coordinates 50,50
self.rect.x = 50
self.rect.y = 50
def draw(self, screen):
# draw this object's image onto the passed in screen variable
screen.blit(self.image, self.rect)
|
Moving a sprite¶
Moving a sprite is really easy! Everytime through the game loop, the sprite is drawn
using its internal rect
object, which stores the location coordinates.
To move it, we just change those coordinates before it is drawn!
We are going to have a theme with this code. Any functionality we want our
BasicSprite
to have, we will put it inside that class!
To illustrate how you can subclass and keep specializing, let’s subclass our previous
BasicSprite
to make a MovingSprite
:
1 2 3 4 5 6 7 8 9 10 | class MovingSprite(BasicSprite):
# MovingSprite has all the functions and properties that
# BasicSprite has
def move(self, dx, dy):
## move dx units in the x direction
## move dy units in the y direction
self.rect.x += dx
self.rect.y += dy
|
Now, let’s change one more thing about this. Let’s alter the __init__
function
so that the dx and dy are internal!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | class MovingSprite(BasicSprite):
# MovingSprite has all the functions and properties that
# BasicSprite has
def __init__(self, color=BLACK, width=100, height=100):
super(MovingSprite, self).__init__(color, width, height)
self.dx = 0
self.dy = 0
def move(self):
## move dx units in the x direction
## move dy units in the y direction
self.rect.x += self.dx
self.rect.y += self.dy
|
Colliding with one other sprite¶
Pygame provides several ways to handle collisions with sprite objects.
From the documentation, it says the following thing:
pygame.sprite.collide_rect()
Collision detection between two sprites, using rects.
collide_rect(left, right) -> bool
Tests for collision between two sprites. Uses the pygame rect colliderect function to calculate the collision.
Intended to be passed as a collided callback function to the *collide functions. Sprites must have a “rect” attributes.
Basically, this means that you can give this function two sprites and it will tell
you True
or False
.
We are going to have a theme with this code. Any functionality we want our sprite objects to have, we will put it inside that class!
To illustrate how you can subclass and keep specializing, let’s subclass our previous
BasicSprite
to make a CollisionSprite
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 | class CollisionSprite(BasicSprite):
# CollisionSprite has all the functions and properties that
# BasicSprite has, which has all of the functions BasicSprite has!
def handle_collision(self, other_sprite, dx, dy):
# we are going to define the logic for handling the collision with
# one other sprite
# there are two extra variables this function is taking.
# they are the dx and dy. we need these so we know which direction
# the sprite is moving!
# Note: we want to make sure we only move x or y.
# if we are moving both, then we don't know whether the collision
# is from the top/bottom or from the sides.
if dx != 0 and dy != 0:
# this syntax is:
# "raise" is a way of manually throwing errors and exceptions
# "Exception" is the default exception
# by doing
# raise Exception(some_message)
# we are stopping the program and causing an error.
raise Exception("ERROR: don't move both x and y at the same time; Collision checking is impossible if you do this!")
if pygame.sprite.collide_rect(self, other_sprite):
## if this "if" is true, then this means a collision is happening!
## let's check and see which direction it is
## check if the sprite is moving in the x direction:
# if dx is positive, it is moving right
# if the right side is past the other rect's left, snap them together
if dx > 0 and self.rect.right > other_sprite.rect.left:
self.rect.right = other_sprite.rect.left
# if dx is negative, it is moving up
# if the left side is past the other rect's right, snap them together
elif dx < 0 and self.rect.left < other_sprite.rect.right:
self.rect.left = other_sprite.rect.right
# if dy is positive, it is moving down
# if the bottom is past the other rect's top, snap them together
if dy > 0 and self.rect.bottom > other_sprite.rect.top:
self.rect.bottom = other_sprite.rect.top
# if dy is negative, it is moving up
# if the top is past the other rect's bottom, snap them together
elif dy < 0 and self.rect.top < other_sprite.rect.bottom:
self.rect.top = other_sprite.rect.bottom
## Let's re-write the move function from before to handle collisions
def move(self, other_sprite=None):
## we will assume that we are given access to a single other sprite
## as an argument to this function
## we will give it a default value of None though, so it's only optional
## move dx units in the x direction
self.rect.x += self.dx
if other_sprite is not None:
# handle the x collision!
self.handle_collision(other_sprite, self.dx, 0)
## move dy units in the y direction
self.rect.y += self.dy
if other_sprite is not None:
# handle the y collision!
self.handle_collision(other_sprite, 0, self.dy)
|
Using Groups of sprites¶
Pygame’s Group
class is really useful for storing objects.
We would use it inside the initialize
function of Game
so store
each of the sprites that we create.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | class Game:
def initialize(self):
# other code was here
# just remember that our screen is made here
self.screen = pygame.display.set_mode(WINDOW_SIZE)
## use group to manage a list of basic sprites
self.basic_sprites = pygame.sprite.Group()
# let's create a couple basic sprites
for i in range(5):
# create the new sprite
# notice no self variable
# that's because I know I'm not saving this inside self
# instead, I'm saving this inside self.basic_sprites
new_sprite = BasicSprite(BLACK, 100, 100)
# doing this to offset the sprites so we can see them
new_sprite.rect.x += i * 50
new_sprite.rect.y += i * 50
# save it to self.basic_sprites
self.basic_sprites.add(new_sprite)
def run(self):
done = False
## the main game loop
while not done:
# other code was here
# because you used a group to handle the basic sprites, you
# can shortcut the drawing of them by using group's draw function:
self.basic_sprites.draw(self.screen)
|
Colliding with many sprites¶
First, we are going to add some functionality to our CollisionSprite
to handle group collisions!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | class GroupCollisionSprite(CollisionSprite):
# CollisionSprite has all the functions and properties that
# CollisionSprite has, which has all of the functions CollisionSprite has!
def handle_group_collision(self, sprite_group, dx, dy):
# we pass in the "sprite_group", and the movements again
# the False here is the option to remove all sprites being collided with
# from the group.
# if True, sprite_group will no longer have them and they won't be drawn anymore
# the returned object, colliding_sprites, is a list of sprites!
colliding_sprites = pygame.sprite.spritecollide(self, sprite_group, False)
# go through each of the sprites in this list
for sprite in colliding_sprites:
# use the function from CollisionSprite to handle this!
self.handle_collision(sprite, dx, dy)
## Let's re-write the move function from before to handle group collisions
def move(self, collision_group=None):
## we will assume that we are given access to a single other sprite
## as an argument to this function
## we will give it a default value of None though, so it's only optional
## move dx units in the x direction
self.rect.x += self.dx
# make sure it's not the default value
if collision_group is not None:
# handle the x collision!
self.handle_group_collision(collision_group, self.dx, 0)
## move dy units in the y direction
self.rect.y += self.dy
# make sure it's not the default value
if collision_group is not None:
# handle the y collision!
self.handle_group_collision(collision_group, 0, self.dy)
|
Now that we have GroupCollisionSprite
which can handle colliding with a group
of sprites, let’s add it into Game
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | class Game:
def initialize(self):
# other code was here
# just remember that our screen is made here
self.screen = pygame.display.set_mode(WINDOW_SIZE)
## use group to manage a list of basic sprites
self.basic_sprites = pygame.sprite.Group()
# let's create a couple basic sprites
for i in range(5):
# create the new sprite
# notice no self variable
# that's because I know I'm not saving this inside self
# instead, I'm saving this inside self.basic_sprites
new_sprite = BasicSprite(BLACK, 100, 100)
# doing this to offset the sprites so we can see them
new_sprite.rect.x += i * 50
new_sprite.rect.y += i * 50
# save it to self.basic_sprites
self.basic_sprites.add(new_sprite)
# it has the same __init__ function as BasicSprite
self.hero = GroupCollisionSprite(BLACK, 100, 100)
def run(self):
done = False
## the main game loop
while not done:
# other code was here
# remember the loop order:
# Events, Updates, and then Draw
# Updates is where collisions and movement goes
# let's move the hero and have it handle sprite collision!
self.hero.move(self.basic_sprites)
# because you used a group to handle the basic sprites, you
# can shortcut the drawing of them by using group's draw function:
self.basic_sprites.draw(self.screen)
self.hero.draw(self.screen)
|
Adding an image to your sprite¶
Adding an image is super easy! The main thing is to change how self.image
gets defined!
Since our class, GroupCollisionSprite
has so much functionality now, let’s just subclass it
and override the __init__
function:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | class ImageSprite(GroupCollisionSprite):
def __init__(self, image_filename, colorkey=WHITE):
# because all of the arguments in BasicSprite were optional, we
# can just call the init function
super(ImageSprite, self).__init__()
# now, we overwrite image
self.image = pygame.image.load(image_filename).convert()
# Set our transparent color
self.image.set_colorkey(colorkey)
# refresh the rect now
self.rect = self.image.get_rect()
|
And that’s it!
If you wanted to do this without subclassing GroupCollisionSprite
, you
could just subclass pygame.sprite.Sprite
again and define self.image
in this way.
Adding event handling to your sprite¶
It’s really useful to be able to handle keyboard input! In fact, if you want people to play your game, it has to be able to handle input.
There are two ways you could do this. You could add code inside Game
which will
manually update the hero
. But we don’t want Game
to care about such things!
So, instead, we will let Game
just give every single event to the hero!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | class Game:
def run(self):
done = False
## the main game loop
while not done:
## the event loop
## the event loop; used to check for events that occurred since the last time around
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
else:
# if the event isn't a quitting event, give it to the hero!
self.hero.handle_event(event)
|
And that’s it! Now, writing this code creates an expectation from python that
our hero
will have this function implemented. So, let’s do that.
class EventHandlingSprite(ImageSprite):
# I inherited from the ImageSprite
# if you don't want to do this, you can replace ImageSprite with GroupCollisionSprite
# since that was our second most advanced sprite so far
# remember, because we are inheriting, we get all of the functionality from before!
def handle_event(self, event):
# there are a couple of different pygame events:
if event.type == pygame.KEYDOWN:
# this is a keydown event
# this means a key is pressed
if event.key == pygame.K_LEFT:
self.dx = -5
elif event.key == pygame.K_RIGHT:
self.dx = 5
elif event.type == pygame.KEYUP:
# this is a keyup event
# this means a key was let go
if event.key == pygame.K_LEFT:
self.dx = 0
elif event.key == pygame.K_RIGHT:
self.dx = 0
Thsi is really simple event handling. For instance, if you press two keys at once, this will have some weird results. But at least it will handle some input!
To overcome the two-keys-at-once problem, you will have to do something a bit more complicated.
For instance, you could have the left key subtract 5 from self.dx
and then
use min
to make sure it is never smaller than -5. You could also have some
boolean variables that are internal to the sprite which keep track of which keys have been pressed.
Making an animated sprite¶
Basic Game Physics¶
Physics is very important to games! Since you are telling the game how each object updates, you have to use math to update the objects to match how physics works. This can sometimes be hard, but there are plenty of ways to make it easier.
In this section, there are the following recipes:
- Bouncing off walls
- If an object is moving in a direction and encounters an obstacle, it could bounce
- Bouncing in certain ways looks and feels weird
- So, you should bounce in a way that feels real!
- Gravity
- Instead of letting objects freely move in both x and y directions, gravity constantly affects the y!
- You can think of this as making so that your object always wants to be moving down at 9 units at a time
- Jumping
- Jumping is just the opposite of gravity
- When the jump happens, there is a force which makes the object want to move up at 9 units!
- In other words, the y speed is set to -9
- Then, every frame, the speed slowly goes back to +9.
Handling Keyboard Input¶
- Basic keyboard input
- handle single keys
- do specialized things
- Continuous keyboard input
- continue to do something until key is released
- this is basically the example in the earlier section!
- Advanced continuous keyboard input
- use extra variables to keep track of which key was pressed!
Scoreboards¶
- Drawing an extra surface that never moves
- In the same logic as the sprite, except that it doesn’t move and is always drawn last.
Cookbook¶
A set of common recipes and design patterns
Game Loop¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | import pygame
## start pygame's engines
pygame.init()
## set the screen size
WINDOW_SIZE = (700, 500)
## get a screen
screen = pygame.display.set_mode(WINDOW_SIZE)
## get a clock used for FPS control
clock = pygame.time.Clock()
## a simple flag variable for the loop
done = False
## the main game loop
while not done:
## the event loop; used to check for events that occurred since the last time around
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
#### update the display and move forward 1 frame
pygame.display.flip()
# --- Limit to 60 frames per second
self.clock.tick(FPS)
|
Drawing¶
Using Rect to draw¶
Rect is a useful PyGame class that is a wrapper around the standard rectangle information.
x = 0
y = 0
width = 100
height = 100
r1 = pygame.Rect(x, y, width, height)
The variable r1
now has access to a variety of different properties
x,y
top, left, bottom, right
topleft, bottomleft, topright, bottomright
midtop, midleft, midbottom, midright
center, centerx, centery
size, width, height
w,h
You can also update r1
using any of those variables. For example:
1 2 3 | r1.center = (50,50)
r1.right = 10
r1.bottomright = 75
|
Bouncing off obstacles¶
Basic collision detection with screen boundaries¶
In the simplest case, we are testing to see if our rect is over some threshold. This happens in the case of bouncing off the edges of the screen. For this example, we assume we know the height and width of the window as well.
1 2 3 4 5 6 7 8 9 10 11 | # W, H are window width and window height
if r1.right > W:
print("Over right side")
elif r1.left < 0:
print("over left side")
if r1.top < 0:
print("Over top")
elif r1.bottom > H:
print("Over bottom")
|
Changing direction based on screen boundary collision¶
Let’s assume that the object in question is moving at some speed. In other words,
the x
and y
properties are being updated by some variable
dx
and dy
. Then, when the object bounces,
it should flip the signs of those speeds.
1 2 3 4 5 6 7 8 9 | # W, H are window width and window height
r1.x += dx
if r1.right > W or r1.left < 0:
dx *= -1
r1.y += dy
if r1.top < 0 or r1.bottom > H:
dy *= -1
|
Colliding with another Rect¶
If you wanted to collide with another Rect, there are several different ways you could it. The easiest way is to use the built in functions which test for collision. However, these functions don’t tell you which parts collided. An example of why this is a problem:
- There is a collision with a Rect and an obstacle from the bottom
- The Rect’s right side is technically past the obstacle’s left
- But, the issue is the y-movement, not the x-movement.
The first part of the solution is to update the X and Y parts separately. With this method, one dimension is changed and checked for collisions. Then, the other is changed and checked for collisions.
The second part of the solution is to “snap” the edges of the object and the obstacle together. This just means making them line up exactly so no more collision is taking place.
The below code illustrates the Rect collision code, the separate x and y movements, and the edge snapping.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | '''
in this example, self.rect is the rect of the object you are moving
'''
def move(self, dx, dy, other_rects):
# move this object in the x direction
self.rect.x += dx
# go over each obstacle
for other_rect in other_rects:
# if there is a collision
# since we moved only the x, we know it has to be this object's left or right
if self.rect.colliderect(other_rect):
# if dx is positive, it is moving right
# if the right side is past the other rect's left, snap them together
if dx > 0 and self.rect.right > other_rect.left:
self.rect.right = other_rect.left
# if dx is negative, it is moving left
# if the left side is past the other rect's right, snap them together
elif dx < 0 and self.rect.left < other_rect.right:
self.rect.left = other_rect.right
# move this object in the y direction
self.rect.y += dy
# go over each obstacle
for other_rect in other_rects:
# if there is a collision
# since we moved only the y, we know it has to be this object's top or bottom
if self.rect.colliderect(other_rect):
# if dy is positive, it is moving down
# if the bottom is past the other rect's top, snap them together
if dy > 0 and self.rect.bottom > other_rect.top:
self.rect.bottom = other_rect.top
# if dy is negative, it is moving up
# if the top is past the other rect's bottom, snap them together
elif dy < 0 and self.rect.top < other_rect.bottom:
self.rect.top = other_rect.bottom
|