How do I move my Rect that has text across the screen? - python

I'm working on a project where I want to move this text box that I made from the left to the right. I have multiple of these text boxes, and I wanted to do that right after one another.
text1_rect = text1.get_rect(center = (window_width/2, window_height*.25)) : This is the current location of the text, but I was looking at the rect.move method to do this.
window.blit(text1, text1_rect) : This is how I blit the text onto the screen when the program first starts up.
I don't require a direct answer, but maybe just some tips or a nudge in the right direction on how to add a little movement to my text box.

Create text1_rect before the application loop and change the position of the rectangle in the application loop
If you want to move the text form from the left to the center of the window, you need to set the start position outside the window and move the text until it is in the center.
Use pygame.time.Clock to control the frames per second and thus the game speed.
The method tick() of a pygame.time.Clock object, delays the game in that way, that every iteration of the loop consumes the same period of time. See pygame.time.Clock.tick():
This method should be called once per frame.
That means that the loop:
clock = pygame.time.Clock()
run = True
while run:
clock.tick(60)
runs 60 times per second.
clock = pygame.time.Clock()
# initial position is outside the window
text1_rect = text1.get_rect(midright = (0, window_height*.25))
# [...]
while run:
clock.tick(60)
# [...]
window.blit(text1, text1_rect)
# move the text to the center
if text1_rect.centerx < window_width // 2:
text1_rect.x += 1
# [...]

Related

Get any objects (Rect, Surfaces...) at position / coordinates X, Y

So the question is simple:
Given a Surface, let's call it screen and x,y coordinates, can I get anything that lays at that coordinates on that Surface?
For example, let's say we have typical, Player attack, and if the attack reach the Enemy position x,y then enemy dies.
So given this simple app (is an example only not a real app)
import pygame as pg
from pygame.math import Vector2
# pygame constants
CLOCK = pg.time.Clock()
WIN_SIZE = (1280, 640)
# pygame setup
pg.init()
# screen
window = pg.display.set_mode(WIN_SIZE, 0, 32)
background = pg.Surface(WIN_SIZE)
player = pg.Surface(Vector2(12, 64))
player_rect = player.get_rect(topleft=Vector2(150, 150))
player_attack = False
player.fill((102, 255, 178))
player_attack_range = 20 # player can hit at min 20 pixel from target
enemy = pg.Surface(Vector2(12, 64))
enemy_rect = player.get_rect(topleft=Vector2(175, 150))
enemy.fill(pg.Color("green"))
while True:
background.fill((0, 0, 0)) # screen clear
# Render enemy
attacked = False
if player_attack:
# !!!!! HERE !!!!!
# Now we check if the playuer is close enough to the enemy, so we MUST know the enemy pos
distance_x = abs(player_rect.x - enemy_rect.x)
if distance_x > player_attack_range:
attacked = True
enemy.fill(pg.Color("red"))
if not attacked:
enemy.fill(pg.Color("green"))
background.blit(enemy, enemy_rect.topleft)
# Render player
background.blit(player, player_rect.topleft)
# Events
for event in pg.event.get():
if event.type == pg.QUIT or (
event.type == pg.KEYDOWN and event.key == pg.K_ESCAPE): # x button and esc terminates the game!
exit(1)
# ............. Mouse ............. #
if event.type == pg.MOUSEBUTTONDOWN:
if event.button == 1:
player_attack = True
if event.type == pg.MOUSEBUTTONUP:
if event.button == 1:
player_attack = False
pg.display.update() # 2) Update the game
window.blit(background, (0, 0)) # 3) Repaint the screen
CLOCK.tick(60) # 4) Wait 60 Frames
When is attacked
Now I always seen it done this way more or less:
distance_x = abs(player_rect.x - enemy_rect.x)
if distance_x > player_attack_range:
attacked = True
enemy.fill(pg.Color("red"))
With this example, I'm not pointing out the code implementation but the fact that, the player must know the target position and then check whether or not the target is hit
But what I want to know, let's say I don't know the enemy position, and the player just attacks, is there a way that we can get what's currently on the surface at the attack range?
So do something like
attacked_area_x = abs(player_rect.x + player_attack_range) # only care of x coords
rects_or_surfaces_in_area = background.what_have_we_got_here(Vector(attacked_area, 0))
for r in rects_or_surfaces_in_area:
print("Hit!")
Update
So By checking MDN documentation of Game Development MDN I actually find a game algorithm / Technique that is similar (but concept is the same) of my solution.
Is called the Broad Phase
From the documentation:
road phase should give you a list of entities that could be colliding. This can be implemented with a spacial data structure that will give you a rough idea of where the entity exists and what exist around it. Some examples of spacial data structures are Quad Trees, R-Trees or a Spacial Hashmap.
So yes, it seems one of many good approach to solve this problem.
So, after some research and thanks to Rabbid76 and his answer here How do I detect collision in pygame? which covers in details the most common collisions in Pygame, it seems that what I was looking for natively is just not possible.
Maybe is normal, I'm also new to game development and maybe what I want to do just doesn't make any sense, but I bet it does.
The scenario I'm facing is, just one player with a sword hitting, so I asked my self, why should I need to know prior what objects lie on the sword path and check if is hit, instead, do the hit and request to the parent screen "what object are in the sword path"? , which, is for sure faster because we don't need to know what object that have collision are (and avoid a for loop and check for each react/surface).
So, let's say there are many many object that have collision and a player may hit it, it would be way faster do don't know what' there but request it instead to the surface, so basically the surface should be aware of its children / objects.
I tried a bit the Surface.subsurface() and the Surface.get_parent() but couldn't make it work, anyway still, in a surface area we may have many thinks like:
draws, Rect, Surfaces, Sprites, Imgs etc...
I have only 2 solutions in my mind:
Map the objects coordinates
This only really works if, the entities are static, if so, then we could create a dict with key x:y coordinates and then check if the sword is in within a certain x:y and exist in the dict, then the entity is hit.
But with entity then moves by themself, is a nigtmare and will have a worst problem than before, you would need to update the keys at each frame and so on..
Make it 'distance' sensible
So, this could apply to each entity that is moving and may or may not hit something that has a collision. But staying on the example context, let's say we are iterating thourgh entities / object that at each frame are updating their position.
We could check how distant are from the player, let's say 2 chunks away (or the sword lenght) and collect them in a list
Then we that list, we check if, once the sword is updated, is hitting anything close by.
Still, pretty sure there are better ways (without changing or extending pygame its self).
And maybe, by extending pygame, there may be a way to implement a 'surface aware' class that when we request a specific Rect area, it tells us what's there.

Is there a more accurate clock for pygame for a rhythm game I'm making

I'm trying to get a series of lines to fall through the screen at exact tempo, for example, you input say 120 BPM and the result is the road lines hitting the bottom of the screen at 120 BPM.
I have tried using both pygame.clock.tick() and pygame.time.delay() (which I heard is more accurate), however when I use these as a clock to blit both the background and the road lines, against a metronome the clock seems very inconsistent.
For making an exact rhythm game which must stay in time for the entire song, is there another way to do this?
#GAMELOOP
while playing==True:
win.blit(bg,(0,0))
for event in pygame.event.get():
if event.type == pygame.QUIT:
playing = False
#win.fill(WHITE)
y1=y1+gameSpeed
y2=y2+gameSpeed
y3=y3+gameSpeed
win.blit(track1,(x,y1))
win.blit(track2,(x,y2))
win.blit(track3,(x,y3))
if y1>=1000:
y1=-2000
if y2>=1000:
y2=-2000
if y3>=1000:
y3=-2000
fpsClock.tick(30)
pygame.display.update()
pygame.quit()
120 BPM (which I assume is "Beats Per Minute"), is only a per half-second timing, that's reasonably slow in computer terms. Most applications repaint the screen at 3600 frame-updates per minute (60Hz).
You can use the pygame.time object to return the number of milliseconds since the program started. This is really useful for timing things in the game:
clock = pygame.time.get_ticks() # time now
So immediately on start-up, this time is "0 milliseconds", and grows from there, forever more.
This value can then be used in relative time comparisons. When you make the track1 fall, it's possible to update the y-position based on the clock. One way to do this is to calculate the future millisecond-time at which the track1 should next move:
clock = pygame.time.get_ticks()
track1_move_next_time = clock + 500 # 500 milliseconds = 120 BPM
The repeatedly check that time until it's in the past. Then move the track1:
# In the Main Loop:
clock = pygame.time.get_ticks()
# ...
if ( clock > track1_move_next_time ): # is it time to move?
y1 += pixels_per_tick # move it
track1_move_next_time = clock + 500 # set next move time

Need help loading images using a for loop in PyGame [duplicate]

This question already has answers here:
What would be the output of range(x, y) if x > y?
(3 answers)
Closed last year.
I'm trying to load images in PyGame based on a value, like when the value is 6 it sets the image to be image number 6.
def bar():
global ink
global screen
global barImg
ink = 0
for ink in range(0,100):
barImg = pygame.image.load(f'inkbar\load{ink}.png')
screen.blit(barImg,(100,100))
pygame.display.update()
The value of ink gets changed in another function and I know that part works. Each image is called load0.png, load1.png and so on until 100, but the image never appears on the screen. I have tested putting the image on the screen by commenting out the for loop and just setting barImg to a specific image and it did put the image on the screen.
px, py = pygame.mouse.get_pos()
if pygame.mouse.get_pressed() == (1,0,0):
pygame.draw.rect(screen, (255,255,255), (px,py,10,10))
ink+=0.5
math.ceil(ink)
print(ink)
this is part of a function that allows the user to draw. This part detects mouse click and increases the value of ink. I tried calling bar() underneath the ink increase, but that decreased the rate of drawing.
I have removed the function bar()
ink+=1
math.ceil(ink)
print(ink)
for ink in range(1,100):
barImg = pygame.image.load(f'inkbar\load{ink}.png')
screen.blit(barImg,(100,100))
This is what I have used as a replacement, but now ink does not increase by one, it goes from 1 to 100 immediately, and causes large amounts of lag.
Maybe the images having "load" in the name is messing with something?
I have some code for running through frames of an animation which I know works
The code:
frame_index += animation_speed
if frame_index >= len(animation):
self.frame_index = 0
image = pygame.image.load(f'Ink ({int(frame_index)}).png')
screen.blit(image, (0,0))
pygame.display.update()
Essentially you want to have two variables, a frame index and an animation speed. The index is the number of the first image you are loading. In this case Ink 0.png or whatever it's called. So your frame index will be 0. This will increment by your animation speed variable. The higher this is, the faster your animation will be. The lower, the slower. After it loops it will go back to 0 if thats what you want. If you don't want that then you can simply remove the if statement.
Also check that you aren't filling the screen AFTER doing this as whatever you're wanting to see will just be covered instantly. Let me know if this works.

How to make scrolling text, without inhibiting the functionality of a game? Pygame

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

Blinking endscreen - pygame

So Im making a game and at the end of 60 seconds I want to make the screen change colors and display some end text.
I set up the timer like this:
time = 60
TICKTOCK = 0
pygame.time.set_timer (TICKTOCK+1, 1000)
and that's being displayed onscreen just fine, but when the end screen appear it flashes between the original white screen and the end screen. For some reason, it doesn't flash if I'm waving my mouse around the screen.
if time <= 0:
playground.fill(black)
playground.blit(end, (0, 100))
"end" is my game over text variable
pygame.display.flip()
playground.fill(white)
clock.tick (fps)
pygame.quit()
This is what I have at the end too if that helps
Is there a way I can make it appear steadily without having to change my timer?
You show not enough code so I can only suggest to do something like this
if not gameover:
playground.fill(...)
# draw normal game
else:
playground.fill(...)
# draw gameover text
playground.flip()

Categories