Basics of Gaming while making (not perfect) Breakout!

Show me the game!

Yes the graphics look bad, but you can make them better!

About me: Dan M.

  • Senior Software Engineer at the Organic Box
  • My past: Infrastructure Engineering jobs and Network Engineering jobs.
  • Went to school for Electrical Engineering (I don't do that anymore)
  • I like kitties, badminton and development.

Feel Free to reach out!

  • You can reach me on linkedin!
    https://www.linkedin.com/in/daniel-mouris-baa4b972/
  • You can reach me on twitter!
    https://twitter.com/dgmouris
    (although I mainly tweet about edmontonpy meetups)
  • You can email me at dgmouris@gmail.com
  • Slack as well (dgmouris)

Disclaimer:

You can ask questions.

I haven't made many games in my life (You'll see, I'm not an expert)

 

Don't be afraid to learn!

Why teal slides?

It's a curve ball.

What am I going to talk about?

  • How to dunk a basketball

  • Basics of Game Development

  • What you need to know for this talk

  • Building Breakout!

    • Pygame Basics and Boiler Plate needed for our game
    • Drawing Shapes for our game
    • Handling Events
    • Collision Detection
    • Gameplay
    • Programming the Game Menu (simplistically)
    • End Game

How to dunk a basketball.

  1. Hold a Basketball

  2. Jump high

  3. Profit!

The Basics of game development

What are the pieces to a game?

  • Main loop
  • Game Physics
  • Artificial Intelligence (not going to cover in depth)
  • Playing audio (not going to cover in depth)
  • Lives Score and Levels (not going to cover in depth)
  • Storage (not going to cover in depth)
  • Networking (not going to cover in depth)

Main Loop

What does the main loop do?

  • Handling events
  • Updating State
  • Drawing stuff

What is the main loop?

The main loop is basically the piece of the program that refreshes the screen.

Also is your framerate is normally either 30-60 frames per second.

Game Physics

You'll either have to write your own (like we will today)

Normally comes with a game engine read: Unity, UDK ...

Artificial Intelligence

Is how will enemies in your game challenge the player.

 

We're not going to touch on this today.

Playing Audio

Sound Effects

Music

 

We're not going to touch on this today.

Tracking Lives and Scores

Basically just keeping score in your game.

 

We're not going to touch on this today.

Storage

Just storing progress either locally or on a server of sorts.

 

We're not going to touch on this today.

Networking

No, Not people networking (Nope never)

This is super complicated and there is no way that I can talk about any of this.

Examples: Games that use xbox live.

 

We're not going to touch on this today.

What you're going to need to know.

nested functions (and their scope)

lambda functions

Basic Class Inheritance

Basic OOP

 

Let's Build Breakout!

How are we building Breakout?

  • Pygame Basics and Boiler Plate needed for our game
  • Drawing Shapes and Text for our game
  • Handling Events
  • Gameplay
  • Collision Detection
  • Programming the Game Menu (simplistically)
  • End Game

Pygame Basics and boiler plate code

Stuff specific to pygame and our project to make it work.

File Structure

  • The ball.py, brick.py and paddle.py are going to contain their respective parts.
  • config and colors just look at the repo, I stole this but don't tell anyone.
  • text_object.py will be the object that displays text on the screen.
  • game.py will contain the Game Object.
├── Pipfile
├── Pipfile.lock
├── README.md
├── ball.py
├── breakout.py
├── brick.py
├── button.py
├── colors.py
├── config.py
├── game.py
├── game_object.py
├── images
│   └── background.jpg
├── paddle.py
├── sound_effects
│   ├── brick_hit.wav
│   ├── effect_done.wav
│   ├── level_complete.wav
│   └── paddle_hit.wav
└── text_object.py

Notes on the structure

  • The Game Object is a visual object that knows how to render itself.
  • Is going to be a base for other objects.
  • It has an update method which updates the objects position (based on speed)
  • As well the draw() method.
from pygame.rect import Rect


class GameObject:
    def __init__(self, x, y, w, h, speed=(0,0)):
        self.bounds = Rect(x, y, w, h)
        self.speed = speed

    def draw(self, surface):
        pass

    def move(self, dx, dy):
        self.bounds = self.bounds.move(dx,
                                       dy)

    def update(self):
        if self.speed == [0, 0]:
            return
        self.move(*self.speed)

    @property
    def left(self):
        return self.bounds.left

    @property
    def right(self):
        return self.bounds.right

... it goes on.

    @property
    def top(self):
        return self.bounds.top

    @property
    def bottom(self):
        return self.bounds.bottom

    @property
    def width(self):
        return self.bounds.width

    @property
    def height(self):
        return self.bounds.height

    @property
    def center(self):
        return self.bounds.center

    @property
    def centerx(self):
        return self.bounds.centerx

    @property
    def centery(self):
        return self.bounds.centery

The Game Object

  • The game class is the core.
  • It runs the main loop of the game.
  • __init__  method initializes the game.
  • update and draw objects loop over objects either draw or update in the main loop.
import pygame
import sys

from collections import defaultdict


class Game:
    def __init__(self,
                 caption,
                 width,
                 height,
                 back_image_filename,
                 frame_rate):
        self.background_image = \
            pygame.image.load(back_image_filename)
        self.frame_rate = frame_rate
        self.game_over = False
        self.objects = []
        pygame.mixer.pre_init(44100, 16, 2, 4096)
        pygame.init()
        pygame.font.init()
        self.surface = pygame.display.set_mode((width, height))
        pygame.display.set_caption(caption)
        self.clock = pygame.time.Clock()
        self.keydown_handlers = defaultdict(list)
        self.keyup_handlers = defaultdict(list)
        self.mouse_handlers = []

    def update(self):
        for o in self.objects:
            o.update()

    def draw(self):
        for o in self.objects:
            o.draw(self.surface)

The Game Class

  • handle_events method basically just takes in user input
  • run method is the main loop discussed earlier.
import pygame
import sys

from collections import defaultdict


class Game:
    ... (on other page)
    
    def handle_events(self):
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                sys.exit()
            elif event.type == pygame.KEYDOWN:
                for handler in self.keydown_handlers[event.key]:
                    handler(event.key)
            elif event.type == pygame.KEYUP:
                for handler in self.keydown_handlers[event.key]:
                    handler(event.key)
            elif event.type in (pygame.MOUSEBUTTONDOWN,
                                pygame.MOUSEBUTTONUP,
                                pygame.MOUSEMOTION):
                for handler in self.mouse_handlers:
                    handler(event.type, event.pos)
    
    def run(self):
        while not self.game_over:
            self.surface.blit(self.background_image, (0, 0))
    
            self.handle_events()
            self.update()
            self.draw()
    
            pygame.display.update()
            self.clock.tick(self.frame_rate)

The Game Class, cont'd

Drawing Shapes and Text

Drawing stuff on the screen where we want it

  • Shows text on the screen!
import pygame

class TextObject:
    def __init__(self, x, y, text_func,
                 color, font_name, font_size):
        self.pos = (x, y)
        self.text_func = text_func
        self.color = color
        self.font = pygame.font.SysFont(
            font_name,
            font_size
        )
        self.bounds = self.get_surface(
            text_func()
        )

    def draw(self, surface, centralized=False):
        text_surface, self.bounds = \
            self.get_surface(self.text_func())
        if centralized:
            pos = (
                self.pos[0] - self.bounds.width // 2,
                self.pos[1]
            )
        else:
            pos = self.pos
        surface.blit(text_surface, pos)

    def get_surface(self, text):
        text_surface = self.font.render(
            text, False, self.color)
        return text_surface, text_surface.get_rect()

    def update(self):
        pass

The Text Object

What Kind of Shapes can I draw?

  • rect 
  • polygon
  • circle
  • ellipse
  • arc
  • line
  • lines
  • anti-aliased line 
  • anti-aliased lines
  • We all know bricks are circles ovals rectangles
import pygame

from game_object import GameObject


class Brick(GameObject):
    def __init__(self, x, y, w, h,
                 color, special_effect=None):
        GameObject.__init__(self, x, y, w, h)
        self.color = color
        self.special_effect = special_effect

    def draw(self, surface):
        pygame.draw.rect(surface,
                    self.color, self.bounds)

Drawing the Brick

  • basketballs balls are very similar looking to the brick class.
  • inherits from gameObject
class Ball(GameObject):
    def __init__(self, x, y, r, color, speed):
        GameObject.__init__(self,
                            x - r,
                            y - r,
                            r * 2,
                            r * 2,
                            speed)
        self.radius = r
        self.diameter = r * 2
        self.color = color

    def draw(self, surface):
        pygame.draw.circle(surface,
                           self.color,
                           self.center,
                           self.radius)

    def update(self):
        super().update()

Balls!

  • just another rectangle, although this moves left and right.
import pygame
 
import config
from game_object import GameObject
 
 
class Paddle(GameObject):
    def __init__(self, x, y, w, h, color, offset):
        GameObject.__init__(self, x, y, w, h)
        self.color = color
        self.offset = offset
        self.moving_left = False
        self.moving_right = False
 
    def draw(self, surface):
        pygame.draw.rect(surface, self.color, self.bounds)

Let's Draw the paddle

Handling Events

Mouse Clicks or Key Board presses

What are the Events that we're going to handle?

  • Mouse Events
  • Key Press Events
  • Timed Events
  • when a key is pressed the handle() method is called.
import pygame

import config
from game_object import GameObject


class Paddle(GameObject):
    def __init__(self, x, y, w, h, color, offset):
        GameObject.__init__(self, x, y, w, h)
        self.color = color
        self.offset = offset
        self.moving_left = False
        self.moving_right = False

    def update(self):
        if self.moving_left:
            dx = -(min(self.offset, self.left))
        elif self.moving_right:
            dx = min(self.offset,
                     config.screen_width - self.right)
        else:
            return
        self.move(dx, 0)

    def draw(self, surface):
        pygame.draw.rect(surface, self.color, self.bounds)

    def handle(self, key):
        if key == pygame.K_LEFT:
            self.moving_left = not self.moving_left
        else:
            self.moving_right = not self.moving_right

Key Events

  • This basically just handles any mouse movement and clicks.
class Button(GameObject):
    def __init__(self, x, y, w, h, text, on_click=lambda x: None, padding=0):
        super().__init__(x, y, w, h)
        self.state = 'normal'
        self.on_click = on_click

        self.text = TextObject(x + padding, y + padding, lambda: text,
                               config.button_text_color,
                               config.font_name,
                               config.font_size)    
    ...

    def handle_mouse_event(self, type, pos):
        if type == pygame.MOUSEMOTION:
            self.handle_mouse_move(pos)
        elif type == pygame.MOUSEBUTTONDOWN:
            self.handle_mouse_down(pos)
        elif type == pygame.MOUSEBUTTONUP:
            self.handle_mouse_up(pos)

    def handle_mouse_move(self, pos):
        if self.bounds.collidepoint(pos):
            if self.state != 'pressed':
                self.state = 'hover'
        else:
            self.state = 'normal'

    def handle_mouse_down(self, pos):
        if self.bounds.collidepoint(pos):
            self.state = 'pressed'

    def handle_mouse_up(self, pos):
        if self.state == 'pressed':
            self.on_click(self)
            self.state = 'hover'

Mouse Events!

  • Timer Events are not specified in the main loop.
  • When would you use them?
    • Freezing the game for example.
class Breakout(Game):
    ...

    def show_message(self,
                     text,
                     color=colors.WHITE,
                     font_name='Arial',
                     font_size=20,
                     centralized=False):
        message = TextObject(
            config.screen_width // 2,
            config.screen_height // 2,
            lambda: text, color,
            font_name, font_size
        )
        self.draw()
        message.draw(self.surface, centralized)
        pygame.display.update()
        time.sleep(config.message_duration)

Timer Events

Game Play

Programming the rules and moving objects based on the game state

  • Stops the paddle from moving off the screen!
import pygame

import config
from game_object import GameObject


class Paddle(GameObject):
    def __init__(self, x, y, w, h, color, offset):
        GameObject.__init__(self, x, y, w, h)
        self.color = color
        self.offset = offset
        self.moving_left = False
        self.moving_right = False

    def update(self):
        if self.moving_left:
            dx = -(min(self.offset, self.left))
        elif self.moving_right:
            dx = min(self.offset,
                     config.screen_width - self.right)
        else:
            return
        self.move(dx, 0)

    def draw(self, surface):
        pygame.draw.rect(surface, self.color, self.bounds)

    def handle(self, key):
        if key == pygame.K_LEFT:
            self.moving_left = not self.moving_left
        else:
            self.moving_right = not self.moving_right

Paddle Rules/Play

  • In the break out class (the main game class) we're just going to make a method to create balls.
  • Ball movement is already taken care of 
class Breakout(Game):
    ...

    def create_ball(self):
        speed = (random.randint(-2, 2),
                 config.ball_speed)
        self.ball = Ball(config.screen_width // 2,
                         config.screen_height // 2,
                         config.ball_radius,
                         config.ball_color,
                         speed)
        self.objects.append(self.ball)

Creating the ball to start

Collision Detetection

Detecting when objects are colliding or "hitting" each other

  • This gives info on whether or not the ball hit something.
  • If it hit something it's going to respond with:
    • 'left'
    • 'right'
    • 'top'
    • 'bottom'
  • Doesn't matter if it's the paddle or a brick.
class Breakout(Game):
    ...

    def handle_ball_collisions(self):
        def intersect(obj, ball):
            edges = dict(
                left=Rect(obj.left, obj.top,
                         1, obj.height),
                right=Rect(obj.right, obj.top,
                           1, obj.height),
                top=Rect(obj.left, obj.top,
                         obj.width, 1),
                bottom=Rect(obj.left, obj.bottom,
                            obj.width, 1))
            collisions = set(
              edge for edge, rect in edges.items() if
              ball.bounds.colliderect(rect))
 
           if not collisions:
                return None
            if len(collisions) == 1:
                return list(collisions)[0]
            if 'top' in collisions:
                if ball.centery >= obj.top:
                    return 'top'
                if ball.centerx < obj.left:
                    return 'left'
                else:
                    return 'right'
            if 'bottom' in collisions:
                if ball.centery >= obj.bottom:
                    return 'bottom'
                if ball.centerx < obj.left:
                    return 'left'
                else:
                    return 'right'

Intersection

  • Ball hitting the paddle will have the same horizontal speed, but vertical speed will be inversed.
  • If the paddle is moving then the horizontal speed is increased.
class Breakout(Game):
    ...

    def handle_ball_collisions(self):
        ...

        # Hit paddle
        s = self.ball.speed
        edge = intersect(self.paddle, self.ball)
        if edge is not None:
            self.sound_effects['paddle_hit'].play()
        if edge == 'top':
            speed_x = s[0]
            speed_y = -s[1]
            if self.paddle.moving_left:
                speed_x -= 1
            elif self.paddle.moving_left:
                speed_x += 1
            self.ball.speed = speed_x, speed_y
        elif edge in ('left', 'right'):
            self.ball.speed = (-s[0], s[1])

Paddle Collisions

  • If the top of the ball is lower than the floor then create a new ball, unless there's no lives.
class Breakout(Game):
    ...

    def handle_ball_collisions(self):
        ...

        # Hit floor
        if self.ball.top > config.screen_height:
            self.lives -= 1
            if self.lives == 0:
                self.game_over = True
            else:
                self.create_ball()

Floor Collision

  • If it hits the ceiling it just reverses the vertical speed.
  • If it hits the right or left wall it just reverses the horizontal speed.
  • Note: s is speed here.
class Breakout(Game):
    ...

    def handle_ball_collisions(self):
        ...

        # Hit ceiling
        if self.ball.top < 0:
            self.ball.speed = (s[0], -s[1])

        # Hit wall
        if (self.ball.left < 0 or
            self.ball.right > config.screen_width):
            
            self.ball.speed = (-s[0], s[1])

Ceiling & Wall
Collision

  • if there is an intersection of the bricks
  • we remove the bricks for the bricks list for the game and the objects of the game.
  • We also add to the score.
  • The ball continues to move in the opposite way of the collision.
  • Note: s is speed here.
class Breakout(Game):
    ...

    def handle_ball_collisions(self):
        ...

        # Hit brick
        for brick in self.bricks:
            edge = intersect(brick, self.ball)
            if not edge:
                continue

            self.bricks.remove(brick)
            self.objects.remove(brick)
            self.score += self.points_per_brick

            if edge in ('top', 'bottom'):
                self.ball.speed = (s[0], -s[1])
            else:
                self.ball.speed = (-s[0], s[1])

Brick Collisions

The (very simple) Game Menu

Basically two buttons "play" and "quit"

The (very simple) Game Menu

  • As you can see in the for loop, we create two buttons.
  • Then we append then to the objects list, buttons list and then attach the handlers to them
  • The on_play removes the buttons from the screen.
class Breakout(Game):
    ...

    def create_menu(self):
        def on_play(button):
            for b in self.menu_buttons:
                self.objects.remove(b)

            self.is_game_running = True
            self.start_level = True

        def on_quit(button):
            self.game_over = True
            self.is_game_running = False
            self.game_over = True

        for i, (text, click_handler) in
            enumerate((
                    ('PLAY', on_play),
                    ('QUIT', on_quit))
                ):
            b = Button(config.menu_offset_x,
                       (config.menu_offset_y +
                         (config.menu_button_h + 5) * i),
                       config.menu_button_w,
                       config.menu_button_h,
                       text,
                       click_handler,
                       padding=5)
            self.objects.append(b)
            self.menu_buttons.append(b)
            self.mouse_handlers.append(
                b.handle_mouse_event
            )

Creating the Menu Buttons

Ending the Game

Just the end, and showing the code of the main loop.

  • Game over happens under any of these conditions:
    • The player clicked the QUIT button from the menu.
    • The player loses their last life.
    • The player cleared all the bricks.
class Game:
    ...
    def run(self):
        while not self.game_over:
            self.surface.blit(self.background_image, (0, 0))

            self.handle_events()
            self.update()
            self.draw()

            pygame.display.update()
            self.clock.tick(self.frame_rate)

Game is over when main loop ends.

Conclusion

Games aren't that scary to program.
Start with an easy game (not AAA).
If I can do it, you can too!

Summary

I hoped you enjoyed this long talk.

Here's my kitties!

Marshmallow

Ghost

Gambit

Questions?

Made with Slides.com