Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

how to change the color a section of text in pygame

I am creating a game with pygame in which the color of a letter changes when you type that letter. Like nitrotype.com. However the problem is that I don't know how to change the colour of individual letters. I can't clear the screen and then do it because then that would change the color of the entire line. So either I need a way to change the colour of individual letters or a way to just put single letters on the screen one at a time. However I don't know how to uniformly put the letters(such that the end sentence is centered). Please could someone help me out here. Either by telling me how to change the color of individual letters or how to put individual letters in a perfect manner and then change their color.

import pygame as pg
import pygame

pg.init()
screenHeight, screenWidth = 600, 800
gameDisplay = pg.display.set_mode((screenWidth, screenHeight))
pg.display.set_caption("Nitrotype")

black = (255, 255, 255)
white = (0, 0, 0)
gameDisplay.fill(white)
pg.display.update()

gameOn = True
with open("text.txt", "r") as f:
    contents = f.read()

def msgToScreen(msg, color, size):
    cur = []
    strings = []
    words = msg.split(" ")
    for i in words:
        cur.append(i)
        if len(" ".join(cur)) >= 35:
            strings.append(" ".join(cur))
            cur = []
    if cur != []:strings.append(" ".join(cur))
    
    curY = 20
    
    for string in strings:
        font = pg.font.SysFont(None, size)
        text = font.render(string, True, color)
        text_rect = text.get_rect(center=(screenWidth/2, curY))
        gameDisplay.blit(text, text_rect)
        curY += 40
    
    return text

textOnScreen = msgToScreen(contents, black, 50)

pg.display.update()

curIdx = 0
keyCombination = {"a":pg.K_a, "b":pg.K_b, "c":pg.K_c, "d":pg.K_d, "e":pg.K_e, "f":pg.K_f,
                "g":pg.K_g, "h":pg.K_h, "i":pg.K_i, "j":pg.K_j, "k":pg.K_k, "l":pg.K_l,
                "m":pg.K_m, "n":pg.K_n, "o":pg.K_o, "p":pg.K_p, "q":pg.K_q, "r":pg.K_r,
                "s":pg.K_s, "t":pg.K_t, "u":pg.K_u, "v":pg.K_v, "w":pg.K_w, "x":pg.K_x,
                "y":pg.K_y, "z":pg.K_z}
while gameOn:
    for event in pygame.event.get():
        if event.type == pg.QUIT:
            gameOn = False
        if event.type == pg.KEYDOWN:
            if event.key == keyCombination[contents[curIdx].lower()]:
                #Here is where the color of the current letter should change
                curIdx += 1

pg.quit()

like image 717
Ved Rathi Avatar asked Oct 14 '25 17:10

Ved Rathi


1 Answers

You can't change the color of a single letter during font rendering; you'll have to render your text letter by letter.

You can either use render() to render each letter to its own surface and blit them to your screen, but you have to calculate where each letter should go manually.

It's a little bit easier if you use the new freetype module, which has a lot of handy functions in the Font class like origin, get_rect and get_metrics which can calculate how big each letter is.

Here's a simple example I hacked together. It's not perfect but you'll get the idea.

import pygame
import pygame.freetype
from itertools import cycle

def main():
    pygame.init()
    screen = pygame.display.set_mode((800, 600))

    # just some demo data for you to type
    data = cycle(['This is an example.', 'This is another, longer sentence.'])
    current = next(data)
    current_idx = 0 # points to the current letter, as you have already guessed
    
    font = pygame.freetype.Font(None, 50)
    # the font in the new freetype module have an origin property.
    # if you set this to True, the render functions take the dest position 
    # to be that of the text origin, as opposed to the top-left corner
    # of the bounding box
    font.origin = True
    font_height = font.get_sized_height()
    
    # we want to know how much space each letter takes during rendering.
    # the item at index 4 is the 'horizontal_advance_x'
    M_ADV_X = 4
    
    # let's calculate how big the entire line of text is
    text_surf_rect = font.get_rect(current)
    # in this rect, the y property is the baseline
    # we use since we use the origin mode
    baseline = text_surf_rect.y
    # now let's create a surface to render the text on
    # and center it on the screen
    text_surf = pygame.Surface(text_surf_rect.size)
    text_surf_rect.center = screen.get_rect().center
    # calculate the width (and other stuff) for each letter of the text
    metrics = font.get_metrics(current)

    while True:
        events = pygame.event.get()
        for e in events:
            if e.type == pygame.QUIT:
                return
            if e.type == pygame.KEYDOWN:
                if e.unicode == current[current_idx].lower():
                    # if we press the correct letter, move the index
                    current_idx += 1
                    if current_idx >= len(current):
                        # if the sentence is complete, let's prepare the
                        # next surface
                        current_idx = 0
                        current = next(data)
                        text_surf_rect = font.get_rect(current)
                        baseline = text_surf_rect.y
                        text_surf = pygame.Surface(text_surf_rect.size)
                        text_surf_rect.center = screen.get_rect().center
                        metrics = font.get_metrics(current)

        # clear everything                        
        screen.fill('white')
        text_surf.fill('white')
        
        x = 0
        # render each letter of the current sentence one by one
        for (idx, (letter, metric)) in enumerate(zip(current, metrics)):
            # select the right color
            if idx == current_idx:
                color = 'lightblue'
            elif idx < current_idx:
                color = 'lightgrey'
            else:
                color = 'black'
            # render the single letter
            font.render_to(text_surf, (x, baseline), letter, color)
            # and move the start position
            x += metric[M_ADV_X]
          
        screen.blit(text_surf, text_surf_rect)
        pygame.display.flip()

if __name__ == '__main__':
    main()

Centering the text is easy using a second Surface and using the Rect class' center property.

enter image description here

like image 146
sloth Avatar answered Oct 18 '25 07:10

sloth