Drawing Shapes with Classes - Faster

Screenshot of Shapes example program
shapes_buffered.py
  1"""
  2This simple animation example shows how to use classes to animate
  3multiple objects on the screen at the same time.
  4
  5Because this is redraws the shapes from scratch each frame, this is SLOW
  6and inefficient.
  7
  8Using buffered drawing commands (Vertex Buffer Objects) is a bit more complex,
  9but faster.
 10
 11See http://arcade.academy/examples/index.html#shape-lists for some examples.
 12
 13Also, any Sprite class put in a SpriteList and drawn with the SpriteList will
 14be drawn using Vertex Buffer Objects for better performance.
 15
 16If Python and Arcade are installed, this example can be run from the command line with:
 17python -m arcade.examples.shapes
 18
 19"""
 20
 21import arcade
 22import random
 23import timeit
 24
 25# Set up the constants
 26SCREEN_WIDTH = 800
 27SCREEN_HEIGHT = 600
 28SCREEN_TITLE = "Shapes! Buffered"
 29
 30RECT_WIDTH = 50
 31RECT_HEIGHT = 50
 32
 33NUMBER_OF_SHAPES = 500
 34
 35
 36class Shape:
 37    """ Generic base shape class """
 38    def __init__(self, x, y, width, height, angle, delta_x, delta_y,
 39                 delta_angle, color):
 40        self.x = x
 41        self.y = y
 42        self.width = width
 43        self.height = height
 44        self.angle = angle
 45        self.delta_x = delta_x
 46        self.delta_y = delta_y
 47        self.delta_angle = delta_angle
 48        self.color = color
 49        self.shape_list = None
 50
 51    def move(self):
 52        self.x += self.delta_x
 53        self.y += self.delta_y
 54        self.angle += self.delta_angle
 55        if self.x < 0 and self.delta_x < 0:
 56            self.delta_x *= -1
 57        if self.y < 0 and self.delta_y < 0:
 58            self.delta_y *= -1
 59        if self.x > SCREEN_WIDTH and self.delta_x > 0:
 60            self.delta_x *= -1
 61        if self.y > SCREEN_HEIGHT and self.delta_y > 0:
 62            self.delta_y *= -1
 63
 64    def draw(self):
 65        self.shape_list.center_x = self.x
 66        self.shape_list.center_y = self.y
 67        # self.shape_list.angle = self.angle
 68        self.shape_list.draw()
 69
 70class Ellipse(Shape):
 71
 72    def __init__(self, x, y, width, height, angle, delta_x, delta_y,
 73                 delta_angle, color):
 74
 75        super().__init__(x, y, width, height, angle, delta_x, delta_y,
 76                         delta_angle, color)
 77
 78        shape = arcade.create_ellipse_filled(0, 0,
 79                                             self.width, self.height,
 80                                             self.color, self.angle)
 81        self.shape_list = arcade.ShapeElementList()
 82        self.shape_list.append(shape)
 83
 84
 85class Rectangle(Shape):
 86
 87    def __init__(self, x, y, width, height, angle, delta_x, delta_y,
 88                 delta_angle, color):
 89
 90        super().__init__(x, y, width, height, angle, delta_x, delta_y,
 91                         delta_angle, color)
 92
 93        shape = arcade.create_rectangle_filled(0, 0,
 94                                               self.width, self.height,
 95                                               self.color, self.angle)
 96        self.shape_list = arcade.ShapeElementList()
 97        self.shape_list.append(shape)
 98
 99class Line(Shape):
100
101    def __init__(self, x, y, width, height, angle, delta_x, delta_y,
102                 delta_angle, color):
103
104        super().__init__(x, y, width, height, angle, delta_x, delta_y,
105                         delta_angle, color)
106
107        shape = arcade.create_line(0, 0,
108                                   self.width, self.height,
109                                   self.color, 2)
110        self.shape_list = arcade.ShapeElementList()
111        self.shape_list.append(shape)
112
113class MyGame(arcade.Window):
114    """ Main application class. """
115
116    def __init__(self):
117        super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
118        self.shape_list = None
119
120        self.processing_time = 0
121        self.draw_time = 0
122        self.frame_count = 0
123        self.fps_start_timer = None
124        self.fps = None
125
126    def setup(self):
127        """ Set up the game and initialize the variables. """
128        self.shape_list = []
129
130        for i in range(NUMBER_OF_SHAPES):
131            x = random.randrange(0, SCREEN_WIDTH)
132            y = random.randrange(0, SCREEN_HEIGHT)
133            width = random.randrange(10, 30)
134            height = random.randrange(10, 30)
135            angle = random.randrange(0, 360)
136
137            d_x = random.randrange(-3, 4)
138            d_y = random.randrange(-3, 4)
139            d_angle = random.randrange(-3, 4)
140
141            red = random.randrange(256)
142            green = random.randrange(256)
143            blue = random.randrange(256)
144            alpha = random.randrange(256)
145
146            shape_type = random.randrange(3)
147            # shape_type = 2
148
149            if shape_type == 0:
150                shape = Rectangle(x, y, width, height, angle, d_x, d_y,
151                                  d_angle, (red, green, blue, alpha))
152            elif shape_type == 1:
153                shape = Ellipse(x, y, width, height, angle, d_x, d_y,
154                            d_angle, (red, green, blue, alpha))
155            elif shape_type == 2:
156                shape = Line(x, y, width, height, angle, d_x, d_y,
157                             d_angle, (red, green, blue, alpha))
158
159            self.shape_list.append(shape)
160
161    def on_update(self, dt):
162        """ Move everything """
163        start_time = timeit.default_timer()
164
165        for shape in self.shape_list:
166            shape.move()
167
168        self.processing_time = timeit.default_timer() - start_time
169
170    def on_draw(self):
171        """
172        Render the screen.
173        """
174        # Start timing how long this takes
175        draw_start_time = timeit.default_timer()
176
177        if self.frame_count % 60 == 0:
178            if self.fps_start_timer is not None:
179                total_time = timeit.default_timer() - self.fps_start_timer
180                self.fps = 60 / total_time
181            self.fps_start_timer = timeit.default_timer()
182        self.frame_count += 1
183
184        arcade.start_render()
185
186        for shape in self.shape_list:
187            shape.draw()
188
189        # Display timings
190        output = f"Processing time: {self.processing_time:.3f}"
191        arcade.draw_text(output, 20, SCREEN_HEIGHT - 20, arcade.color.WHITE, 16)
192
193        output = f"Drawing time: {self.draw_time:.3f}"
194        arcade.draw_text(output, 20, SCREEN_HEIGHT - 40, arcade.color.WHITE, 16)
195
196        if self.fps is not None:
197            output = f"FPS: {self.fps:.0f}"
198            arcade.draw_text(output, 20, SCREEN_HEIGHT - 60, arcade.color.WHITE, 16)
199
200        self.draw_time = timeit.default_timer() - draw_start_time
201
202
203def main():
204    window = MyGame()
205    window.setup()
206    arcade.run()
207
208
209if __name__ == "__main__":
210    main()