Making text appear one character at a time [PyGame] - python

I am trying to make the dialog appear one character at a time (Like in Pokemon games and other similar).
I have searched over the internet but have not managed to find anything helpful.
I am aware of another question asked like this, but it didn't solve what I am trying to do. I know this can be done because I have seen games made with python where this has been done.

import pygame, sys
from pygame.locals import *
WINDOW_WIDTH = 500
WINDOW_HEIGHT = 500
pygame.init()
DISPLAYSURF = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT))
BLACK = ( 0, 0, 0)
WHITE = (255, 255, 255)
def display_text_animation(string):
text = ''
for i in range(len(string)):
DISPLAYSURF.fill(WHITE)
text += string[i]
text_surface = font.render(text, True, BLACK)
text_rect = text_surface.get_rect()
text_rect.center = (WINDOW_WIDTH/2, WINDOW_HEIGHT/2)
DISPLAYSURF.blit(text_surface, text_rect)
pygame.display.update()
pygame.time.wait(100)
def main():
while True:
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
display_text_animation('Hello World!')
main()
NOTE: I haven't used pygame before so this may not work.

The below works very well. And stops any overloading of the event queue (so multiple lines or lots of text don't stall the animation). This is necessary if embedded in a simple application that doesn't handle the event queue in some other way.
line_space = 16
basicfont = pygame.font.SysFont('MorePerfectDOSVGA', 16)
def text_ani(str, tuple):
x, y = tuple
y = y*line_space ##shift text down by one line
char = '' ##new string that will take text one char at a time. Not the best variable name I know.
letter = 0
count = 0
for i in range(len(str)):
pygame.event.clear() ## this is very important if your event queue is not handled properly elsewhere. Alternativly pygame.event.pump() would work.
time.sleep(0.05) ##change this for faster or slower text animation
char = char + str[letter]
text = basicfont.render(char, False, (2, 241, 16), (0, 0, 0)) #First tuple is text color, second tuple is background color
textrect = text.get_rect(topleft=(x, y)) ## x, y's provided in function call. y coordinate amended by line height where needed
screen.blit(text, textrect)
pygame.display.update(textrect) ## update only the text just added without removing previous lines.
count += 1
letter += 1
print char ## for debugging in console, comment out or delete.
text_ani('this is line number 1 ', (0, 1)) # text string and x, y coordinate tuple.
text_ani('this is line number 2', (0, 2))
text_ani('this is line number 3', (0, 3))
text_ani('', (0, 3)) # this is a blank line

This is a bit more simplistic, it creates a function which you can use whenever you want. In this example I called my function 'slow' and made it so that you need to input a string when calling it. Whatever is inputted will display character by character. The speed depends on the value near 'sleep'. Hope this helped.
from time import * #imports all the time functions
def slow(text): #function which displays characters one at a time
for letters in text: #the variable goes through each character at a time
print(letters, end = "") #current character is printed
sleep(0.02) #insert the time between each character shown
#the for loop will move onto the next character
slow("insert text here") #instead of print, use the name of the function
If you run this as it is, it should output "insert text here" one character at a time.

Related

Python: why the surface.blit function doesn't display all inputs?

I am trying to write a little digits game using pygame. The idea of the game is to guess the four-digit number randomly chosen by computer. But I am stuck at the very beginning I started by creating all the essential elements: colours, fonts, surfaces, etc. I used blit to 'simulate' computer choice and to show the user's guess. And interestingly enough, not all the inputs are displayed. E.g. '1234' and '9999' is displayed. However, '5738' and '7365' are not. Looking forward to hearing opinions of the experienced users.
import random
import pygame
pygame.init()
width = 900
height = 500
black = (0,0,0)
pastel_blue = (200,205,230)
win = pygame.display.set_mode((width,height))
pygame.display.set_caption("Bulls and Cows")
digit_font = pygame.font.SysFont('comicsans', 30)
a = (random.randint(1000, 10000))
print(a)
def display():
win.fill((pastel_blue))
number = digit_font.render("_ _ _ _", 1, black)
win.blit(number, (width//2-number.get_width()//2, height//4))
pygame.display.update()
display()
def guess_number():
global c
c = input("Guess the number: ")
guess_number()
def guess_display():
text = digit_font.render(c, 1, black)
print(text.get_width()//2)
win.blit(text, [width//2-text.get_width()/2, 300]) #this seems to be the part that doesn't work correctly
pygame.display.update()
pygame.time.delay(2000)
guess_display()
You have to handle the events in the application loop. See pygame.event.get() respectively pygame.event.pump():
For each frame of your game, you will need to make some sort of call to the event queue. This ensures your program can internally interact with the rest of the operating system.
def guess_display():
text = digit_font.render(c, 1, black)
print(text.get_width()//2)
win.blit(text, [width//2-text.get_width()/2, 300]) #this seems to be the part that doesn't work correctly
pygame.display.update()
pygame.event.pump() # <---
pygame.time.delay(2000)
However, the usual way is to use an application loop. Also see Why is my PyGame application not running at all?:
def guess_display():
text = digit_font.render(c, 1, black)
print(text.get_width()//2)
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
win.blit(text, [width//2-text.get_width()/2, 300])
pygame.display.update()
Also see:
Why is my display not responding while waiting for input?
Why does pygame.display.update() not work if an input is directly followed after it?
How to create a text input box with pygame?

change the color of a variable in an fstring python [duplicate]

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()
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.

Can't find a way to make multiple lines in pygame

I'm aware of the fact that pygame's screen.blit is not meant to support multiple lines, however I can't figure out a work around. All of the other threads that ask this question just don't work with my code. How do I make this work?
I've tried to split response into two by using splitline() on DisplayRoom.prompt and then having the game just load two lines separately, but DisplayRoom.prompt.splitline() does not turn `DisplayRoom.prompt from a tuple to a list and only returns the value for it.
screen.fill(background_colour)
txt_surface = userfont.render(text, True, color)
screen.blit(txt_surface, (100, 800))
response = promptfont.render(DisplayRoom.prompt, True, color)
screen.blit(response, (80, 300))
pygame.display.flip()
clock.tick_busy_loop(60) # limit FPS
When I defined DisplayRoom.prompt, I expected \n to linebreak it but it doesn't work which is why I'm here.
It is not Surface.blit which doesn't support multiple lines. blit simply draw a Surface on another Surface, it doesn't care what the Surface contains.
It's pygame.Font.render which doesn't support multilines. The docs clearly say:
The text can only be a single line: newline characters are not rendered.
So I don't know what DisplayRoom.prompt is in your code, but if is not a string, is bound to fail: render raises a TypeError: text must be a unicode or bytes.
And if is a string with newlines, newlines are just not rendered.
You have to split the text and render each line separately.
In the following example I create a simple function blitlines which illustrates how you could do.
import sys
import pygame
def blitlines(surf, text, renderer, color, x, y):
h = renderer.get_height()
lines = text.split('\n')
for i, ll in enumerate(lines):
txt_surface = renderer.render(ll, True, color)
surf.blit(txt_surface, (x, y+(i*h)))
background_colour = (0, 0, 0)
textcolor = (255, 255, 255)
multitext = "Hello World!\nGoodbye World!\nI'm back World!"
pygame.init()
screen = pygame.display.set_mode((500, 500))
userfont = pygame.font.Font(None, 40)
screen.fill(background_colour)
blitlines(screen, multitext, userfont, textcolor, 100, 100)
pygame.display.flip()
#main loop
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
This is the result on the screen.

Text on top of each other pygame

I have created a definition called message_display(text) but when ever i use it in my code the text gets on top of each other in the lower left corner you see that hello and world is printed on top of eacht other. Your help is appreciated
import pygame
import random
import sys
pygame.init()
win = pygame.display.set_mode((800,700))
pygame.display.set_caption('Knights Of Dungeons')
gb = pygame.image.load('background.png')
font= pygame.font.SysFont('Gabriola', 30, False,True)
game = True
roll_dice = font.render('Press Enter To roll the dice' ,10,(0,0,0))
def dice():
x = random.randint(1,6)
return x
def message_display(text):
dis_text = font.render(text, 10, (0,0,0))
win.blit(dis_text,(10,650))
pygame.display.update()
player_1 = font.render('Name: Kaan '+ 'Health: 100
' + 'Damage: 0 ' + 'Armour: 0 ')
while game:
pygame.time.delay(50)
for event in pygame.event.get():
if event.type == pygame.QUIT:
game = False
keys = pygame.key.get_pressed()
if keys[pygame.K_RETURN]:
num_thrown = dice()
roll_dice = font.render( 'The number you have throwen is:
'+str(0+num_thrown) ,20,(0,0,0))
win.blit(gb,(0,15))
win.blit(player_1 ,(0,100))
win.blit(roll_dice,(455,650))
message_display('Hello')
message_display('world')
pygame.display.update()
pygame.quit()
When I use pygame, I like to make my text display function much more general. Something like this:
import pygame as pg
def draw_text(surface, text, size, color, x, y):
"""Draw text to surface
surface - Pygame surface to draw to
text - string text to draw
size - font size
color - color of text
x - x position of text on surface
y - y position of text on surface
"""
font = pg.font.Font(font_name, size)
text_surf = font.render(str(text), True, color)
text_rect = text_surf.get_rect()
text_rect.topleft = (x, y) # I use topleft here because that makes sense to me
# for English (unless you want it centered).
# But you can use any part of the rect to start drawing the text
surface.blit(text_surf, text_rect)
Note that you also have to set up the font_name. You can do this inside or outside the function (if you just want one). I've done this globally for my usecase like this:
font_name = pg.font.match_font('arial')

Pygame freezes while displaying text

I'm creating the base of a game that will have scrolling text and text wrapping when a line of text goes to far on the screen.
Everything works (so far) but at a certain point when using my default_display_text function pygame stops responding. It always stops around the "broke, slap some fancy words" part when I use my default_display_text function with greeting as the variable.
I've been able to solve all my other problems with google but this one has got me really stuck! I'll be glad to give any other information and I hope you understand what my code does with help from the comments.
import pygame, math, pygame.display, time, pygame.mouse
from pygame.locals import *
pygame.init()
pygame.font.init()
infoObject = pygame.display.Info()
window_height = infoObject.current_w
window_width = infoObject.current_h
gameDisplay = pygame.display.set_mode((window_height, window_width), pygame.RESIZABLE)
pygame.display.set_caption('Aperture_Game')
pygame.mouse.set_visible(False)
c_font = 'C:\Windows\Fonts\Courier Regular.fon'
background = (10,10,10)
black = (0,0,0)
white = (255,255,255)
text_color = (39, 167, 216)
clock = pygame.time.Clock()
loop = True
default_font_size = 26
greeting = "Greetings! My name is Cave Johnson, CEO of Aperture Science. Our motto here is \"If it ain't broke, slap some fancy words on it and sell it for profit!\""
'''
def message_display(text,x,y,font,color,size):
fontObj = pygame.font.SysFont(font, size)
label = fontObj.render(text, 1, color)
gameDisplay.blit(label, (x,y))
'''
def default_display_text(string,x,y):
text = ''
#a tracks i's value.
a = 0
#newline helps determine when to wrap the text.
newline = 0
#ref is a reference for when to wrap the text (I only want to wrap text after a space so the wrapping doesn't split words in half).
ref = ' '
fontObj = pygame.font.SysFont(c_font, default_font_size)
text_surface = fontObj.render(text, True, text_color)
text_rect = text_surface.get_rect()
#this for loop is what displays the text one character at a time and wraps it.
for i in range(len(string)):
gameDisplay.fill(background)
if newline == 0:
gameDisplay.blit(text_surface, (x,y))
#if the text goes to far to the right and a word is complete, I want to start a new line.
if pygame.Surface.get_width(text_surface) > 1000:
#makes sure that its not in the middle of a word.
if string[a] == ref:
#making sure its not a double space
if string[a+1] != ref:
newline = 1
text = text[a:]
else:
newline = 0
#adds another character of the string to the text variable
if newline == 0:
text += string[i]
# if newline is not 0 then the start of the text is shifted down and all the previous text is deleted.
else:
y += 25
newline = 0
text += string[a:a+1]
text = text[a:]
a = 0
a += 1
pygame.display.update()
pygame.time.wait(50)
#Just your average main loop
def main():
loop = True
while loop:
for event in pygame.event.get():
if event.type == pygame.QUIT:
loop = False
if event.type == KEYDOWN:
if event.key == K_ESCAPE:
loop = False
#add controls here later
gameDisplay.fill(background)
default_display_text(greeting, 50, 50)
pygame.display.update()
clock.tick(60)
main()
pygame.quit()
quit()
The Python Debugger is your friend.
Start your program using the syntax
python -m pdb <scriptfile>
Enter "c" for continue into the cli
As soon as your program seems to freeze, press CTRL+C
You should then be able to analyze the current state of the program using the debugger. For instance entering "where" into the cli, you should see the current callstack and line number.

Categories