Rendering Text and Handling Input in pygame-ce

Drawing Text

Loading a Typeface

Before any text appears on the screen you need a Font object. There are two common ways to obtain one:

  • Load a .ttf or .otf file that you ship with your game (recommended for portability).
  • Ask the operating system for an installed face via SysFont.
import pygame as pg
my_font = pg.font.Font("assets/myfont.ttf", 24)      # from file
fallback  = pg.font.SysFont("Arial", 24)            # from OS

Rasterising Strings

Turn a Python string into a Surface with Font.render:

label = my_font.render("Hello pygame-ce", True, (255, 255, 255), (30, 30, 30))

The second argument toggles anti-aliasing; the third is the foreground colour; the optional fourth is the background colour (transparent if omitted). The resulting surface can be blitted like any other image.

Thread Safety Warning

Each Font instance is not thread-safe. If you need to render from multiple threads, create one Font object per thread.

Decorative Styles

Bold, italic, underline and strikethrough can be toggled on the fly:

my_font.bold = True
my_font.italic = True

Automatic Word-Wrap (≥ 2.3.0)

Pass wraplength (in pixels) to render and optionally set font.align:

my_font.align = pg.FONT_CENTER
wrapped = my_font.render("A very long sentence …", True, (0, 0, 0), wraplength=200)

Writing Direction (≥ 2.1.4)

Use set_direction for RTL, TTB or BTT layouts:

my_font.set_direction(pg.DIRECTION_RTL)
rtl = my_font.render("مرحبا", True, (0, 0, 0))

Font Module Quick-Reference

Extended Typography with freetype

Import from pygame import freetype for kerning, rotatino, and sub-pixel positioning. The API mirrors font but adds attributes such as Font.rotation, Font.kerning, and Font.render_to for direct blitting.

Keyboard Handling

Polling Keystate

pg.key.get_pressed() returns a sequence indexed by key constants; a value of 1 means the key is currently held down.

keys = pg.key.get_pressed()
if keys[pg.K_LEFT]:
    player.move_left()

Modifier Keys

Query the state of Shift, Ctrl, Alt, etc. with pg.key.get_mods():

if pg.key.get_mods() & pg.KMOD_CTRL:
    fire_rocket()

Repeat & Delay

Control how often KEYDOWN events are generated while a key is held:

pg.key.set_repeat(500, 50)   # 500 ms delay, then 50 ms repeat

Text Input & IME

Enable the platform input method:

os.environ["SDL_IME_SHOW_UI"] = "1"
pg.key.start_text_input()
pg.key.set_text_input_rect(pg.Rect(cursor_x, cursor_y, 0, 0))

Listen for TEXTINPUT events to receive fully composed characters.

Mouse Support

Position & Buttons

x, y = pg.mouse.get_pos()
left, middle, right = pg.mouse.get_pressed()

Hiding & Custom Cursors

pg.mouse.set_visible(False)                 # hide
pg.mouse.set_cursor(pg.SYSTEM_CURSOR_HAND)  # system hand
pg.mouse.set_cursor((16, 16), hotspot, *pg.cursors.compile(cursor_bits))

Relative Mode

For first-person cameras:

pg.mouse.set_relative_mode(True)   # hides cursor & locks to window

Practical Example: Mini Text Editor

The following single-file program demonstrates everything discussed above: loading a font, handling keyboard input, displaying an IME candidate window, and wrapping long lines.

import os, pygame as pg
from pygame.locals import *

os.environ["SDL_IME_SHOW_UI"] = "1"
pg.init()
screen = pg.display.set_mode((400, 300), RESIZABLE)
font   = pg.font.SysFont("consolas", 22)
clock  = pg.time.Clock()

text_lines = [""]
cursor = (0, 0)           # (line, col)
blink  = True
pg.time.set_timer(pg.USEREVENT, 500)
pg.key.set_repeat(500, 30)

def wrap(txt, max_w):
    """Naïve word-wrapper."""
    words, line, lines = txt.split(" "), "", []
    for w in words:
        if font.size(line + w)[0] > max_w:
            lines.append(line)
            line = w + " "
        else:
            line += w + " "
    lines.append(line)
    return lines or [""]

running = True
while running:
    for ev in pg.event.get():
        if ev.type == QUIT:
            running = False
        elif ev.type == pg.USEREVENT:
            blink = not blink
        elif ev.type == TEXTINPUT:
            y, x = cursor
            text_lines[y] = text_lines[y][:x] + ev.text + text_lines[y][x:]
            cursor = (y, x + len(ev.text))
        elif ev.type == KEYDOWN:
            y, x = cursor
            if ev.key == K_BACKSPACE and x > 0:
                text_lines[y] = text_lines[y][:x-1] + text_lines[y][x:]
                cursor = (y, x-1)
            elif ev.key == K_DELETE and x < len(text_lines[y]):
                text_lines[y] = text_lines[y][:x] + text_lines[y][x+1:]
            elif ev.key == K_LEFT and x > 0:
                cursor = (y, x-1)
            elif ev.key == K_RIGHT and x < len(text_lines[y]):
                cursor = (y, x+1)
            elif ev.key == K_RETURN:
                text_lines.insert(y+1, text_lines[y][x:])
                text_lines[y] = text_lines[y][:x]
                cursor = (y+1, 0)

    screen.fill((25, 25, 25))
    for i, line in enumerate(text_lines):
        screen.blit(font.render(line, True, (220, 220, 220)), (10, 10 + i*26))
    # draw blinking caret
    if blink:
        y, x = cursor
        caret_x = 10 + font.size(text_lines[y][:x])[0]
        caret_y = 10 + y*26
        pg.draw.rect(screen, (255, 255, 255), (caret_x, caret_y, 2, 26))
    pg.display.flip()
    clock.tick(60)

pg.quit()

Tags: pygame-ce font text-rendering keyboard-input mouse-input

Posted on Sun, 10 May 2026 03:18:51 +0000 by desenhistas