The Fastest Text Drawing: pyglet Batches

Screenshot of drawing with text objects

This example demonstrates the most efficient way to render arcade.Text objects: adding them to pyglet’s Batch. Otherwise, it is the same as the Better Text Drawing with Text Objects example.

For a much simpler and slower approach, see Slow but Easy Text Drawing.

drawing_text_objects.py
  1"""
  2The current fastest way to draw text with arcade.
  3
  4This example improves on the other two text-drawing examples
  5by using pyglet's batch functionality.
  6
  7Although pyglet's batches do not support non-drawing features like
  8Arcade's SpriteList, they offer similar benefits for drawing. Adding
  9arcade.Text objects to a batch allows drawing thousands of them with
 10almost the same cost as drawing a single one directly.
 11
 12If Python and Arcade are installed, this example can be run from the command line with:
 13python -m arcade.examples.drawing_text_objects_batch
 14"""
 15import arcade
 16from pyglet.graphics import Batch
 17
 18# Load fonts bundled with Arcade such as the Kenney fonts
 19arcade.resources.load_kenney_fonts()
 20arcade.resources.load_liberation_fonts()
 21
 22WINDOW_WIDTH = 1280  # Window width in pixels
 23WINDOW_HEIGHT = 800  # Window height in pixels
 24WINDOW_TITLE = "Drawing Text Example"  # Window title
 25DEFAULT_LINE_HEIGHT = 45  # Line height to use in pixels
 26DEFAULT_FONT_SIZE = 20  # Default font size in points
 27
 28
 29class GameView(arcade.View):
 30    """
 31    Main application class.
 32    """
 33
 34    def __init__(self):
 35        super().__init__()
 36
 37        self.background_color = arcade.color.BEIGE
 38        self.text_angle = 0
 39        self.time_elapsed = 0.0
 40
 41        self.batch = Batch()
 42
 43        # Add the screen title
 44        start_x = 0
 45        start_y = WINDOW_HEIGHT - DEFAULT_LINE_HEIGHT * 1.5
 46        self.title = arcade.Text(
 47            "Text Drawing Examples",
 48            start_x,
 49            start_y,
 50            arcade.color.BLACK,
 51            DEFAULT_FONT_SIZE * 2,
 52            width=WINDOW_WIDTH,
 53            align="center",
 54            batch=self.batch,
 55        )
 56
 57        # start_x and start_y make the start point for the text. We draw a dot to make it
 58        # easy too see the text in relation to its start x and y.
 59        start_x = 10
 60        start_y = WINDOW_HEIGHT - DEFAULT_LINE_HEIGHT * 3
 61        self.fonts = arcade.Text(
 62            "Fonts:",
 63            start_x,
 64            start_y,
 65            arcade.color.FRENCH_WINE,
 66            DEFAULT_FONT_SIZE, bold=True,
 67            batch=self.batch,
 68        )
 69
 70        # Move the y value down to create another line of text
 71        start_y -= DEFAULT_LINE_HEIGHT
 72        self.font_default = arcade.Text(
 73            "Default Font (Arial)",
 74            start_x,
 75            start_y,
 76            arcade.color.BLACK,
 77            DEFAULT_FONT_SIZE,
 78            batch=self.batch,
 79        )
 80
 81        # Show some built-in fonts
 82        start_y -= DEFAULT_LINE_HEIGHT
 83        self.font_kenney_blocks = arcade.Text(
 84            "Kenney Blocks Font",
 85            start_x,
 86            start_y,
 87            arcade.color.BLACK,
 88            DEFAULT_FONT_SIZE,
 89            font_name="Kenney Blocks",
 90            batch=self.batch,
 91        )
 92
 93        start_y -= DEFAULT_LINE_HEIGHT
 94        self.font_kenney_future = arcade.Text(
 95            "Kenney Future Font",
 96            start_x,
 97            start_y,
 98            arcade.color.BLACK,
 99            DEFAULT_FONT_SIZE,
100            font_name="Kenney Future",
101            batch=self.batch,
102        )
103
104        start_y -= DEFAULT_LINE_HEIGHT
105        self.font_kenney_high = arcade.Text(
106            "Kenney High Font",
107            start_x,
108            start_y,
109            arcade.color.BLACK,
110            DEFAULT_FONT_SIZE,
111            font_name="Kenney High",
112            batch=self.batch,
113        )
114
115        start_y -= DEFAULT_LINE_HEIGHT
116        self.font_kenney_high_square = arcade.Text(
117            "Kenney High Square Font",
118            start_x,
119            start_y,
120            arcade.color.BLACK,
121            DEFAULT_FONT_SIZE,
122            font_name="Kenney High Square",
123            batch=self.batch,
124        )
125
126        start_y -= DEFAULT_LINE_HEIGHT
127        self.font_kenney_mini_square = arcade.Text(
128            "Kenney Mini Square Font",
129            start_x, start_y,
130            arcade.color.BLACK,
131            DEFAULT_FONT_SIZE,
132            font_name="Kenney Mini Square",
133            batch=self.batch,
134        )
135
136        start_y -= DEFAULT_LINE_HEIGHT
137        self.font_kenney_pixel = arcade.Text(
138            "Kenney Pixel Font",
139            start_x, start_y,
140            arcade.color.BLACK,
141            DEFAULT_FONT_SIZE,
142            font_name="Kenney Pixel",
143            batch=self.batch,
144        )
145
146        start_y -= DEFAULT_LINE_HEIGHT
147        self.font_kenney_pixel_square = arcade.Text(
148            "Kenney Pixel Square Font",
149            start_x, start_y,
150            arcade.color.BLACK,
151            DEFAULT_FONT_SIZE,
152            font_name="Kenney Pixel Square",
153            batch=self.batch,
154        )
155
156        start_y -= DEFAULT_LINE_HEIGHT
157        self.font_kenney_rocket = arcade.Text(
158            "Kenney Rocket Font",
159            start_x, start_y,
160            arcade.color.BLACK,
161            DEFAULT_FONT_SIZE,
162            font_name="Kenney Rocket",
163            batch=self.batch,
164        )
165
166        start_y -= DEFAULT_LINE_HEIGHT
167        self.font_kenney_rocket_square = arcade.Text(
168            "Kenney Rocket Square Font",
169            start_x, start_y,
170            arcade.color.BLACK,
171            DEFAULT_FONT_SIZE,
172            font_name="Kenney Rocket Square",
173            batch=self.batch,
174        )
175
176        start_y -= DEFAULT_LINE_HEIGHT
177        # When trying to use system fonts, it can be risky to specify
178        # only a single font because someone else's computer might not
179        # have it installed. This is especially true if they run a
180        # different operating system. For example, if you are on Windows
181        # and a friend has a mac or Linux, they might not have the same
182        # fonts. Your game could look different or broken on their computer.
183        # One way around that is to provide multiple options for draw_text
184        # to try. It will use the first one it finds, and use Arial as a
185        # default if it can't find any of them.
186        # In the example below, draw_text is given a tuple of names for very
187        # similar fonts, each of which is common on a different major
188        # operating systems.
189        self.font_times_new_roman = arcade.Text(
190            "Times New Roman (Or closest match on system)",
191            start_x, start_y,
192            arcade.color.BLACK,
193            DEFAULT_FONT_SIZE,
194            font_name=(
195                "Times New Roman",  # Comes with Windows
196                "Times",  # MacOS may sometimes have this variant
197                # Common on Linux systems + we ship it with Arcade
198                "Liberation Serif"
199            ),
200            batch=self.batch,
201        )
202
203        start_y -= DEFAULT_LINE_HEIGHT
204        self.multi_line_breaks = arcade.Text(
205            "Multi-Line\ntext using\n\\n characters.",
206            start_x, start_y,
207            arcade.color.BLACK,
208            DEFAULT_FONT_SIZE / 2,
209            multiline=True,
210            width=300,
211            batch=self.batch,
212        )
213
214        start_y -= DEFAULT_LINE_HEIGHT * 1.5
215        self.multiline_wrap = arcade.Text(
216            "Wrapping really long text automatically to a new line. "
217            "The quick brown fox jumped over the lazy dogs.",
218            start_x,
219            start_y,
220            arcade.color.BLACK,
221            DEFAULT_FONT_SIZE / 2,
222            multiline=True,
223            width=300,
224            batch=self.batch,
225        )
226
227        # --- Column 2 ---
228        start_x = 750
229        start_y = WINDOW_HEIGHT - DEFAULT_LINE_HEIGHT * 3
230        self.text_positioning = arcade.Text(
231            "Text Positioning:",
232            start_x,
233            start_y,
234            arcade.color.FRENCH_WINE,
235            DEFAULT_FONT_SIZE,
236            bold=True,
237            batch=self.batch,
238        )
239
240        # start_x and start_y make the start point for the text.
241        # We draw a dot to make it easy too see the text in relation to
242        # its start x and y.
243        start_y -= DEFAULT_LINE_HEIGHT
244
245        self.default_baseline_left = arcade.Text(
246            "Default of 'baseline' and 'Left'",
247            start_x,
248            start_y,
249            arcade.color.BLACK,
250            DEFAULT_FONT_SIZE,
251            batch=self.batch,
252        )
253
254        start_y -= DEFAULT_LINE_HEIGHT
255        self.bottom_left = arcade.Text(
256            "'bottom' and 'left'",
257            start_x,
258            start_y,
259            arcade.color.BLACK,
260            DEFAULT_FONT_SIZE,
261            anchor_x="left",
262            anchor_y="bottom",
263            batch=self.batch,
264        )
265
266        start_y -= DEFAULT_LINE_HEIGHT
267        self.top_left = arcade.Text(
268            "'top' and 'left'",
269            start_x, start_y,
270            arcade.color.BLACK,
271            DEFAULT_FONT_SIZE,
272            anchor_x="left",
273            anchor_y="top",
274            batch=self.batch,
275        )
276
277        start_y -= DEFAULT_LINE_HEIGHT * 2
278        self.basline_center = arcade.Text(
279            "'baseline' and 'center'",
280            start_x, start_y,
281            arcade.color.BLACK,
282            DEFAULT_FONT_SIZE,
283            anchor_x="center",
284            anchor_y="baseline",
285            batch=self.batch,
286        )
287
288        start_y -= DEFAULT_LINE_HEIGHT
289        self.baseline_right = arcade.Text(
290            "'baseline' and 'right'",
291            start_x,
292            start_y,
293            arcade.color.BLACK,
294            DEFAULT_FONT_SIZE,
295            anchor_x="right",
296            anchor_y="baseline",
297            batch=self.batch,
298        )
299
300        start_y -= DEFAULT_LINE_HEIGHT
301        self.center_center = arcade.Text(
302            "'center' and 'center'",
303            start_x,
304            start_y,
305            arcade.color.BLACK,
306            DEFAULT_FONT_SIZE,
307            anchor_x="center",
308            anchor_y="center",
309            batch=self.batch,
310        )
311
312        start_y -= DEFAULT_LINE_HEIGHT * 4
313        # start_x = 0
314        # start_y = 0
315        self.rotating_text = arcade.Text(
316            "Rotating Text",
317            start_x, start_y,
318            arcade.color.BLACK,
319            DEFAULT_FONT_SIZE,
320            anchor_x="center",
321            anchor_y="center",
322            rotation=0,
323            batch=self.batch,
324        )
325
326    def on_update(self, delta_time):
327        self.text_angle += 1
328        self.time_elapsed += delta_time
329        self.rotating_text.rotation = self.text_angle
330
331    def on_draw(self):
332        """
333        Render the screen.
334        """
335        # This command should happen before we start drawing. It will clear
336        # the screen to the background color, and erase what we drew last frame.
337        self.clear()
338
339        # Draw all the text objects
340        self.batch.draw()
341
342        arcade.draw_point(
343            self.default_baseline_left.x,
344            self.default_baseline_left.y,
345            arcade.color.BARN_RED,
346            5,
347        )
348
349        arcade.draw_point(
350            self.bottom_left.x,
351            self.bottom_left.y,
352            arcade.color.BARN_RED,
353            5,
354        )
355
356        arcade.draw_point(
357            self.top_left.x,
358            self.top_left.y,
359            arcade.color.BARN_RED,
360            5,
361        )
362
363        arcade.draw_point(
364            self.basline_center.x,
365            self.basline_center.y,
366            arcade.color.BARN_RED,
367            5
368        )
369
370        arcade.draw_point(
371            self.baseline_right.x,
372            self.baseline_right.y,
373            arcade.color.BARN_RED,
374            5,
375        )
376
377        arcade.draw_point(
378            self.center_center.x,
379            self.center_center.y,
380            arcade.color.BARN_RED,
381            5,
382        )
383
384        arcade.draw_point(
385            self.rotating_text.x,
386            self.rotating_text.y,
387            arcade.color.BARN_RED,
388            5,
389        )
390
391    def on_key_press(self, symbol: int, modifiers: int):
392        """ Handle key press events """
393        if symbol == arcade.key.ESCAPE:
394            self.window.close()
395
396
397def main():
398    """ Main function """
399    # Create a window class. This is what actually shows up on screen
400    window = arcade.Window(WINDOW_WIDTH, WINDOW_HEIGHT, WINDOW_TITLE)
401
402    # Create and setup the GameView
403    game = GameView()
404
405    # Show GameView on screen
406    window.show_view(game)
407
408    # Start the arcade game loop
409    arcade.run()
410
411
412if __name__ == "__main__":
413    main()