In this project you will be building your own 2D game that will read user input, display graphics, and simulate 2D physics!
- Python 3.5
- Install SDL2, SDL2_image, SDL2_ttf via
apt
:sudo apt install libsdl2-dev libsdl2-image-dev libsdl2-ttf-dev
- Install pysdl2 via
pip3
:pip3 install --user pysdl2
Optional:
mypy
- The Window (game.py)
- The Renderer and Game Loop (game.py)
- The Game State and Update Method (game.py)
- Render Player Texture (player.py)
- Physics (player.py)
- Collision Detection and AABB (tilemap.py)
- Camera (game.py)
- Stopwatch (stopwatch.py)
Textbox, Point2d, Vector2d, Controller, Stopwatch
Follow the steps in each objective to complete your game. You may customize your game to your liking after completing each objective.
New Programming Concepts:
- Tuples
- Classes
- Keyword Arguments
- Module Variables
- Local Variables
- Method (aka. Member Functions)
Steps:
- Choose a title for the window, store it as a string in a module variable
- Choose a size for the window, store it as a tuple of two integers in a module variable
- Instantiate a
Window
object, passing the title as the first argument, and the size as a named argument with namesize
, and... - Store the created
Window
instance in awindow
variable in themain
function - Call the
show
method on theWindow
object
Documentation for Window
:
Resources:
Classes in Python Practice - https://www.learnpython.org/en/Classes_and_Objects
Sharing variables across files - http://effbot.org/pyfaq/how-do-i-share-global-variables-across-modules.htm
New Programming Concepts:
- Properties
- RGB colors
while
loopbreak
statement
Steps:
- Instantiate a
Renderer
object, passing the window from Objective 1 as its only argument, and... - Store the created
Renderer
instance in arenderer
variable in themain
method - Instantiate a
Color
object with three keyword arguments (r
,g
, andb
), store it in a local variable in themain
function - Assign the created color to the
color
property on theRenderer
object - Inside the game loop, call the
clear
andpresent
methods on theRenderer
object
I used red = 110, green = 132, blue = 174
Documentation for Renderer
and Color
:
- https://pysdl2.readthedocs.io/en/rel_0_9_5/modules/sdl2ext_sprite.html
- https://pysdl2.readthedocs.io/en/rel_0_9_5/modules/sdl2ext_color.html
Questions:
- When does the game loop exit?
Resources:
While Loops in Python - https://pythonschool.net/basics/while-loops/
New Game Concepts:
- Game Loop
- Frame
- Time Step
Steps:
- Instantiate a
Game
object, passing theResources
object as the only argument - In between the
clear
andpresent
calls to theRenderer
object, call therender
method on theGame
object. Pass it the appropriate arguments. - Before the rendering, call the
update
method the appropriate number of frames.
For step 3. you need to use a while
-loop, the local lag
variable, and the SECONDS_PER_UPDATE
module variable.
The lag
variable represents the amount of time in seconds that has yet to be updated. Each time we process a frame, the lag should be decreased by the amount of time that frame represents (i.e. the SECONDS_PER_UPDATE
).
Hint: modify the
lag
variable each time though the loop as appropriate, and use it to check for the finished condition
Additional Reading:
http://gameprogrammingpatterns.com/game-loop.html
New Programming Concepts:
- Unpacking a Tuple
for
loop- Modules
import
- Member variable
New Graphics Concepts:
- Point
- Vector
- Sprite Sheet
Steps:
- Find the correct coordinates for the player texture to be drawn
- Read the explanation on what is happening to the sprite sheet
- Use the
renderer.copy
method to draw each part of the player texture in the correct place - Import the player module into the game module
- Create a Player member variable on the Game object called
player
(make sure to pass it the Resources) - Call the render method on the game's player object during the game's render method (make sure to pass it the camera)
For step 1, consider the following diagram:
|--1--2--3--4--| <-- x-axis screen (int)
| * | <-- player
0--1--2--3--|--5--6--7--8--| <-- x-axis world (float)
^ <-- camera
For step 2, read:
We need to cut up the player sheet into an array of tuples. The upper left is (0, 0). The total size is 256x128.
If you divide the sections into 32x32 squares it looks like:
|a a a|b b b|-|-| a = body
|a a a|b b b|c c| b = body shadow
|a a a|b b b|d d| c = foot
| |e|-|-|-|-|-| d = foot shadow
e = basic eye (left)
We take the pieces and overlay them on top of each other:
( dx, dy, w, h)
1. foot shadow (back) -> (-60, 0, 96, 48) <-- 3/2 scale
2. foot (back) -> (-60, 0, 96, 48) <-- 3/2 scale
3. body shadow -> (-48, -48, 96, 96)
4. body -> (-48, -48, 96, 96)
5. foot shadow (front) -> (-36, 0, 96, 48) <-- 3/2 scale
6. foot (front) -> (-36, 0, 96, 48) <-- 3/2 scale
7. basic eye (left) -> (-18, -21, 36, 36) <-- 9/8 scale
8. basic eye (right) -> ( -6, -21, 36, 36) <-- 9/8 scale *FLIPPED*
For step 3. you will be given the sprite sheet mappings that split it up like a puzzle and rebuild it using render.copy
.
The parameters are the texture
followed by the unpacked tuple variables:
- source - the x, y, w, h of the texture puzzle piece to "cut out"
- destination - the x, y, w, h of where to draw it on screen
- flipped - whether to flip the image and how (i.e. vert/hort)
The flipped parameter is a keyword argument named flip
.
Use a for loop to iterate over and unpack the body parts.
Bonus: Feel free to tweak the mappings from step 2. and see what happens :)
Bonus: Get a different player texture here: https://ddnet.tw/skins/
New Programming Concepts:
- Reading a File
Steps:
- Get the path of the tilemap file from the Resources object
- Open the file of the tilemap file for reading
- Read through each line
- For each line split it into the numbers (requires conversion from string to int)
- Store each number in the tiles array
- Along the way keep track of the width and height of the tilemap (i.e. each line adds to the height, the number of numbers in a line is the width)
- Read the lookup function for transforming world locations into tiles
- Convert from the two dimensional (nx, ny) = (col, row) values back into tiles index for tile lookup
- import, create, and add a Tilemap to the Game object
Bonus: verify that the width is the same in each line/row of the tilemap
New Game Concepts:
- The Update Method
- Physics Engine
- Collision Detection
Steps:
- Call the player update method from the game update method
- Use the tilemap to move the player by the new velocity determined by the physics logic
Our physics engine is going to have the following traits:
- gravity (constant acceleration)
- horizontal friction
- terminal horizontal velocity
- acceleration-based running
- velocity-based jumping
The physics engine is what determines the feel of the character movement.
Here how ours determines the velocity to apply to the player:
- If user wants to jump (and we are on the ground) vertical velocity is set to 21 pixels / frame
- Gravity is applied every frame to decrease velocity by a 0.75 pixels / frame
- If we are on the ground, the horizontal velocity slows to a half of what it was
- Otherwise, the horizontal velocity shows to 95% of what it was
- If we are on the ground and the user is moving the horizontal velocity is increased by 4 pixels / frame in that direction
- If we are in the air and the user is moving the horizontal velocity is increased by 2 pixels / frame in that direction
- The horizonal velocity is capped at 8 pixels / frame
Completing step 2 then does:
- Modify the player position by the new velocity vector
- If any movement along our velocity vector would move us into a solid tile our velocity drops to 0 in that direction, and we move up to the edge of the solid tile in the direction of the vector.
Bonus: Play with the physics engine components and see what happens
Bonus: Try modifying the
UPDATES_PER_SECOND
variable and see what happens
Additional Reading:
http://gameprogrammingpatterns.com/update-method.html
Set up camera movement to follow the player.
Steps:
- Find the Objective 7 markers in the code and follow directions
Augment the Game
update method.
- Get the tile type from the tile map for the player position
- If the tile type is a start tile, then call start on the stopwatch
- If the tile type is an end tile, then call stop on the stopwatch
- Otherwise, just call step on the stopwatch
- Build your own map?
- Play with the physics engine
- Change player skin
- Add a second player... can they race?
- Double Jump?
https://pysdl2.readthedocs.io/en/rel_0_9_5/
https://hookrace.net/blog/writing-a-2d-platform-game-in-nim-with-sdl2/