{"id":"pygame-techniques-text","slug":"text","title":"Text in the window","text":"pygame draws text in two steps. First, a **font** object knows the shape of the letters and their size. Its `render` method then makes an image of a piece of text, and that image is drawn with `blit`, like any other image:\n\n```python\nimport pygame\n\npygame.init()\nwindow = pygame.display.set_mode((320, 240))\nfont = pygame.font.SysFont(\"Arial\", 32)\n\nwhile True:\n    for event in pygame.event.get():\n        if event.type == pygame.QUIT:\n            exit()\n\n    window.fill((0, 0, 0))\n    text = font.render(\"Hello, pygame!\", True, (255, 220, 0))\n    window.blit(text, (40, 100))\n    pygame.display.flip()\n```\n\n```window-shot\n{\"image\":\"lessons/windows/54160d54e48a68fb.png\",\"size\":[320,240],\"title\":\"pygame window\",\"frames\":30,\"log\":\"fill (0, 0, 0)\\nblit text \\\"Hello, pygame!\\\" at (40, 100)\",\"ended\":\"closed\",\"code\":\"import pygame\\n\\npygame.init()\\nwindow = pygame.display.set_mode((320, 240))\\nfont = pygame.font.SysFont(\\\"Arial\\\", 32)\\n\\nwhile True:\\n    for event in pygame.event.get():\\n        if event.type == pygame.QUIT:\\n            exit()\\n\\n    window.fill((0, 0, 0))\\n    text = font.render(\\\"Hello, pygame!\\\", True, (255, 220, 0))\\n    window.blit(text, (40, 100))\\n    pygame.display.flip()\\n\",\"window\":{\"frames\":30,\"events\":[]}}\n```\n\n- `pygame.font.SysFont(\"Arial\", 32)` makes a font: the typeface Arial, 32 pixels high. Make the font once, before the main loop, since loading it takes time.\n- `font.render(\"Hello, pygame!\", True, (255, 220, 0))` makes an image of the text in yellow. `True` asks for smooth edges on the letters.\n- The window as text names the text it drew: `blit text \"Hello, pygame!\" at (40, 100)`.\n\nOn your own computer, `SysFont` looks for the named typeface, and if the computer doesn't have it, it uses pygame's own font. On this site every font is pygame's own, whatever its name. (`pygame.font.Font(None, 32)` always gives that font.) Letters look a little different on different computers, so the checks compare the window as text, not the pictures.\n\n### Text that changes\n\nA score or a time changes during the game, so make its image in every frame, from the current value. An f-string puts the value in the text. Here each press of the space bar is a point:\n\n```python\nimport pygame\n\npygame.init()\nwindow = pygame.display.set_mode((320, 240))\nfont = pygame.font.SysFont(\"Arial\", 24)\nscore = 0\n\nwhile True:\n    for event in pygame.event.get():\n        if event.type == pygame.KEYDOWN and event.key == pygame.K_SPACE:\n            score += 1\n        if event.type == pygame.QUIT:\n            exit()\n\n    window.fill((0, 0, 0))\n    text = font.render(f\"Score: {score}\", True, (255, 255, 255))\n    window.blit(text, (10, 10))\n    pygame.display.flip()\n```\n\n```window-shot\n{\"image\":\"lessons/windows/92458dbef283e703.png\",\"size\":[320,240],\"title\":\"pygame window\",\"frames\":30,\"log\":\"fill (0, 0, 0)\\nblit text \\\"Score: 3\\\" at (10, 10)\",\"ended\":\"closed\",\"code\":\"import pygame\\n\\npygame.init()\\nwindow = pygame.display.set_mode((320, 240))\\nfont = pygame.font.SysFont(\\\"Arial\\\", 24)\\nscore = 0\\n\\nwhile True:\\n    for event in pygame.event.get():\\n        if event.type == pygame.KEYDOWN and event.key == pygame.K_SPACE:\\n            score += 1\\n        if event.type == pygame.QUIT:\\n            exit()\\n\\n    window.fill((0, 0, 0))\\n    text = font.render(f\\\"Score: {score}\\\", True, (255, 255, 255))\\n    window.blit(text, (10, 10))\\n    pygame.display.flip()\\n\",\"window\":{\"frames\":30,\"events\":[{\"at\":5,\"type\":\"keydown\",\"key\":\"K_SPACE\"},{\"at\":6,\"type\":\"keyup\",\"key\":\"K_SPACE\"},{\"at\":12,\"type\":\"keydown\",\"key\":\"K_SPACE\"},{\"at\":13,\"type\":\"keyup\",\"key\":\"K_SPACE\"},{\"at\":20,\"type\":\"keydown\",\"key\":\"K_SPACE\"},{\"at\":21,\"type\":\"keyup\",\"key\":\"K_SPACE\"}]}}\n```\n\n### The time, and centered text\n\n`pygame.time.get_ticks()` gives the number of milliseconds since the program called `pygame.init()`. With `// 1000` it's whole seconds, which makes a stopwatch. A text image has a width like any other image, so it can be centered the same way:\n\n```python\nimport pygame\n\npygame.init()\nwindow = pygame.display.set_mode((320, 240))\nfont = pygame.font.SysFont(\"Arial\", 40)\nclock = pygame.time.Clock()\n\nwhile True:\n    for event in pygame.event.get():\n        if event.type == pygame.QUIT:\n            exit()\n\n    seconds = pygame.time.get_ticks() // 1000\n    text = font.render(f\"{seconds} s\", True, (255, 255, 255))\n    x = (320 - text.get_width()) // 2\n\n    window.fill((0, 0, 60))\n    window.blit(text, (x, 100))\n    pygame.display.flip()\n    clock.tick(60)\n```\n\n```window-shot\n{\"image\":\"lessons/windows/44002c68505e2235.png\",\"size\":[320,240],\"title\":\"pygame window\",\"frames\":200,\"log\":\"fill (0, 0, 60)\\nblit text \\\"3 s\\\" at (141, 100)\",\"ended\":\"closed\",\"code\":\"import pygame\\n\\npygame.init()\\nwindow = pygame.display.set_mode((320, 240))\\nfont = pygame.font.SysFont(\\\"Arial\\\", 40)\\nclock = pygame.time.Clock()\\n\\nwhile True:\\n    for event in pygame.event.get():\\n        if event.type == pygame.QUIT:\\n            exit()\\n\\n    seconds = pygame.time.get_ticks() // 1000\\n    text = font.render(f\\\"{seconds} s\\\", True, (255, 255, 255))\\n    x = (320 - text.get_width()) // 2\\n\\n    window.fill((0, 0, 60))\\n    window.blit(text, (x, 100))\\n    pygame.display.flip()\\n    clock.tick(60)\\n\",\"window\":{\"frames\":200,\"events\":[]}}\n```\n\nThe text is narrower or wider as the number changes, and `get_width` keeps it centered. On this site the clock is counted: every `clock.tick(60)` is a 60th of a second, so after 200 frames the stopwatch says 3 seconds, the same in every run.\n","task":"Fill in the blanks, so the program makes a font and an image of the text `Level 1`, and draws it.\n","kind":"fill","seed":"import pygame\n\npygame.init()\nwindow = pygame.display.set_mode((320, 240))\nfont = pygame.font.[[SysFont]](\"Arial\", 32)\n\nwhile True:\n    for event in pygame.event.get():\n        if event.type == pygame.QUIT:\n            exit()\n\n    window.fill((0, 0, 0))\n    text = font.[[render]](\"Level 1\", True, (255, 255, 255))\n    window.blit(text, (110, 100))\n    pygame.display.flip()\n","solution":"import pygame\n\npygame.init()\nwindow = pygame.display.set_mode((320, 240))\nfont = pygame.font.SysFont(\"Arial\", 32)\n\nwhile True:\n    for event in pygame.event.get():\n        if event.type == pygame.QUIT:\n            exit()\n\n    window.fill((0, 0, 0))\n    text = font.render(\"Level 1\", True, (255, 255, 255))\n    window.blit(text, (110, 100))\n    pygame.display.flip()\n","hints":["The font comes from a function of `pygame.font` that takes a typeface's name and a size. The font's method that makes an image of a text comes second.","`pygame.font.SysFont` makes the font, and `font.render` the text's image."],"tests":[{"name":"Test 1","stdin":"","window":{"frames":10,"events":[]},"expectWindow":{"frames":10,"log":"fill (0, 0, 0)\nblit text \"Level 1\" at (110, 100)","size":[320,240],"title":"pygame window","image":"lessons/windows/947425d3116f6a5c.png"},"expect":"","hidden":false},{"name":"Test 2","stdin":"","window":{"frames":4,"events":[]},"expectWindow":{"frames":4,"log":"fill (0, 0, 0)\nblit text \"Level 1\" at (110, 100)","size":[320,240],"title":"pygame window","image":"lessons/windows/947425d3116f6a5c.png"},"expect":"","hidden":true}],"require":[],"forbid":[],"more":[{"task":"Count clicks. The window shows the text `Clicks: 0` at (10, 10), and each press of a mouse button, any button, makes the number one bigger. Draw the text in white on black, with any font.\n","kind":"code","seed":"import pygame\n\npygame.init()\nwindow = pygame.display.set_mode((320, 240))\n\nwhile True:\n    for event in pygame.event.get():\n        if event.type == pygame.QUIT:\n            exit()\n\n    window.fill((0, 0, 0))\n    pygame.display.flip()\n","solution":"import pygame\n\npygame.init()\nwindow = pygame.display.set_mode((320, 240))\nfont = pygame.font.SysFont(\"Arial\", 32)\nclicks = 0\n\nwhile True:\n    for event in pygame.event.get():\n        if event.type == pygame.MOUSEBUTTONDOWN:\n            clicks += 1\n        if event.type == pygame.QUIT:\n            exit()\n\n    window.fill((0, 0, 0))\n    text = font.render(f\"Clicks: {clicks}\", True, (255, 255, 255))\n    window.blit(text, (10, 10))\n    pygame.display.flip()\n","hints":["Make the font before the main loop, and keep the count in a variable that starts at 0.","On a `pygame.MOUSEBUTTONDOWN` event, add one to the count.","In every frame, render `f\"Clicks: {clicks}\"` and blit it at (10, 10)."],"tests":[{"name":"Three clicks","stdin":"","window":{"frames":20,"events":[{"at":3,"type":"mousedown","pos":[100,100],"button":1},{"at":4,"type":"mouseup","pos":[100,100],"button":1},{"at":8,"type":"mousedown","pos":[200,50],"button":3},{"at":9,"type":"mouseup","pos":[200,50],"button":3},{"at":13,"type":"mousedown","pos":[50,200],"button":1},{"at":14,"type":"mouseup","pos":[50,200],"button":1}]},"expectWindow":{"frames":20,"log":"fill (0, 0, 0)\nblit text \"Clicks: 3\" at (10, 10)","size":[320,240],"title":"pygame window","image":"lessons/windows/5339f855f85ff19e.png"},"expect":"","hidden":false},{"name":"No clicks","stdin":"","window":{"frames":6,"events":[]},"expectWindow":{"frames":6,"log":"fill (0, 0, 0)\nblit text \"Clicks: 0\" at (10, 10)","size":[320,240],"title":"pygame window","image":"lessons/windows/fc2fff807d2308e1.png"},"expect":"","hidden":true},{"name":"Test 3","stdin":"","window":{"frames":25,"events":[{"at":2,"type":"mousedown","pos":[10,10],"button":1},{"at":3,"type":"mouseup","pos":[10,10],"button":1},{"at":5,"type":"mousedown","pos":[10,10],"button":1},{"at":6,"type":"mouseup","pos":[10,10],"button":1},{"at":8,"type":"mousedown","pos":[300,230],"button":1},{"at":9,"type":"mouseup","pos":[300,230],"button":1},{"at":11,"type":"mousedown","pos":[160,120],"button":2},{"at":12,"type":"mouseup","pos":[160,120],"button":2},{"at":15,"type":"mousedown","pos":[10,200],"button":1},{"at":16,"type":"mouseup","pos":[10,200],"button":1}]},"expectWindow":{"frames":25,"log":"fill (0, 0, 0)\nblit text \"Clicks: 5\" at (10, 10)","size":[320,240],"title":"pygame window","image":"lessons/windows/04999e65da70fd27.png"},"expect":"","hidden":true},{"name":"A button held down","stdin":"","window":{"frames":20,"events":[{"at":3,"type":"mousedown","pos":[100,100],"button":1},{"at":8,"type":"mouseup","pos":[100,100],"button":1},{"at":17,"type":"mousedown","pos":[60,60],"button":3}]},"expectWindow":{"frames":20,"log":"fill (0, 0, 0)\nblit text \"Clicks: 2\" at (10, 10)","size":[320,240],"title":"pygame window","image":"lessons/windows/a23e257c0636d909.png"},"expect":"","hidden":true}],"require":[],"forbid":[]},{"task":"Make a countdown from 10 seconds. The window shows `Time left: 10` at first, and the number is 10 minus the whole seconds since `pygame.init()` (use `pygame.time.get_ticks()`). When no time is left (the number would be 0 or less), it shows `Time's up!` instead. Use the starter code's font, and center the text across the window with its top at y = 100: its x is `(320 - text.get_width()) // 2`. Use a clock at 60 frames per second.\n","kind":"code","seed":"import pygame\n\npygame.init()\nwindow = pygame.display.set_mode((320, 240))\nfont = pygame.font.SysFont(\"Arial\", 32)\nclock = pygame.time.Clock()\n\nwhile True:\n    for event in pygame.event.get():\n        if event.type == pygame.QUIT:\n            exit()\n\n    window.fill((0, 0, 0))\n    text = font.render(\"Time left: 10\", True, (255, 255, 255))\n    window.blit(text, (10, 100))\n    pygame.display.flip()\n    clock.tick(60)\n","solution":"import pygame\n\npygame.init()\nwindow = pygame.display.set_mode((320, 240))\nfont = pygame.font.SysFont(\"Arial\", 32)\nclock = pygame.time.Clock()\n\nwhile True:\n    for event in pygame.event.get():\n        if event.type == pygame.QUIT:\n            exit()\n\n    left = 10 - pygame.time.get_ticks() // 1000\n    if left > 0:\n        text = font.render(f\"Time left: {left}\", True, (255, 255, 255))\n    else:\n        text = font.render(\"Time's up!\", True, (255, 255, 255))\n\n    window.fill((0, 0, 0))\n    window.blit(text, ((320 - text.get_width()) // 2, 100))\n    pygame.display.flip()\n    clock.tick(60)\n","hints":["`pygame.time.get_ticks() // 1000` is the whole seconds so far. The time left is 10 minus that.","Make the text with an `if`: while the time left is more than 0, `f\"Time left: {left}\"`, and otherwise `\"Time's up!\"`.","Render the text first, then center it with its own width: `(320 - text.get_width()) // 2`."],"tests":[{"name":"After 150 frames","stdin":"","window":{"frames":150,"events":[]},"expectWindow":{"frames":150,"log":"fill (0, 0, 0)\nblit text \"Time left: 8\" at (102, 100)","size":[320,240],"title":"pygame window","image":"lessons/windows/efb32ad82b294c95.png"},"expect":"","hidden":false},{"name":"Test 2","stdin":"","window":{"frames":330,"events":[]},"expectWindow":{"frames":330,"log":"fill (0, 0, 0)\nblit text \"Time left: 5\" at (102, 100)","size":[320,240],"title":"pygame window","image":"lessons/windows/485861ca6dcadcf7.png"},"expect":"","hidden":true},{"name":"After 700 frames","stdin":"","window":{"frames":700,"events":[]},"expectWindow":{"frames":700,"log":"fill (0, 0, 0)\nblit text \"Time's up!\" at (106, 100)","size":[320,240],"title":"pygame window","image":"lessons/windows/fcdb6bdc5b7f81b0.png"},"expect":"","hidden":true},{"name":"In the tenth second","stdin":"","window":{"frames":640,"events":[]},"expectWindow":{"frames":640,"log":"fill (0, 0, 0)\nblit text \"Time's up!\" at (106, 100)","size":[320,240],"title":"pygame window","image":"lessons/windows/fcdb6bdc5b7f81b0.png"},"expect":"","hidden":true},{"name":"After 100 frames","stdin":"","window":{"frames":100,"events":[]},"expectWindow":{"frames":100,"log":"fill (0, 0, 0)\nblit text \"Time left: 9\" at (102, 100)","size":[320,240],"title":"pygame window","image":"lessons/windows/a5e5f558748d8f85.png"},"expect":"","hidden":true}],"require":[],"forbid":[]}]}