Creating a Typewriter Effect Text Box in Pygame

Resources


Introduction

I had a youtube comment on my video on how to use sprites for games in PyGames in python. He asked:

“Do you think you would be able to show how to make text with pygame look like it is typing out, instead of just appearing all at once? And potentially how to integrate it with dirty sprites to clear it off of the screen for a new line of text to appear?"

I thought this was a great little project, it has turned into a journey.

After playing around with a lot of different ideas, I finalized my journey by creating this Python code to simulate text on a text area in a PyGame window. The concepts in the code can be used in other graphics systems.

So I will walk through the important parts of the code. I assume you are familiar with PyGame and Python.

Example of program running:

Code and explanation:

main.py

Here is part of the PyGame Main loop

(1) First I create a PyGame Rect object with where we want the text area. Then we create to object passing in the parameters to determine the text to scroll (STORY), its font, foreground color, background color, and finally the optional parameter for the speed of typing in words per minute.

(2) Here, I call the update() method and the draw method(screen) so that update will make any needed changes to the area, and draw() will render it to the screen when needed

text_area.py (class TextArea)

(1) File Docstring, note I use a normal curve for the timing between simulated keypresses based on observation of real humans. See reference above.

(2) I will use the gauss method to create random values on a normal curve, and the deque class to store a first in first out queue for feeding characters to be displayed. This buffer will allow adding new text dynamically.

(3) Here we just calculate some useful constants to deal with going from words per minute to characters per second. And BASE_DELAY_MS is the average time in milliseconds for a normal typist at 40 words per minute.

(1) The _type_delay method is an internal method to return a random sample delay appropriate for the words per minute parameter passed in.

(2) We calculate speed_factor which is a ratio to normal 40 wpm typing speed and mean expressed in milliseconds.

(3) I get the normal distribution random number from the random.gauss method. I make the standard deviation half of the mean, this is my ad-hoc approach based on some common sense. The return statement limits the time between 0 and a scaled max of 3 seconds for a 40 wpm typist.

Class TextArea

(1) Note in the class docstring here, that there is another file typing_area_sprite.py, that is the same as this but implements a PyGame DirtySprite class. Also it points out to make sure to call the update and draw methods in the main game loop.

(1) Here is the Constructor and the docstring for its arguments.

(2) char_queue will hold the characters that will be popped off as needed during the rendering.

(3) area_surface is the actual surface for the text area.

(4) y will keep the state of the vertical pixel position to draw the next line at. y_delta is how far to move y to go between lines.

(5) line just holds the current line characters, one char is added to this at a time. next_time is when the next character should be rendered, and dirty flags when it is time to render in the draw method.

(1) The Method _render_new_line is only called when a ‘\n’ character is encountered, it either moves y down a line or scrolls the whole area up to make room for a new line.

(2) If it is determined that there is no room at y to fit a new line into the area, then we use blit to copy all the pixels in the area up one line, and then erase the bottom. This now sets up _render_char to start rendering new characters in this newly erased area.

(1) _render_char method just calls _render_new_line if the character is a new line ('\n').

(2) Otherwise, it adds the character to the end of the line string, then renders it to y, and sets dirty.

(1) The update method uses a while loop to render all the characters it needs to before the next game refresh at tick(). This is needed to handle the possibility of multiple characters between ticks.

In the loop, the code gets the character from the queue and calls _render_char. Then we advance next_time with a random delay.

(2) We arrive here if there are no characters, we will just set next_time to the current time to cause new characters that arrive to an empty queue to start displaying immediately.

(1) draw is very standard, it just blits the area to the screen if dirty is set


Remember you can find a link code at the top of this article in the references area.