Learning Python Programming - part 7.1 - Let's make a game

in #programming8 years ago

Welcome to the 7.1st part of an ongoing series, Learning Python!

Let's just code something already!


(source)

What should our game be?

What if you are a survivor and you need to shoot all the zombies on the screen while picking up ammo drops and avoiding getting eaten.

For this game we'll use keyboard input, mouse input, angular velocity math, tkinter to display, and classes.

How do we even start coding this?


(source)

To start coding this game, we need to look at what we will need for our minimum viable product.
In this case our minimum viable product will be a dot that runs around the screen with keyboard input; after we program that, we can add more features like the zombies and shooting, then getting eaten and power-ups!

Now that we've broken our project down into some design steps, let's start coding!

What first?

To program our minimum viable product, we need a screen. Let's take a look at the previous tkinter tutorial to learn how to make a screen. For those of you who remember, this will be review and you can skip down to the next step.

import tkinter as tk

WIDTH,HEIGHT = 600,600 
# you can assign multiple variables in a line like this, but 
# I would only recommend it if these variables are related
 
root = tk.Tk()
canvas = tk.Canvas(root, width=WIDTH, height=HEIGHT)

canvas.pack()
root.mainloop()

Pretty simple so far, but now we have a screen.

Cool, a screen, but where's my dude to run around?

To code the survivor, we could make a variable called survivor_pos that holds where our survivor is on the screen, or we could make him into a full blown class. A single variable will work fine, but you will have to hard code some things like his size and colour; what if we want his size and colour to change with future power-ups though? This is only one reason why we will use a class. To program our class, we will need to look back on how a class is made. (Also see this tutorial for more on classes)

class Survivor:
    def __init__(self, pos):
        self.pos = pos

        self.speed = 1 # the speed our person will move across the screen at

        self.rad = 6 # A good starting radius for our circle that represents a survivor
        self.colour = "Red" # If the zombies are green, we should be the opposite
        self.drawn = None

    def draw(self, canvas):
        self.drawn = canvas.draw_oval(self.pos[0]-self.rad, self.pos[1]-self.rad, self.pos[0]+rad, self.pos[1]+rad)

Now we've got a class for our survivor. Let's draw him on the screen!

Remembering back to our tkinter tutorial we can make our update loop and make our program initialize our survivor.

def update():
    root.after(16, update)

player = Survivor([WIDTH/2,HEIGHT/2])
player.draw(canvas)

update()

Now we've got our player on the screen!

Tkinter's keyboard and mouse input are quite simple.
We need to add in the part that tells tkinter that we want to do something when the user presses a key and we need a function/method to handle what happens when a key is pressed.

def kb_handler(event): # I recommend putting this just under your update loop
    pass

root.bind("<Key>", kb_handler) # I recommend putting this statement just above your canvas.pack()

Wait a second, I made my key handler, but my survivor doesn't move!

We have the survivor, we have the canvas, we have the drawing, and we have the key handler, but we haven't written the code to tell Python what to do when we press a key.
The tkinter event has a char attribute that holds the character of the key that has been pressed. Because this char attribute is different for every key you press, you can use it as a key identifier.
Note: an easy way to figure out which key is being pressed, put in a print statement to view what char each key has, then just test run your program and press keys to see what char is held by those keys.

def kb_handler(event):
    print(event.char)

Now we can write an if structure that asks for each key and put in some code to move our survivor.

def kb_handler(event):
    print(event.char)
    if (event.char == "w"):
        survivor.move("u") # move up
    if (event.char == "a"):
        survivor.move("l") # move left
    if (event.char == "s"):
        survivor.move("s") # move down
    if (event.char == "d"):
        survivor.move("r") # move right

With how we wrote that key handler, we need a method in the survivor class that moves our survivor.

Let's go back to the survivor class and make a method called move. This method will track the survivor's movement and move him on the screen.
Note: this method is inside the survivor class underneath the __init__ method and tabbed over just like our draw method.

    def move(self, dir):
        if dir == "u" or dir=="d":
            axis = 1
        else:
            axis = 0
        if dir == "d" or dir == "r":
            pos = 1
        else:
            pos = -1
        self.pos[axis] += pos*self.speed

In this movement example we first analyze if we are moving on the x or y axis, and we assign a temporary variable to hold that information in the form of x = 0 and y = 1, the same way Python lists work. Then we ask if we are moving positively along the axis, and if we are our temporary variable will be positive with the opposite being negative. At the end of the code we use the two temporary variables in the expression that actually changes the position variable.

Why down and right for positive movement?

When tkinter sets up the canvas, the position (0,0), or the origin, is in the top left corner with the positive x axis moving to the right and the positive y axis moving downward.
The possible history behind why this is a fact of drawing on a screen is when the raster scan started to be used in televisions for image display it started in the top-left of the screen. If you would like to learn more about how an image is displayed on a screen in this way, I suggest you read raster graphics.

Why pos*self.speed?

self.speed is the variable that holds how fast we are moving across the screen, and pos is short for positive. This can be summerized to moving on an axis at a rate of self.speed in the direction of the variable pos.
Example: if we want to move from 0 to 1 we would need to use pos as positive and self.speed as 1, but if we wanted to move from 0 to -1 we would need to use pos as negative as self.speed as 1.
These numbers make more sense if you were to imagine it on a two dimensional graph.

My survivor should move now, right?

We have almost everything to make our survivor move, but we haven't told tkinter to move the drawn object. What is happening is the survivor is moving, but tkinter isn't moving the survivor. Fixing this is pretty simple; at the bottom of our move method we'll put in a tkinter canvas function call that tells tkinter to move the object. The function we'll be using looks like this:

canvas.move(object, deltax, deltay)

If you haven't looked at a documentation, this is how the methods are presented in one. They give you something similar to the above and then describe everything that method does. If you haven't seen a documentation, or docs, I recommend you check out the Python docs and the tkinter docs.

The deltax and deltay above stand for the change in x and y. Instead of entering new coordinates, you enter the difference in the coordinates.
For example to move from (0,0) to (-10,1): your deltax would be -10 and your deltay would be 1.

But to move our survivor on the screen, we need to add this code to the move method in the survivor class.

if axis == 0:
    canvas.move(self.drawn,pos*self.speed, 0)
else:
    canvas.move(self.drawn,0,pos*self.speed)

This code will move the survivor on the screen in any of the four directions.


(source)

With a screen and a survivor that runs around on it, we have met our minimum viable product.

In the next parts of this game, we'll be making the zombies and the shooting, then the ammo drops and power ups, and maybe some different attack styles.

This is the code for this part all together and operational. I recommend following the tutorial and writing it yourself, but nothing is stopping you from copying and pasting this code to play what we have so far.

import tkinter as tk

WIDTH, HEIGHT = 600,600

root = tk.Tk()
canvas = tk.Canvas(root, width=WIDTH, height=HEIGHT)

class Survivor:
    def __init__(self, pos):
        self.pos = pos

        self.speed = 10

        self.rad = 6
        self.colour = "Red"
        self.drawn = None

    def move(self, dir):
        if dir == "u" or dir == "d":
            axi = 1
        else:
            axi = 0
        if dir == "d" or dir == "r":
            pos = 1
        else:
            pos = -1
        
        self.pos[axi] += pos*self.speed
        
        if axi == 0:
            canvas.move(self.drawn, pos*self.speed, 0)
        else:
            canvas.move(self.drawn, 0, pos*self.speed)

    def draw(self, canvas):
        self.drawn = canvas.create_oval(self.pos[0]-self.rad,self.pos[1]-self.rad,
                                        self.pos[0]+self.rad,self.pos[1]+self.rad,
                                        fill=self.colour)

def update():
    root.after(16, update)

def kb_handler(event):
    if event.char == "w":
        survivor.move("u")
    elif event.char == "a":
        survivor.move("l")
    elif event.char == "s":
        survivor.move("d")
    elif event.char == "d":
        survivor.move("r")

survivor = Survivor([WIDTH/2,HEIGHT/2])
survivor.draw(canvas)

update()
root.bind("<Key>", kb_handler)
canvas.pack()
root.mainloop()