18. Using the Window Class

../../_images/window.svg

We can use a class to represent our program. The Arcade library has a built-in class that represents a window on the screen. We can create our own child class and override functions to handle:

  • Start-up and initialization

  • Drawing the items on our screen

  • Animating/Updating the positions of items on our screen

  • Responding to the keyboard

  • Responding to the mouse

One of the best ways of learning to program is to look at sample code. This chapter has several examples designed to learn how to:

  • Open a window using an object-oriented approach

  • Animating objects

  • Moving objects with the mouse

  • Moving objects with the keyboard

  • Moving objects with the joystick

18.1. Creating a Window with a Class

Up to now, we have used a function called open_window to open a window. Here’s the code:

open_window_with_function.py
 1import arcade
 2
 3SCREEN_WIDTH = 640
 4SCREEN_HEIGHT = 480
 5
 6
 7def main():
 8    arcade.open_window(SCREEN_WIDTH, SCREEN_HEIGHT, "Drawing Example")
 9
10    arcade.run()
11
12
13main()

We can also create an instance of a class called Window to open a window. The code is rather straight-forward:

open_window_with_object.py
 1import arcade
 2
 3SCREEN_WIDTH = 640
 4SCREEN_HEIGHT = 480
 5
 6
 7def main():
 8    window = arcade.Window(SCREEN_WIDTH, SCREEN_HEIGHT, "Drawing Example")
 9
10    arcade.run()
11
12
13main()

Function calls, and calls to create an instance of an object look very similar. The tell-tale clue that we are creating an instance of an object in the second example is the fact that Window is capitalized.

18.2. Extending the Window Class

Arcade’s Window class has a lot of built-in methods that are automatically called when needed. Methods for drawing, for responding to the keyboard, the mouse, and more. You can see all the methods by looking at the Window Class Documentation. But by default, these methods don’t do anything. We need to change that.

As we learned from the prior chapter, we can extend the functionality of a class by creating a child class. Therefore, we can extend the Window class by creating a child class of it. I’m going to call my child class MyGame.

extending_window_class.py
 1import arcade
 2
 3
 4class MyGame(arcade.Window):
 5
 6    def __init__(self, width, height, title):
 7
 8        # Call the parent class's init function
 9        super().__init__(width, height, title)
10
11
12def main():
13    window = MyGame(640, 480, "Drawing Example")
14
15    arcade.run()
16
17
18main()

18.3. Drawing with the Window Class

To draw with the Window class, we need to create our own method called on_draw. This will override the default on_draw method built into the Window class. We will put our drawing code in there.

The on_draw method gets called about 60 times per second. We’ll use this fact when we do animation.

We also need to set the background color. Since we only need to do this once, we will do that in the __init__ method. No sense setting the background 60 times per second when it isn’t changing.

drawing.py
 1import arcade
 2
 3
 4class MyGame(arcade.Window):
 5
 6    def __init__(self, width, height, title):
 7
 8        # Call the parent class's init function
 9        super().__init__(width, height, title)
10
11        # Set the background color
12        arcade.set_background_color(arcade.color.ASH_GREY)
13
14    def on_draw(self):
15        """ Called whenever we need to draw the window. """
16        arcade.start_render()
17
18        arcade.draw_circle_filled(50, 50, 15, arcade.color.AUBURN)
19
20
21def main():
22    window = MyGame(640, 480, "Drawing Example")
23
24    arcade.run()
25
26
27main()

The result of this program just looks like:

../../_images/draw_example.png

18.4. Animating

By overriding the update method, we can update our ball position and animate our scene:

simple_animation.py
 1import arcade
 2
 3
 4class MyGame(arcade.Window):
 5
 6    def __init__(self, width, height, title):
 7
 8        # Call the parent class's init function
 9        super().__init__(width, height, title)
10
11        # Set the background color
12        arcade.set_background_color(arcade.color.ASH_GREY)
13
14        # Attributes to store where our ball is
15        self.ball_x = 50
16        self.ball_y = 50
17
18    def on_draw(self):
19        """ Called whenever we need to draw the window. """
20        arcade.start_render()
21
22        arcade.draw_circle_filled(self.ball_x, self.ball_y, 15, arcade.color.AUBURN)
23
24    def update(self, delta_time):
25        """ Called to update our objects. Happens approximately 60 times per second."""
26        self.ball_x += 1
27        self.ball_y += 1
28
29
30def main():
31    window = MyGame(640, 480, "Drawing Example")
32
33    arcade.run()
34
35
36main()

18.4.1. Encapsulating Our Animation Object

It doesn’t take much imagination to realize that adding more parameters to the ball, getting it to bounce, or even having several balls on the screen would make our MyApplication class very complex.

If only there was a way to encapsulate all that “ball” stuff together. Wait! There is! Using classes!

Here is a more complex example, but all the logic for the ball has been moved into a new Ball class.

ball_class_example.py
 1import arcade
 2
 3SCREEN_WIDTH = 640
 4SCREEN_HEIGHT = 480
 5
 6
 7class Ball:
 8    """ This class manages a ball bouncing on the screen. """
 9
10    def __init__(self, position_x, position_y, change_x, change_y, radius, color):
11        """ Constructor. """
12
13        # Take the parameters of the init function above, and create instance variables out of them.
14        self.position_x = position_x
15        self.position_y = position_y
16        self.change_x = change_x
17        self.change_y = change_y
18        self.radius = radius
19        self.color = color
20
21    def draw(self):
22        """ Draw the balls with the instance variables we have. """
23        arcade.draw_circle_filled(self.position_x, self.position_y, self.radius, self.color)
24
25    def update(self):
26        """ Code to control the ball's movement. """
27
28        # Move the ball
29        self.position_y += self.change_y
30        self.position_x += self.change_x
31
32        # See if the ball hit the edge of the screen. If so, change direction
33        if self.position_x < self.radius:
34            self.change_x *= -1
35
36        if self.position_x > SCREEN_WIDTH - self.radius:
37            self.change_x *= -1
38
39        if self.position_y < self.radius:
40            self.change_y *= -1
41
42        if self.position_y > SCREEN_HEIGHT - self.radius:
43            self.change_y *= -1
44
45
46class MyGame(arcade.Window):
47    """ My window class. """
48
49    def __init__(self, width, height, title):
50        """ Constructor. """
51
52        # Call the parent class's init function
53        super().__init__(width, height, title)
54        arcade.set_background_color(arcade.color.ASH_GREY)
55
56        # Create our ball
57        self.ball = Ball(50, 50, 3, 3, 15, arcade.color.AUBURN)
58
59    def on_draw(self):
60        """ Called whenever we need to draw the window. """
61        arcade.start_render()
62        self.ball.draw()
63
64    def update(self, delta_time):
65        """ Called to update our objects. Happens approximately 60 times per second."""
66        self.ball.update()
67
68
69def main():
70    window = MyGame(640, 480, "Drawing Example")
71
72    arcade.run()
73
74
75main()

Here it is in action:

../../_images/ball_class_example.gif

18.4.2. Animating a List

Wouldn’t it be nice to animate multiple items? How do we track multiple items? With a list! This takes our previous example and animates three balls at once.

ball_list_example.py
 1import arcade
 2
 3SCREEN_WIDTH = 640
 4SCREEN_HEIGHT = 480
 5
 6
 7class Ball:
 8    """ This class manages a ball bouncing on the screen. """
 9
10    def __init__(self, position_x, position_y, change_x, change_y, radius, color):
11        """ Constructor. """
12
13        # Take the parameters of the init function above, and create instance variables out of them.
14        self.position_x = position_x
15        self.position_y = position_y
16        self.change_x = change_x
17        self.change_y = change_y
18        self.radius = radius
19        self.color = color
20
21    def draw(self):
22        """ Draw the balls with the instance variables we have. """
23        arcade.draw_circle_filled(self.position_x, self.position_y, self.radius, self.color)
24
25    def update(self):
26        """ Code to control the ball's movement. """
27
28        # Move the ball
29        self.position_y += self.change_y
30        self.position_x += self.change_x
31
32        # See if the ball hit the edge of the screen. If so, change direction
33        if self.position_x < self.radius:
34            self.change_x *= -1
35
36        if self.position_x > SCREEN_WIDTH - self.radius:
37            self.change_x *= -1
38
39        if self.position_y < self.radius:
40            self.change_y *= -1
41
42        if self.position_y > SCREEN_HEIGHT - self.radius:
43            self.change_y *= -1
44
45
46class MyGame(arcade.Window):
47
48    def __init__(self, width, height, title):
49
50        # Call the parent class's init function
51        super().__init__(width, height, title)
52        arcade.set_background_color(arcade.color.ASH_GREY)
53
54        # Create a list for the balls
55        self.ball_list = []
56
57        # Add three balls to the list
58        ball = Ball(50, 50, 3, 3, 15, arcade.color.AUBURN)
59        self.ball_list.append(ball)
60
61        ball = Ball(100, 150, 2, 3, 15, arcade.color.PURPLE_MOUNTAIN_MAJESTY)
62        self.ball_list.append(ball)
63
64        ball = Ball(150, 250, -3, -1, 15, arcade.color.FOREST_GREEN)
65        self.ball_list.append(ball)
66
67    def on_draw(self):
68        """ Called whenever we need to draw the window. """
69        arcade.start_render()
70
71        # Use a "for" loop to pull each ball from the list, then call the draw
72        # method on that ball.
73        for ball in self.ball_list:
74            ball.draw()
75
76    def update(self, delta_time):
77        """ Called to update our objects. Happens approximately 60 times per second."""
78
79        # Use a "for" loop to pull each ball from the list, then call the update
80        # method on that ball.
81        for ball in self.ball_list:
82            ball.update()
83
84
85def main():
86    window = MyGame(640, 480, "Drawing Example")
87
88    arcade.run()
89
90
91main()
../../_images/ball_list_example.gif