Fiddling around with pygame and I'm not sure what's happening.
code is just supposed to make a red box and bounce it around a gray screen. It works, but only when I quit the display.
I've checked out questions that are similar but none seem to have an answer that applies to me (might be wrong about that). Does anyone know how this code could be improved?
import pygame
from pygame.locals import *
from rect import *
##from pygame.font import *
RED = (255, 0, 0)
GRAY = (150, 150, 150)
width = 500
height = 200
pygame.init()
screen = pygame.display.set_mode((width, height))
rect = Rect(100, 50, 50, 50)
v = [2, 2]
##moving = False
running = True
while running:
for event in pygame.event.get():
if event.type == QUIT:
running = False
rect.move_ip(v)
if rect.left < 0 or rect.right > width:
v[0] *= -1
if rect.top < 0 or rect.bottom > height:
v[1] *= -1
screen.fill(GRAY)
pygame.draw.rect(screen, RED, rect)
pygame.display.flip()
pygame.quit()
It seems that the problem is the program running too fast. Since so little work is being done in each loop, the rectangle ends up moving around too quickly to clearly see what is happening.
You can restrict the speed of the program using a pygame.time.Clock object. Before the start of your loop, probably right after your screen definition, you can construct a clock.
clock = pygame.time.Clock()
Then in your main loop, as the very first step each iteration (right after while running:) you can put clock.tick(60) to restrict the loop to running 60 times per second (60fps). This makes the program run smoothly, and you get the nice bouncing rectangle!
The tick method works well, but on my system it seems to have small hitches every so often. If you also experience this or if you want to be more accurate, you can use clock.tick_busy_loop(60) instead.
Both of these tick methods work the same way: By measuring the amount of time that passed since the last call to clock.tick() and delaying a certain additional amount based on that so that the target fps will be met.
More info on Clock at Pygame docs.
I also needed to import pygame.rect instead of just import rect, but if you're not getting an import error you should be fine.
I've figured it out. Stupidly I had another file in my test directory named "rect.py", I changed the file name and my code to from pygame.rect import * and it's working fine now. Thank you Baked Potato for the help and for making me wonder where x=50, y=60, w=200, h=80 left=50, top=60, right=250, bottom=140 center=(150, 100) was coming from!
Related
How would I prevent two masks from overlapping each other when a collision is detected? I know how to detect mask collisions but I can't wrap my head around actually preventing them from colliding. I'm pretty sure the solution has to do something with mask.overlap_area, but when I try using the code provided, It doesn't seem to work at all:
example gif (the blue dot is [dx, dy] )
import pygame
import sprites
SCREEN_HEIGHT, SCREEN_WIDTH = 800, 800
running = True
pygame.init()
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
clock = pygame.time.Clock()
player = sprites.Block((100, 100))
block2 = sprites.Block((100, 100))
blocks = pygame.sprite.Group(block2)
block2.rect.topleft = 150, 150
while running:
events = pygame.event.get()
screen.fill((100, 100, 100))
for event in events:
if event.type == pygame.QUIT:
running = False
player.move(screen.get_rect())
screen.blit(player.image, player.rect)
for block in blocks:
offset = (player.rect.x - block.rect.x, player.rect.y - block.rect.y)
dx = player.mask.overlap_area(block.mask, (offset[0] + 1, offset[1])) - \
player.mask.overlap_area(block.mask, (offset[0] - 1, offset[1]))
dy = player.mask.overlap_area(block.mask, (offset[0], offset[1] + 1)) - \
player.mask.overlap_area(block.mask, (offset[0], offset[1] - 1))
screen.blit(block.image, block.rect)
print(dx, dy)
pygame.draw.circle(screen, (0, 0, 255), (dx + block.rect.x, dy + block.rect.y), 5)
clock.tick(144)
pygame.display.flip()
Do I just have the wrong idea?
I think the issue is that your program is allowing the overlap in the first place. Once they're colliding you can't do anything.
Before moving the object, check that the destination location is not already occupied by doing a "future collision" check. If there's going to be a collision, then either don't allow the movement at all, or handle it in some nicer way.
If you know the direction of movement - say the player pushed ←, and is moving left. The code can easily move the player as far left as possible, to the point just before colliding.
This way you never have to deal with objects atop each other.
It's not really clear to me what approach the program is taking. The API pygame.mask.overlap_area() returns the number of bits overlapping. The code is calculating the collision normal, not trying to prevent or undo the overlap. Maybe it can move each object by the inverse of this direction, or suchlike.
This question already has answers here:
Pygame clock and event loops
(1 answer)
Framerate affect the speed of the game
(1 answer)
Closed last year.
Here is an example code:
import pygame
import sys
pygame.init()
screen = pygame.display.set_mode((1000, 500))
clock = pygame.time.Clock()
square = pygame.Surface((350, 350))
square.fill((0, 0, 0))
x_pos = 1000
while True:
screen.fill((255, 255, 255))
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
if x_pos <= -350:
x_pos = 1000
x_pos -= 5
screen.blit(square, (x_pos, 50))
clock.tick(60)
pygame.display.update()
Questions:
1 - Does the .tick() method defines how many fps your game runs at? If not, what does it actually do?
2 - The higher the value passed in the .tick() method, the faster the square on the example goes to the left, why does that happen?
3 - Assuming that the .tick() method defines how many fps your game is going to run at, in some games like valorant, league of legends and any other game, when the fps is higher, the game only looks smoother, not faster like in the examples, is there any reason for that?
Thanks!
pygame.tick() is just fps. You can read more about it at https://www.pygame.org/docs/ref/time.html#pygame.time.Clock.tick
You can think of FPS as how many times the while loops runs every second. If you run x_pos += 1 every frame, the higher the FPS, the more times that line of code will run; In return, the more times that line of code runs, the faster the square moves.
The reason games look smoother at higher FPS is because of the refresh rate on your monitor (Hz). The refresh rate is literally how many times your monitor updates, meaning if your FPS is lower than your Hz, it will look clunky. Any FPS above your monitors refresh rate won't affect your experience in any way.
The reason other games don't get faster with higher FPS is because of delta time, which takes the time elapsed between each frame and changes the move speed based on how many milliseconds passed. (Feel free to add more/correct me in comments)
If you're curious about how to implement delta time in pygame, I recommend watching https://www.youtube.com/watch?v=XuyrHE6GIsc
This is an issue that has been bugging me for a few weeks now. Whenever I have a pygame clock variable, so for example: clock = pygame.time.clock and I limit the fps using: clock.tick(fps) the game will occasionally stutter. I have a simple example below - a window with a cube that moves from side to side.
import pygame
screen = pygame.display.set_mode((0, 0), pygame.FULLSCREEN)
width, height = screen.get_size()
rect = pygame.Rect(0, height // 2 - 50, 100, 100)
delta_x = 5
clock = pygame.time.Clock()
running = True
while running:
event = pygame.event.poll()
if event.type == pygame.QUIT:
running = False
screen.fill((0, 0, 0))
if rect.left < 0 or rect.right > width:
delta_x *= -1
pygame.draw.rect(screen, (255, 255, 255), rect)
rect.x += delta_x
pygame.display.flip()
clock.tick(60)
Video: https://www.youtube.com/watch?v=6spFoKIqVQY&ab_channel=NotAHackusator
Does anyone know how to fix this? Thanks in advance.
Setting clock.tick to 144 from 60 makes the stutter go away from my end. I'm not not sure if you want it to be limited to 60 and why though.
There actually is something that might help to improve performance (because the stuttering seams to come because of unstable performance. At least in my case it helped to disconnect displaying and computation meaning you cap the FPS using a custom FPS limiter.
Example:
# In your game-loop:
cur_ticks = pygame.time.get_ticks()
if cur_ticks - last_ticks > 1000//60: # your framerate, also known as FPS, here 60
# your draw code and display.update goes here
last_ticks = pygame.time.get_ticks()
clock.tick(60) # number of computation steps per second, should be greater equal to FPS-value
You need to initialze last_ticks before your game-loop to zero or to the current value of pygame.time.get_ticks().
For my game this improved the FPS from 40 to 50 FPS compared to the usual approach.
It might also help to increase the clock.tick-value to something higher like 120. This way the FPS is still limited to 60, but allows more computation steps.
In my case this helped to improve the number of computation steps/second to over 60 also meaning 60 FPS (for displaying). To keep the speed of game-objects "constant" then requires you to change their speeds based on the current FPS-value. The reason why this helps seams to be that tick is very "heavy".
I am creating a multiplayer game with splitted screen.
I start by drawing the first player on the left-hand side (spaceship, fire bombs, stars in the background (scrolling at half speed) and finally the background), then I update the first part of the screen, for the first player. Then I do the same things for the second player, on the other part of the screen.
But most of the images overlap thoughout the two half-screens. (see image below)
So, basically I need to update one part of the screen using pygame.display.update(), then the other part.
But the command doesn’t work, and updates the entire screen. And everything overlaps.
I've tried the following:
pygame.display.update(Rect((pos, 0), size))
pygame.display.update(Rect((pos, 0, size[0], size[1])))
pygame.display.update((pos, 0, size[0], size[1]))
pygame.display.update(pos, 0, size[0], size[1])
pygame.display.update((pos, 0), size)
But all of these are doing exactly the same thing, and they don't work as expected.
When you are using pygame.display.update() there are two kinds of an optional argument, single rect (which defaults to None) and a list of rects. If no argument is passed, it updates the entire surface area - like display.flip() does.
update(rectangle=None) -> None
update(rectangle_list) -> None
To update only specific elements, either create a list of these elements if you want to update the same group at the same time
background_rects = [star_rect, star_rect, star_rect, some_other_rect]
foreground_rects = [player_rect, enemy1_rect, enemy2_rect, bullet1_rect, bullet2_rect]
pygame.display.update(background_rects)
pygame.display.update(foreground_rects)
or call update(rect) multiple times with the individual elements:
pygame.display.update(star1_rect)
pygame.display.update(star2_rect)
pygame.display.update(star3_rect)
pygame.display.update(character_rect)
pygame.display.update(enemy_rect)
Link to the documentation: https://www.pygame.org/docs/ref/display.html#pygame.display.update
There seems to be some (probably unintended as there is nothing about it in the docs) difference between the handling of pygame 1.9.6 and the 2.0.0.dev branches - below is a MRE which works with 1.9.6, but not with the 2.0.0.dev10 version. In 1.9.6 the difference in updating the display is easily visible. I suggest you install the stable 1.9.6 version if you need this exact functionality!
In case others want to try their luck, here is the MRE with which I tested:
import pygame
import time
screen = pygame.display.set_mode((720, 480))
rect = pygame.Rect((10, 50), (32, 32))
image = pygame.Surface((32, 32))
image.fill((0,100,0))
rect2 = pygame.Rect((10, 10), (32, 32))
image2 = pygame.Surface((32, 32))
image2.fill((100,100,0))
i = 0
while True:
i += 1
for event in pygame.event.get():
if event.type == pygame.QUIT:
quit()
screen.blit(image, rect)
screen.blit(image2, rect2)
rect.x += 1 # we update the position every cycle
rect2.x += 1 # we update the position every cycle
# but update the rect on screen at different times:
if i < 10:
pygame.display.update() # both
elif i > 50 and i < 75:
pygame.display.update(rect2) # only rect2
elif i >= 100:
pygame.display.update(rect) # only rect
time.sleep(0.1)
I recently discovered the different blending modes you can apply to blitted surfaces in pygame and I wanted to see how flexible the system was. Unless I'm doing something wrong, it's apparently pretty limited (just like the rest of pygame OOOOOOOH shots fired). I wrote a simple program that draws a bunch of gradient circles using alpha and blits them all around the screen. This is the code:
import pygame
import pygame.gfxdraw
pygame.init()
import random
SCREEN = pygame.display.set_mode((800, 600))
SCREEN.fill((0, 0, 0))
def draw_square(surface, colour, x, y):
"""
Yeah it's called draw square but it actually draws a circle thing I was just too lazy
to change the name so you gotta deal with it.
"""
square = pygame.Surface((100, 100))
square.fill((0, 0, 0))
colour += (int(15/255*100), )
for i in range(25):
pygame.gfxdraw.filled_circle(square, 50, 50, i*2, colour)
# Comment the previous two lines out and uncomment the next line to see different results.
# pygame.draw.circle(square, colour[:3], (50, 50), 50)
surface.blit(square, (x - 50, y - 50), special_flags=pygame.BLEND_RGB_ADD)
running = True
while running:
for evt in pygame.event.get():
if evt.type == pygame.QUIT:
running = False
draw_square(SCREEN, (25, 255, 25), random.randint(0, 800), random.randint(0, 600))
pygame.display.update()
pygame.quit()
It seems to work when drawing a normal circle, but when drawing the circles with pygame.gfxdraw.filled_circle additive blending doesn't work. Any ideas?
EDIT: I'm using Python 3, so 15/255 evaluates properly to a float.
The issue is still with this line:
colour += (int(15/255*100), )
It should go to white initially, but the alpha is so low it will take a long time (well, it should in theory...).
Doing:
colour += (int(125/255*100), )
Makes the effect more obvious.
Result: