Book Image

Python Game Programming By Example

By : Alejandro Rodas de Paz, Joseph Howse
Book Image

Python Game Programming By Example

By: Alejandro Rodas de Paz, Joseph Howse

Overview of this book

With a growing interest in learning to program, game development is an appealing topic for getting started with coding. From geometry to basic Artificial Intelligence algorithms, there are plenty of concepts that can be applied in almost every game. Python is a widely used general-purpose, high-level programming language. It provides constructs intended to enable clear programs on both a small and large scale. It is the third most popular language whose grammatical syntax is not predominantly based on C. Python is also very easy to code and is also highly flexible, which is exactly what is required for game development. The user-friendliness of this language allows beginners to code games without too much effort or training. Python also works with very little code and in most cases uses the “use cases” approach, reserving lengthy explicit coding for outliers and exceptions, making game development an achievable feat. Python Game Programming by Example enables readers to develop cool and popular games in Python without having in-depth programming knowledge of Python. The book includes seven hands-on projects developed with several well-known Python packages, as well as a comprehensive explanation about the theory and design of each game. It will teach readers about the techniques of game design and coding of some popular games like Pong and tower defense. Thereafter, it will allow readers to add levels of complexities to make the games more fun and realistic using 3D. At the end of the book, you will have added several GUI libraries like Chimpunk2D, cocos2d, and Tkinter in your tool belt, as well as a handful of recipes and algorithms for developing games with Python.
Table of Contents (9 chapters)
8
Index

Movement and collisions

Now that we have placed all of our game objects, we can define the methods that will be executed in the game loop. This loop runs indefinitely until the game ends, and each iteration updates the position of the ball and checks the collision that occurs.

With the Canvas widget, we can calculate what the items that overlap with the given coordinates are, so for now, we will implement the methods that are responsible for moving the ball and changing its direction.

Let's start with the movement of the ball and the conditions for recreating the bouncing effect when it reaches the canvas borders:

    def update(self):
        coords = self.get_position()
        width = self.canvas.winfo_width()
        if coords[0] <= 0 or coords[2] >= width:
            self.direction[0] *= -1
        if coords[1] <= 0:
            self.direction[1] *= -1
        x = self.direction[0] * self.speed
        y = self.direction[1] * self.speed
        self.move(x, y)

The update method does the following:

  • It gets the current position and the width of the canvas. It stores the values in the coords and width local variables, respectively.
  • If the position collides with the left or right border of the canvas, the horizontal component of the direction vector changes its sign
  • If the position collides with the upper border of the canvas, the vertical component of the direction vector changes its sign
  • We scale the direction vector by the ball's speed
  • The self.move(x, y) moves the ball

For instance, if the ball hits the left border, the coords[0] <= 0 condition evaluates to true, so the x-axis component of the direction changes its sign, as shown in this diagram:

Movement and collisions

If the ball hits the top-right corner, both coords[2] >= width and coords[1] <= 0 evaluate to true. This changes the sign of both the components of the direction vector, like this:

Movement and collisions

The logic of the collision with a brick is a bit more complex, since the direction of the rebound depends on the side where the collision occurs.

We will calculate the x-axis component of the ball's center and check whether it is between the lowermost and uppermost x-axis coordinates of the colliding brick. To translate this into a quick implementation, the following snippet shows the possible changes in the direction vector as per the ball and brick coordinates:

        coords = self.get_position()
        x = (coords[0] + coords[2]) * 0.5
        brick_coords = brick.get_position()
        if x > brick_coords[2]:
            self.direction[0] = 1
        elif x < brick_coords[0]:
            self.direction[0] = -1
        else:
            self.direction[1] *= -1

For instance, this collision causes a horizontal rebound, since the brick is being hit from above, as shown here:

Movement and collisions

On the other hand, a collision from the right-hand side of the brick would be as follows:

Movement and collisions

This is valid when the ball hits the paddle or a single brick. However, the ball can hit two bricks at the same time. In this situation, we cannot execute the previous statements for each brick; if the y-axis direction is multiplied by -1 twice, the value in the next iteration of the game loop will be the same.

We could check whether the collision occurred from above or behind, but the problem with multiple bricks is that the ball may overlap the lateral of one of the bricks and, therefore, change the x-axis direction as well. This happens because of the ball's speed and the rate at which its position is updated.

We will simplify this by assuming that a collision with multiple bricks at the same time occurs only from above or below. That means that it changes the y-axis component of the direction without calculating the position of the colliding bricks:

        if len(game_objects) > 1:
            self.direction[1] *= -1

With these two conditions, we can define the collide method. As we will see later, another method will be responsible for determining the list of colliding bricks, so this method only handles the outcome of a collision with one or more bricks:

    def collide(self, game_objects):
        coords = self.get_position()
        x = (coords[0] + coords[2]) * 0.5
        if len(game_objects) > 1:
            self.direction[1] *= -1
        elif len(game_objects) == 1:
            game_object = game_objects[0]
            coords = game_object.get_position()
            if x > coords[2]:
                self.direction[0] = 1
            elif x < coords[0]:
                self.direction[0] = -1
            else:
                self.direction[1] *= -1

        for game_object in game_objects:
            if isinstance(game_object, Brick):
                game_object.hit()

Note that this method hits every brick instance that is colliding with the ball, so the hit counters are decreased and the bricks are removed if they reach zero hits.