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 arcade.open_window() to open a window. Here’s the code:

open_window_with_function.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import arcade

SCREEN_WIDTH = 640
SCREEN_HEIGHT = 480


def main():
    arcade.open_window(SCREEN_WIDTH, SCREEN_HEIGHT, "Drawing Example")

    arcade.run()


main()

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

open_window_with_object.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import arcade

SCREEN_WIDTH = 640
SCREEN_HEIGHT = 480


def main():
    window = arcade.Window(SCREEN_WIDTH, SCREEN_HEIGHT, "Drawing Example")

    arcade.run()


main()

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
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
import arcade


class MyGame(arcade.Window):

    def __init__(self, width, height, title):

        # Call the parent class's init function
        super().__init__(width, height, title)


def main():
    window = MyGame(640, 480, "Drawing Example")

    arcade.run()


main()

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
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import arcade


class MyGame(arcade.Window):

    def __init__(self, width, height, title):

        # Call the parent class's init function
        super().__init__(width, height, title)

        # Set the background color
        arcade.set_background_color(arcade.color.ASH_GREY)

    def on_draw(self):
        """ Called whenever we need to draw the window. """
        arcade.start_render()

        arcade.draw_circle_filled(50, 50, 15, arcade.color.AUBURN)


def main():
    window = MyGame(640, 480, "Drawing Example")

    arcade.run()


main()

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
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
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
import arcade


class MyGame(arcade.Window):

    def __init__(self, width, height, title):

        # Call the parent class's init function
        super().__init__(width, height, title)

        # Set the background color
        arcade.set_background_color(arcade.color.ASH_GREY)

        # Attributes to store where our ball is
        self.ball_x = 50
        self.ball_y = 50

    def on_draw(self):
        """ Called whenever we need to draw the window. """
        arcade.start_render()

        arcade.draw_circle_filled(self.ball_x, self.ball_y, 15, arcade.color.AUBURN)

    def update(self, delta_time):
        """ Called to update our objects. Happens approximately 60 times per second."""
        self.ball_x += 1
        self.ball_y += 1


def main():
    window = MyGame(640, 480, "Drawing Example")

    arcade.run()


main()

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
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
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
import arcade

SCREEN_WIDTH = 640
SCREEN_HEIGHT = 480


class Ball:
    """ This class manages a ball bouncing on the screen. """

    def __init__(self, position_x, position_y, change_x, change_y, radius, color):
        """ Constructor. """

        # Take the parameters of the init function above, and create instance variables out of them.
        self.position_x = position_x
        self.position_y = position_y
        self.change_x = change_x
        self.change_y = change_y
        self.radius = radius
        self.color = color

    def draw(self):
        """ Draw the balls with the instance variables we have. """
        arcade.draw_circle_filled(self.position_x, self.position_y, self.radius, self.color)

    def update(self):
        """ Code to control the ball's movement. """

        # Move the ball
        self.position_y += self.change_y
        self.position_x += self.change_x

        # See if the ball hit the edge of the screen. If so, change direction
        if self.position_x < self.radius:
            self.change_x *= -1

        if self.position_x > SCREEN_WIDTH - self.radius:
            self.change_x *= -1

        if self.position_y < self.radius:
            self.change_y *= -1

        if self.position_y > SCREEN_HEIGHT - self.radius:
            self.change_y *= -1


class MyGame(arcade.Window):
    """ My window class. """

    def __init__(self, width, height, title):
        """ Constructor. """

        # Call the parent class's init function
        super().__init__(width, height, title)
        arcade.set_background_color(arcade.color.ASH_GREY)

        # Create our ball
        self.ball = Ball(50, 50, 3, 3, 15, arcade.color.AUBURN)

    def on_draw(self):
        """ Called whenever we need to draw the window. """
        arcade.start_render()
        self.ball.draw()

    def update(self, delta_time):
        """ Called to update our objects. Happens approximately 60 times per second."""
        self.ball.update()


def main():
    window = MyGame(SCREEN_WIDTH, SCREEN_HEIGHT, "Drawing Example")

    arcade.run()


main()

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
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
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
import arcade

SCREEN_WIDTH = 640
SCREEN_HEIGHT = 480


class Ball:
    """ This class manages a ball bouncing on the screen. """

    def __init__(self, position_x, position_y, change_x, change_y, radius, color):
        """ Constructor. """

        # Take the parameters of the init function above, and create instance variables out of them.
        self.position_x = position_x
        self.position_y = position_y
        self.change_x = change_x
        self.change_y = change_y
        self.radius = radius
        self.color = color

    def draw(self):
        """ Draw the balls with the instance variables we have. """
        arcade.draw_circle_filled(self.position_x, self.position_y, self.radius, self.color)

    def on_update(self):
        """ Code to control the ball's movement. """

        # Move the ball
        self.position_y += self.change_y
        self.position_x += self.change_x

        # See if the ball hit the edge of the screen. If so, change direction
        if self.position_x < self.radius:
            self.change_x *= -1

        if self.position_x > SCREEN_WIDTH - self.radius:
            self.change_x *= -1

        if self.position_y < self.radius:
            self.change_y *= -1

        if self.position_y > SCREEN_HEIGHT - self.radius:
            self.change_y *= -1


class MyGame(arcade.Window):

    def __init__(self, width, height, title):

        # Call the parent class's init function
        super().__init__(width, height, title)
        arcade.set_background_color(arcade.color.ASH_GREY)

        # Create a list for the balls
        self.ball_list = []

        # Add three balls to the list
        ball = Ball(50, 50, 3, 3, 15, arcade.color.AUBURN)
        self.ball_list.append(ball)

        ball = Ball(100, 150, 2, 3, 15, arcade.color.PURPLE_MOUNTAIN_MAJESTY)
        self.ball_list.append(ball)

        ball = Ball(150, 250, -3, -1, 15, arcade.color.FOREST_GREEN)
        self.ball_list.append(ball)

    def on_draw(self):
        """ Called whenever we need to draw the window. """
        arcade.start_render()

        # Use a "for" loop to pull each ball from the list, then call the draw
        # method on that ball.
        for ball in self.ball_list:
            ball.draw()

    def on_update(self, delta_time):
        """ Called to update our objects. Happens approximately 60 times per second."""

        # Use a "for" loop to pull each ball from the list, then call the update
        # method on that ball.
        for ball in self.ball_list:
            ball.on_update()


def main():
    window = MyGame(SCREEN_WIDTH, SCREEN_HEIGHT, "Drawing Example")

    arcade.run()


main()
../../_images/ball_list_example.gif