So I'm recreating a part of pokemon yellow (trying to make it as close to the original as possible) And for 2 days now I'm searching a smart and efficient way to render and display a string one character at a time in the textbox, just like the pokemon games!(By the way I'm using pygame and python). Does anyone know any way of achieving this? I've tried many things but when rendering one character at a time, there is always inadequate space between them.
Sorry for the long question!
Cheers,
Alex
(edit) Thanks for your interest guys!!!
I'm not sure if I know the correct way to display my code here, If I should just
copy paste it in here or upload in dropbox or somewhere else..
(edit2) Just to clarify, i use the font at size 28, so the way I'm trying to render the characters right now, is to make a list where every element has the format (character_to_render,x_pos_to_render,y_pos_to_render). The next character would be (character2_to_render,x_pos_to_render + 28,y_pos_to_render). But approaching the problem this way, leaves inadequate space between some characters and some others are just fine.
(Edit 3) : Thanks for all your answers guys ! After closely observing the emulator, I noticed that the inadequate spacing between rendered characters is apparent there as well! So I'll just ignore this issue Andover on with my project !! Cheers and have a nice day!
Ok so here is the best solution that I have come up with so far.
You want to be able to display a string, but you only want to do it one character at a time. With strings you can do something like string[0:len(string)] which will return the whole string. So what I am thinking and please correct me if I'm wrong, but say you lower the FPS for a couple seconds, or If you do not want to do this because you still want to accept user input to skip the text.
So you have your while loop, and you check for if text is being displayed. If it is, you want to add a new letter to the string that you are displaying to the screen. I would recommend using a class for the text displayed on the screen.
surf = pygame.Surface(80, 80)
Text = TextBoxString("Hello World")
font = pygame.font.SysFont("Arial", 18)
while true:
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
exit()
elif event.type == MOUSEBUTTONUP:
Text.showAll()
surf.fill((0,0,0))
text = font.render(Text.currentString, (0,0,0))
surf.blit(text, (0,0))
Text.addOn()
class TextBoxString:
def __init__(self, string):
#string that you will be dealing with
self.totalString = string
self.currentString = string[0]
#how many characters you want shown to the screen
self.length = 0
#this means that every four times through yourÂ
#while loop a new char is displayed
self.speed = 4
def addOn(self) #adds one to the loop num and then checks if the loop num equals the speed
self.loopNum += 1
if self.loopNum == self.speed:
self.length += 1
self.loopNum=0
self.currentString = totalString[0: self.length]
def showAll(self):
self.length = len(self.totalString)
self.currentString = [0: self.length]
Related
Below is a snippet from a game I am currently making for A-Level coursework. I am making an intro cutscene in which I wish to make text be revealed one letter at a time (scroll, pokemon style). However my current solution requires the use of a for loop per line of text. This is fine for visual effect however it prevents the user from being able to interact with the window. I would like to add a skip button but cannot do so sue to this problem. I tried using more if statements but the code became messy, buggy, and a lot less efficient. Is there an easier, more efficient fix?
screen.blit(introImage4,(0,16))
if flags["IntroStage3"] == True:
for i in range(len(introText[0])):
introTextImage1 = myFont.render(introText[0][i], True, white)
screen.blit(introTextImage1,(50 + (myFont.size(introText[0][:i])[0]), 50))
pygame.display.update()
clock.tick(textFPS)
for i in range(len(introText[1])):
introTextImage2 = myFont.render(introText[1][i], True, white)
screen.blit(introTextImage2,(50 + (myFont.size(introText[1][:i])[0]), 100))
pygame.display.update()
clock.tick(textFPS)
for i in range(len(introText[2])):
introTextImage3 = myFont.render(introText[2][i], True, white)
screen.blit(introTextImage3,(50 + (myFont.size(introText[2][:i])[0]), 150))
pygame.display.update()
clock.tick(textFPS)
flags["IntroStage4"] = True
flags["IntroStage3"] = False
if flags["IntroStage4"] == True:
introTextImage1 = myFont.render(introText[0], True, white)
introTextImage2 = myFont.render(introText[1], True, white)
introTextImage3 = myFont.render(introText[2], True, white)
screen.blit(introTextImage1,(50, 50))
screen.blit(introTextImage2,(50, 100))
screen.blit(introTextImage3,(50, 150))
flags["IntroStage5"] = True
the issue here is that the event handler can't run a new check until your for loop is done.
the solution is to write an animation function for your text. you can do this by adding a variable which contains the text shown on screen, you can then change the value of this variable to another part of the complete text you want to have scroll based on some time dependent value.
this time dependent value can be the time that has passed since the event that triggered the scrolling text.
just to make it a bit more clear here's an example:
say i want the complete text to be "Alice has a big basket of fruit" but i can only fit one word in my scrolling text box and i want to show it for two seconds:
text = "Alice has a big basket of fruit"
def animate_text(text,t): #here t is current unix time minus the unix time whenthat the event that triggered the text scrolling
text_batches=text.split(' ')
return text_batches[t//2] if t//2 <= len(text_batches) else return False
so now we've split the text into batches instead of nesting a loop in your main loop you can blit the batch corresponding to the time that has passed since the animation started
while 1!=0:
# game loopy stuff here
blit(animate_text(text,time.time()-time_when_animation_started))
now that's all a little messy and pseudocodey and it doesn't perfectly handle your particular situation but you should get the idea here
I am new to python and new to programming so please forgive my ignorance.
I am using python-docx to automatically format a document as I need it. In our database application we have a good number of forms that are updated periodically in batches. They all follow pretty much the same format and we are given the newly updated document not formatted for our needs.
So I have a couple questions for what I am trying to do:
1) In each document, there is a number such as 5.1 at the beginning of the document. After the number I need to place a tab and then underline the remainder of the paragraph. I cannot figure out, and maybe it is not possible with the way I am looking at it, but I cannot put a tab in a certain spot or figure out how to underline the remaining of the paragraph because there is only a single run only and I cannot find any way to split a single run into two runs. What i have been able to do is to open the document and use pyautogui to move the number of spaces over to the right using pyautogui.press('right') in a loop after counting how many numbers there are in the 1st paragraph. But that is not preferred i think.
I thought that maybe i could insert the text into a string, then split the number from the rest of the words, and then use python-docx to remove the old text and then insert the new text with the different formatting(runs). Is that the best way to do this or are there better ways?
This is currently how I am performing this task but it does not allow me to bold. I would like to perform the whole task using python-docx so that i am not as dependent using the gui to make the changes
def JITitleNumberLength():
doc = docx.Document('1ji.docx')
p0 = doc.paragraphs[0]
p0Size = len(p0.text) #finds length of title in paragraph 0
JI_Title = p0.text
JI_Title_List = list(JI_Title)
#print(JI_Title_List[2])
JI_Index_Length = 0 #Returns the amount of numbers in the title of the Jury Instruction
counter = 0
while (counter < p0Size) and True:
#print(JI_Title_List[counter], ' ', JI_Index_Length)
if (JI_Title_List[counter] == '1' or
JI_Title_List[counter] == '2' or
JI_Title_List[counter] == '3' or
JI_Title_List[counter] == '4' or
JI_Title_List[counter] == '5' or
JI_Title_List[counter] == '6' or
JI_Title_List[counter] == '7' or
JI_Title_List[counter] == '8' or
JI_Title_List[counter] == '9' or
JI_Title_List[counter] == '0' or
JI_Title_List[counter] == '.'):
#print('If Statement True')
JI_Index_Length = JI_Index_Length + 1
else:
#print('False')
False
counter = counter + 1
return JI_Index_Length
def OpenDocumentForAutoGUI():
os.system("start " + '1ji.docx')
time.sleep(1) #causes delay to allow document to full open before next command runs
def main():
TitleNumberLength = int(JITitleNumberLength())
for i in range(TitleNumberLength):
pyautogui.press('right')
pyautogui.press(['delete', 'tab']) #removes space and inserts tab between number and instruction name
2) In the middle of a paragraph, there will be different options given in a format of [option 1] [option 2] [option 3]. I would like to create a content control that would give a drop down option of these three options. No where that i have read has there been something to content controls with docx. Is there any way to do this or just manually doing this with pyautogui the only option i have? Basically my thought is that i would search the paragraphs for the brackets [] and then input them into a content control somehow, and if need be, use pyautogui which i prefer to stay away from if possible.
I havent even begun the code for this part yet, my only thought would be to put each option into a list and then recall from the list after using pyautogui to manually move the mouse to click on the developer tab in word and then select the content control as there is no keyboard shortcut to bring in a content control. I would really prefer not to do this because then the screen resolution plays a big part and only specific screen resolutions would work.
Sorry - I am pretty sure that run-level formatting is the most granular that you can get. It should be trivial to add the code to create a second Run in the paragraph and apply an underline style to it.
No idea on drop-down list boxes
Two stylistic tips:
You can use 'in' and the constant string.digits with a concatenation operator to simplify your very long if statement
if JI_Title_List[counter] in (string.digits+'.') ....
You can use += to say x = x +; e.g. x+=1 is x = x + 1
counter += 1
JI_Index_Length +=1
I'm trying to create a typewriter effect for text being blitted. By typewriter effect, I simply mean that Im trying to avoid the entirety of the text being blitted on screen at once. Instead, im trying to have each letter appear individually, with a slight delay before the next character in the string appears.
The catch is that im not using pygame's font.render. Instead, i've made my own custom fonts, each letter being saved as a separate image file. Now each alphanumeric character has it's own variable to which it's image is attached and each is appended to a list.
e.g:
letter_IMGs = []
a = "a" == pygame.image.load("IMG/letter_a.gif)
letter_IMG.append(a)
Lower, I have something along these lines:
letter_pos_x = 0
text = "Hello"
for i, c in enumerate(text):
screen.blit(letter_IMGs[i], (letter_pos_x,0))
letter_pos_x += 20
scroll_wait #this is a clock.delay variable. It's value was set outside the loop. I'm just calling it here.
Now as you'd guess, the result with that code is that the entire line of text appears simultaneously after the delay. I've been trying to code it as needed from there, but most of what I come up with returns with a "cannot iterate through surface objects" error.
I'm pretty much at a loss on how I should proceed next. Note that, ive been learning a bit of code on my own, on and off, for the past year and that I don't really know what im doing yet. Any and all help will be much appreciated.
Thanks in advance for your time.
Without getting into the pygame specifices too much, you just need to change the iterator so it returns substrings rather than letters:
def iterate_text(text):
for r in range(len(text)):
yield text[:r + 1]
which will return the substring iteratively:
for t in iterate_text('hello'):
print t
# h
# he
# hel
# hell
# hello
use a separate function to draw the string:
def draw_text(x, y, text):
characters = [letter_IMGs[t] for t in text]
cursor = x
for char in characters:
screen.blit(char, cursor, y)
cursor += 20
in your main loop you can decide when to get the next character. You'll basically do something like:
typewriter = iter_text('hello world')
text_to_draw = None
advance_text = False
at a level outside the loop that survive from frame to frame. When you want to draw the next character, you set advance_text to True, in and in the main loop:
if typewriter and advance_text:
text_to_draw = typewriter.next()
advance_text = False # until you set it again
if text_to_draw :
draw_text(0,0, draw_text)
You can start over by resetting the typewriter with new text, and control the timing of the new character appearing by setting advance_text to True before the next frame
I have tried a for loop with the blit and draw methods and using different variables for " PlayerSprite " and " Treegroup "
for PlayerSprite in Treegroup:
surface.blit(PlayerSprite,(random.randrange(100,500),random.randrange(100,600)))
also tried
SPRITES=[]
for Sprites in range(10):
Sprites= PlayerSprite
SPRITES.append(Sprites)
all I get are errors
screen=pygame.display.set_mode((640,480))
background1=pygame.image.load("C:\Pygame-Docs\examples\data\Random Map.bmp")
class Tree1(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image=pygame.image.load('C:\Pygame-Docs\examples\data\Tree 1.bmp')
self.image=self.image.convert()
self.rect=self.image.get_rect()
self.rect.centerx=random.randrange(10,100)
self.rect.centery=random.randrange(10,100)
# Makes a group of trees
Howmanytrees=random.randrange(5,10)
Trees=[]
for tree in range(Howmanytrees):
trees=Tree1()
Trees.append(trees)
# Howmany groups
for Treegroup in range(10):
Treegroup=Trees
# Places groups
PlayerSprite=pygame.sprite.Group(Treegroup)
# keeps loop ( game ) going until canceled
keepgoing=True
while keepgoing:
for event in pygame.event.get():
if event.type==pygame.QUIT:
keepgoing=False
# actually draws screen
screen.blit(background1,(0,0))
PlayerSprite.draw(screen)
pygame.display.flip()
This code only displays 5 to 10 trees " Trees=[] "
and nothing else. I have worked on this problem for over a week , read many tutorials, looked on many websites, nothing seems to work. I must be overlooking or missing somethig. I thought this would be easy!
Thanks so much!
As far as I understand what you want to achieve, the below code should help you. I kept it very very simple regarding python syntax, as you seems to be a newbie (for experienced programmers: yes, what I wrote below is horrible python code, but I believe the OP can understand it and it may help).
The key is that if you want to get several groups of trees, you must have a loop within a loop. The inner loop put the trees inside the group, and the outer loop put several groups. Of course you can (and should) certainly hide the inner loop behind some function or class.
# Howmany groups ? say 3
trees_groups = []
number_of_groups = 3
for _ in range(number_of_groups):
# Choose a base position for my group of trees
base_x = random.randrange(0,530)
base_y = random.randrange(0,370)
# Makes a group of trees
trees=[]
number_of_trees = random.randrange(5,10)
for _ in range(number_of_trees):
one_tree = Tree1()
trees.append(one_tree)
for tree in trees:
tree.rect.centerx += base_x
tree.rect.centery += base_y
trees_groups.append(tree)
# Places groups
PlayerSprite=pygame.sprite.Group(trees_groups)
Some after notes:
And as other posters said, you should not use capitalized variables as you do. The python usage is to keep them for classes
Also looping using range when the base variant is not used is bad practice. I emphasized this by using underline as a variable name for the loop variant when it is not used.
I would use randint and move_ip to get what you want. Here is a code snippet from my own game that works just as well:
self.rect.move_ip(random.randint(minX, maxX), random.randint(minY, maxY))
the four variables minX, maxX, minY, maxY form a rectangle where the sprite can be placed. In your case, the trees would be placed along the entire screen, but with a reduced max X and Y range so trees won't clip through the bottom of the screen.
Also, use a Group class to store your trees rather than a list. A list stops the spawning of multiple trees, while a Group does. Here is how to call it:
Treegroup = pygame.sprite.Group
and to add a sprite to the group:
Treegroup.add(Tree1(screen))
Make sure the class itself has screen in its init, like so:
def __init__(self, screen)
Once that's done, your code should look something like this:
for Treegroup in range(10):
Treegroup.add(Tree(screen))
[...]
class Tree(pygame.sprite.Sprite):
def __init__(self, screen):
pygame.sprite.Sprite.__init__(self)
self.image, self.rect = load_image('tree.png', -1)
self.rect.move_ip(random.randint(0, 800), random.randint(0, 600))
self.area = screen.get_rect()
It doesn't really make much sense to me.
for tree in range(Howmanytrees):
trees=Tree1()
Trees.append(trees)
Your loop here is doing nothing at all. tree should be a number between 0 and Howmanytrees. Again, the following block isn't indented so it's not part of the loop. Even so, the block still wouldn't work. Also, you're confusing yourself and us with variable names. Trees is the object? trees is the list? Don't do this. Seriously.
No idea what the following code is up to.
# Howmany groups
for Treegroup in range(10):
Treegroup=Trees
Create your SpriteGroup passing in the aforementioned trees list? Or am I missing something :) TreeGroup = Trees 10 times is just going to do that 10 times. You are not using the loop variant. The only thing that modifies during the loop. Even so, this entire block of code is useless.
while keepgoing:
for event in pygame.event.get():
if event.type==pygame.QUIT:
keepgoing=False
This is going to cause a nice infinite loop. It is evaluating the keepgoing variable constantly. This will never get set to false unless the user quits, but also it will never display anything on the screen. Lose this. Use this instead:
for event in pygame.event.get():
if event.type == QUIT:
return
This will not cause an infinite loop as there are only so many events to be processed per tick. Then the program will loop around and do the process or updating, rendering and getting input again.
I'm currently making a function using pygame that draws a message on the screen, adding one character each frame (i.e. The Hunt for Red October). I know that I could simply copy (or pass) gradually bigger slices from the original string, but I know that it would be very resource-intensive. Is there a better way to do this?
Code, using gradually bigger slices:
def full_screen_dialog_tt(thesurface, thefont, theclock, message, thebeep):
i = 0
while(i < len(message)): # Initialize the string display
theclock.tick(60)
thesurface.fill((0, 0, 0))
thesurface.blit(thefont.render(message[i]+"_"))
pygame.display.flip()
thebeep.play()
while(1): # Whole string is here now
theclock.tick(60)
for event in pygame.events.get():
if event.type == MOUSEBUTTONDOWN: return
In a place where you are intentionally slowing down the game (for the text fade-in) - does it really matter? You could pass the whole string, and change the display routine to display one more letter in every frame.
Can't you just print the characters one at a time displaced without clearing the background? You can get the character using slicing.
Assuming you're having a screen object available, this might work:
import time
text = 'The Hunt for Red October'
myfont = pygame.font.SysFont("arial", 16)
for index, ch in enumerate(text):
letter = myfont.render(ch, True, (0, 0, 0), (255, 255, 255))
screen.blit(letter, (index * 20, 20))
time.sleep(1)
You can access a single character from a string using indexes:
>>> s = 'string'
>>> s[2]
'r'