Game

Example usage:

# your_game.py

from levels import Level1  # Level1 is an entity

class MyGame(Game):
    def __init__(self):
        super().__init__()
        self.children = Group()
        self.child_groups = [self.children]
        self.children.add(Level1(...))  

if __name__ == "__main__":
    MyGame().main()

Bases: Entity

Special case of Entity; it is at the very top of the object tree.

Handles much of the pygame boilerplate setup, including:

  • initialising the display
  • setting up and running the main game loop.
  • doing clock.tick() every iteration and enforcing the framerate
  • filling the screen with self.screen_color every iteration
  • updating the EventQueue with new events
  • maintaining the FPS tracker (and drawing it in debug mode)
Source code in robingame/objects/game.py
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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
class Game(Entity):
    """
    Special case of Entity; it is at the very top of the object tree.

    Handles much of the pygame boilerplate setup, including:

    - initialising the display
    - setting up and running the main game loop.
    - doing `clock.tick()` every iteration and enforcing the framerate
    - filling the screen with `self.screen_color` every iteration
    - updating the EventQueue with new events
    - maintaining the FPS tracker (and drawing it in debug mode)
    """

    fps: int = 60
    window_width: int = 500
    window_height: int = 500
    window_caption: str = "Game"
    screen_color = Color("black")
    debug: bool = False  # draw / print debug info?
    running: bool  # is the main game loop running

    def __init__(self):
        """
        Handles a lot of the boilerplate pygame setup.
        Creates the display (`self.window`).
        """
        super().__init__()
        pygame.init()
        self.fps_tracker = FpsTracker()
        self.window = pygame.display.set_mode((self.window_width, self.window_height))
        pygame.display.set_caption(self.window_caption)
        self.clock = pygame.time.Clock()

    def main(self):
        """
        Contains the main game loop.
        Calls `self._update()` and `self._draw()` on every iteration of the game loop.
        """
        self.running = True
        while self.running:
            self._update()
            self._draw(self.window, debug=self.debug)
        pygame.quit()
        sys.exit()

    def read_inputs(self):
        """
        Called by `self._update()`, before `super().update()` updates the children.

        Any code that polls external joysticks/controllers should go here.
        """

        # I've put this in a separate method because I don't like the idea of putting the inputs
        # in the same list as other child groups. The order might get ruined, or a subclass might
        # overwrite the list. It's crucial that the inputs are read before updating.
        EventQueue.update()
        for event in EventQueue.events:
            if event.type == pygame.QUIT:
                pygame.quit()
                sys.exit()
            if event.type == pygame.KEYDOWN and event.key == pygame.K_F1:
                self.debug = not self.debug

    def print_debug_info(self):
        """
        Override this if you want to print any debug info.
        """
        pass

    def _update(self):
        """
        1. read inputs
        2. update
        """
        self.read_inputs()
        if self.debug:
            self.print_debug_info()
        self.update()
        self.fps_tracker.update()
        if self.fps:
            self.clock.tick(self.fps)

    def _draw(self, surface: Surface, debug: bool = False):
        surface.fill(self.screen_color)  # clear the screen
        self.draw(surface, debug)
        self.fps_tracker.draw(surface, debug)
        pygame.display.update()  # print to screen

__init__()

Handles a lot of the boilerplate pygame setup. Creates the display (self.window).

robingame/objects/game.py
34
35
36
37
38
39
40
41
42
43
44
def __init__(self):
    """
    Handles a lot of the boilerplate pygame setup.
    Creates the display (`self.window`).
    """
    super().__init__()
    pygame.init()
    self.fps_tracker = FpsTracker()
    self.window = pygame.display.set_mode((self.window_width, self.window_height))
    pygame.display.set_caption(self.window_caption)
    self.clock = pygame.time.Clock()

main()

Contains the main game loop. Calls self._update() and self._draw() on every iteration of the game loop.

robingame/objects/game.py
46
47
48
49
50
51
52
53
54
55
56
def main(self):
    """
    Contains the main game loop.
    Calls `self._update()` and `self._draw()` on every iteration of the game loop.
    """
    self.running = True
    while self.running:
        self._update()
        self._draw(self.window, debug=self.debug)
    pygame.quit()
    sys.exit()

print_debug_info()

Override this if you want to print any debug info.

robingame/objects/game.py
76
77
78
79
80
def print_debug_info(self):
    """
    Override this if you want to print any debug info.
    """
    pass

read_inputs()

Called by self._update(), before super().update() updates the children.

Any code that polls external joysticks/controllers should go here.

robingame/objects/game.py
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
def read_inputs(self):
    """
    Called by `self._update()`, before `super().update()` updates the children.

    Any code that polls external joysticks/controllers should go here.
    """

    # I've put this in a separate method because I don't like the idea of putting the inputs
    # in the same list as other child groups. The order might get ruined, or a subclass might
    # overwrite the list. It's crucial that the inputs are read before updating.
    EventQueue.update()
    for event in EventQueue.events:
        if event.type == pygame.QUIT:
            pygame.quit()
            sys.exit()
        if event.type == pygame.KEYDOWN and event.key == pygame.K_F1:
            self.debug = not self.debug