Loop within main loop in pygame - python-3.x

I'm fairly new to python and pygame. I'm trying to make a simple game as a practice. My problem is how do I have a loop (or many loops) inside the main game loop so that the graphic also updates inside sub loops? For example I have a button and a rectangle, if I press on the button I want the rectangle to move across the screen. Things I've tried:
Another while loop, it works but the rectangle won't "move" and just appears wherever the loop is finished
Custom event, however it either works once per frame or continuously in case of set_timer() function
Here's my code:
import pygame as pg
pg.init()
clock = pg.time.Clock()
running = True
window = pg.display.set_mode((640, 480))
window.fill((255, 255, 255))
btn = pg.Rect(0, 0, 100, 30)
rect1 = pg.Rect(0, 30, 100, 100)
while running:
clock.tick(60)
window.fill((255, 255, 255))
for e in pg.event.get():
if e.type == pg.MOUSEBUTTONDOWN:
(mouseX, mouseY) = pg.mouse.get_pos()
if(btn.collidepoint((mouseX, mouseY))):
rect1.x = rect1.x + 1
if e.type == pg.QUIT:
running = False
#end event handling
pg.draw.rect(window, (255, 0, 255), rect1, 1)
pg.draw.rect(window, (0, 255, 255), btn)
pg.display.flip()
#end main loop
pg.quit()
Any help is much appreciated

You have to implement some kind of state. Usually, you would use the Sprite class, but in your case, a simple variable would do.
Take a look at this example:
import pygame as pg
pg.init()
clock = pg.time.Clock()
running = True
window = pg.display.set_mode((640, 480))
window.fill((255, 255, 255))
btn = pg.Rect(0, 0, 100, 30)
rect1 = pg.Rect(0, 30, 100, 100)
move_it = False
move_direction = 1
while running:
clock.tick(60)
window.fill((255, 255, 255))
for e in pg.event.get():
if e.type == pg.MOUSEBUTTONDOWN:
(mouseX, mouseY) = pg.mouse.get_pos()
if(btn.collidepoint((mouseX, mouseY))):
move_it = not move_it
if e.type == pg.QUIT:
running = False
#end event handling
if move_it:
rect1.move_ip(move_direction * 5, 0)
if not window.get_rect().contains(rect1):
move_direction = move_direction * -1
rect1.move_ip(move_direction * 5, 0)
pg.draw.rect(window, (255, 0, 255), rect1, 1)
pg.draw.rect(window, (255, 0, 0) if move_it else (0, 255, 255), btn)
pg.display.flip()
#end main loop
pg.quit()
When the button is pressed, we just set the move_it flag.
Then, in the main loop, we check if this flag is set, and then move the Rect.
You should avoid creating multiple logic loops (sorry, I don't have a better word) in your game; see the problems you mentioned. Aim for one single main loop that does three things: input handling, game logic, and drawing.

Related

Pygame window is not responding after interaction with it

I need to generate circles in random place every second. Now I have this code.
import pygame as pg
from threading import Thread
import random
pg.init()
WIDTH = 600
HEIGH = 600
sc = pg.display.set_mode((WIDTH, HEIGH))
pg.display.set_caption("Bacteria")
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
GREEN = (0, 255, 0)
sc.fill(BLACK)
pg.display.flip()
clock = pg.time.Clock()
FPS = 24
class FoodGen(Thread):
def run(self):
while 1:
clock.tick(1)
pg.draw.circle(sc, WHITE, (random.randint(10, WIDTH-10), random.randint(10, WIDTH-10)), 10)
class Main(Thread):
def run(self):
running = 1
while running:
clock.tick(FPS)
for event in pg.event.get():
if event.type == pg.QUIT:
running = 0
break
pg.display.flip()
pg.quit()
t1 = FoodGen()
t1.start()
t2 = Main()
t2.start()
When is move mouse on the window it turns to waiting mode. The window is running until I try to move or close it. I've already red many information about this problem, but still can't fix it.
See How to run multiple while loops at a time in Python. If you want to control something over time in Pygame you have two options:
Use pygame.time.get_ticks() to measure time and and implement logic that controls the object depending on the time.
Use the timer event. Use pygame.time.set_timer() to repeatedly create a USEREVENT in the event queue. Change object states when the event occurs.
e.g.:
import pygame as pg
import random
WIDTH, HEIGH = 600, 600
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
FPS = 24
pg.init()
sc = pg.display.set_mode((WIDTH, HEIGH))
pg.display.set_caption("Bacteria")
clock = pg.time.Clock()
timer_interval = 100 # 0.1 seconds
timer_event_id = pg.USEREVENT + 1
pg.time.set_timer(timer_event_id, timer_interval)
circles = []
running = 1
while running:
clock.tick(FPS)
for event in pg.event.get():
if event.type == pg.QUIT:
running = 0
if event.type == timer_event_id:
circles.append((random.randint(10, WIDTH-10), random.randint(10, WIDTH-10)))
sc.fill(BLACK)
for center in circles:
pg.draw.circle(sc, WHITE, center, 10)
pg.display.flip()
pg.quit()

Make a Single Word Within a String Bold

Given the following code (from this answer: How to display text in pygame?):
pygame.font.init() # you have to call this at the start,
# if you want to use this module.
my_font = pygame.font.SysFont('Comic Sans MS', 30)
text_surface = my_font.render('Some Text', False, (0, 0, 0))
Is it possible to make the word 'Text' bold while keeping the word 'Some ' regular?
(without rendering multiple times)
No it is not possible. You must use the "bold" version of the font. "bold" isn't a flag or attribute, it's just a different font. So you need to render the word "Text" with one font and the word "Some" with another font.
You can use pygame.font.match_font() to find the path to a specific font file.
Minimal example:
import pygame
pygame.init()
window = pygame.display.set_mode((400, 200))
clock = pygame.time.Clock()
courer_regular = pygame.font.match_font("Courier", bold = False)
courer_bold = pygame.font.match_font("Courier", bold = True)
font = pygame.font.Font(courer_regular, 50)
font_b = pygame.font.Font(courer_bold, 50)
text1 = font.render("Some ", True, (255, 255, 255))
text2 = font_b.render("Text", True, (255, 255, 255))
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
window.fill(0)
window.blit(text1, (50, 75))
window.blit(text2, (50 + text1.get_width(), 75))
pygame.display.flip()
clock.tick(60)
pygame.quit()
exit()
With the pygame.freetype modle the text can be rendered with different styles like STYLE_DEFAULT and STYLE_STRONG. However, the text can only be rendered with one style at a time. So you still have to render each word separately:
import pygame
import pygame.freetype
pygame.init()
window = pygame.display.set_mode((400, 200))
clock = pygame.time.Clock()
ft_font = pygame.freetype.SysFont('Courier', 50)
text1, rect1 = ft_font.render("Some ",
fgcolor = (255, 255, 255), style = pygame.freetype.STYLE_DEFAULT)
text2, rect2 = ft_font.render("Text",
fgcolor = (255, 255, 255), style = pygame.freetype.STYLE_STRONG)
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
window.fill(0)
window.blit(text1, (50, 75))
window.blit(text2, (50 + rect1.width, 75))
pygame.display.flip()
clock.tick(60)
pygame.quit()
exit()
See also Text and font

Pygame limiting collision targets [duplicate]

I have made a list of bullets and a list of sprites using the classes below. How do I detect if a bullet collides with a sprite and then delete that sprite and the bullet?
#Define the sprite class
class Sprite:
def __init__(self,x,y, name):
self.x=x
self.y=y
self.image = pygame.image.load(name)
self.rect = self.image.get_rect()
def render(self):
window.blit(self.image, (self.x,self.y))
# Define the bullet class to create bullets
class Bullet:
def __init__(self,x,y):
self.x = x + 23
self.y = y
self.bullet = pygame.image.load("user_bullet.BMP")
self.rect = self.bullet.get_rect()
def render(self):
window.blit(self.bullet, (self.x, self.y))
In PyGame, collision detection is done using pygame.Rect objects. The Rect object offers various methods for detecting collisions between objects. Even the collision between a rectangular and circular object such as a paddle and a ball can be detected by a collision between two rectangular objects, the paddle and the bounding rectangle of the ball.
Some examples:
pygame.Rect.collidepoint:
Test if a point is inside a rectangle
repl.it/#Rabbid76/PyGame-collidepoint
import pygame
pygame.init()
window = pygame.display.set_mode((250, 250))
rect = pygame.Rect(*window.get_rect().center, 0, 0).inflate(100, 100)
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
point = pygame.mouse.get_pos()
collide = rect.collidepoint(point)
color = (255, 0, 0) if collide else (255, 255, 255)
window.fill(0)
pygame.draw.rect(window, color, rect)
pygame.display.flip()
pygame.quit()
exit()
pygame.Rect.colliderect
Test if two rectangles overlap
See also How to detect collisions between two rectangular objects or images in pygame
repl.it/#Rabbid76/PyGame-colliderect
import pygame
pygame.init()
window = pygame.display.set_mode((250, 250))
rect1 = pygame.Rect(*window.get_rect().center, 0, 0).inflate(75, 75)
rect2 = pygame.Rect(0, 0, 75, 75)
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
rect2.center = pygame.mouse.get_pos()
collide = rect1.colliderect(rect2)
color = (255, 0, 0) if collide else (255, 255, 255)
window.fill(0)
pygame.draw.rect(window, color, rect1)
pygame.draw.rect(window, (0, 255, 0), rect2, 6, 1)
pygame.display.flip()
pygame.quit()
exit()
Furthermore, pygame.Rect.collidelist and pygame.Rect.collidelistall can be used for the collision test between a rectangle and a list of rectangles. pygame.Rect.collidedict and pygame.Rect.collidedictall can be used for the collision test between a rectangle and a dictionary of rectangles.
The collision of pygame.sprite.Sprite and pygame.sprite.Group objects, can be detected by pygame.sprite.spritecollide(), pygame.sprite.groupcollide() or pygame.sprite.spritecollideany(). When using these methods, the collision detection algorithm can be specified by the collided argument:
The collided argument is a callback function used to calculate if two sprites are colliding.
Possible collided callables are collide_rect, collide_rect_ratio, collide_circle, collide_circle_ratio, collide_mask
Some examples:
pygame.sprite.spritecollide()
repl.it/#Rabbid76/PyGame-spritecollide
import pygame
pygame.init()
window = pygame.display.set_mode((250, 250))
sprite1 = pygame.sprite.Sprite()
sprite1.image = pygame.Surface((75, 75))
sprite1.image.fill((255, 0, 0))
sprite1.rect = pygame.Rect(*window.get_rect().center, 0, 0).inflate(75, 75)
sprite2 = pygame.sprite.Sprite()
sprite2.image = pygame.Surface((75, 75))
sprite2.image.fill((0, 255, 0))
sprite2.rect = pygame.Rect(*window.get_rect().center, 0, 0).inflate(75, 75)
all_group = pygame.sprite.Group([sprite2, sprite1])
test_group = pygame.sprite.Group(sprite2)
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
sprite1.rect.center = pygame.mouse.get_pos()
collide = pygame.sprite.spritecollide(sprite1, test_group, False)
window.fill(0)
all_group.draw(window)
for s in collide:
pygame.draw.rect(window, (255, 255, 255), s.rect, 5, 1)
pygame.display.flip()
pygame.quit()
exit()
For a collision with masks, see How can I make a collision mask? or Pygame mask collision
See also Collision and Intersection
pygame.sprite.spritecollide() / collide_circle
repl.it/#Rabbid76/PyGame-spritecollidecollidecircle
import pygame
pygame.init()
window = pygame.display.set_mode((250, 250))
sprite1 = pygame.sprite.Sprite()
sprite1.image = pygame.Surface((80, 80), pygame.SRCALPHA)
pygame.draw.circle(sprite1.image, (255, 0, 0), (40, 40), 40)
sprite1.rect = pygame.Rect(*window.get_rect().center, 0, 0).inflate(80, 80)
sprite1.radius = 40
sprite2 = pygame.sprite.Sprite()
sprite2.image = pygame.Surface((80, 89), pygame.SRCALPHA)
pygame.draw.circle(sprite2.image, (0, 255, 0), (40, 40), 40)
sprite2.rect = pygame.Rect(*window.get_rect().center, 0, 0).inflate(80, 80)
sprite2.radius = 40
all_group = pygame.sprite.Group([sprite2, sprite1])
test_group = pygame.sprite.Group(sprite2)
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
sprite1.rect.center = pygame.mouse.get_pos()
collide = pygame.sprite.spritecollide(sprite1, test_group, False, pygame.sprite.collide_circle)
window.fill(0)
all_group.draw(window)
for s in collide:
pygame.draw.circle(window, (255, 255, 255), s.rect.center, s.rect.width // 2, 5)
pygame.display.flip()
pygame.quit()
exit()
What does this all mean for your code?
pygame.Surface.get_rect.get_rect() returns a rectangle with the size of the Surface object, that always starts at (0, 0) since a Surface object has no position. The position of the rectangle can be specified by a keyword argument. For example, the centre of the rectangle can be specified with the keyword argument center. These keyword arguments are applied to the attributes of the pygame.Rect before it is returned (see pygame.Rect for a list of the keyword arguments).
See *Why is my collision test always returning 'true' and why is the position of the rectangle of the image always wrong (0, 0)?
You do not need the x and y attributes of Sprite and Bullet at all. Use the position of the rect attribute instead:
#Define the sprite class
class Sprite:
def __init__(self, x, y, name):
self.image = pygame.image.load(name)
self.rect = self.image.get_rect(topleft = (x, y))
def render(self):
window.blit(self.image, self.rect)
# Define the bullet class to create bullets
class Bullet:
def __init__(self, x, y):
self.bullet = pygame.image.load("user_bullet.BMP")
self.rect = self.bullet.get_rect(topleft = (x + 23, y))
def render(self):
window.blit(self.bullet, self.rect)
Use pygame.Rect.colliderect() to detect collisions between instances of Sprite and Bullet.
See How to detect collisions between two rectangular objects or images in pygame:
my_sprite = Sprite(sx, sy, name)
my_bullet = Bullet(by, by)
while True:
# [...]
if my_sprite.rect.colliderect(my_bullet.rect):
printe("hit")
From what I understand of pygame you just need to check if the two rectangles overlap using the colliderect method. One way to do it is to have a method in your Bullet class that checks for collisions:
def is_collided_with(self, sprite):
return self.rect.colliderect(sprite.rect)
Then you can call it like:
sprite = Sprite(10, 10, 'my_sprite')
bullet = Bullet(20, 10)
if bullet.is_collided_with(sprite):
print('collision!')
bullet.kill()
sprite.kill()
There is a very simple method for what you are trying to do using built in methods.
here is an example.
import pygame
import sys
class Sprite(pygame.sprite.Sprite):
def __init__(self, pos):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface([20, 20])
self.image.fill((255, 0, 0))
self.rect = self.image.get_rect()
self.rect.center = pos
def main():
pygame.init()
clock = pygame.time.Clock()
fps = 50
bg = [255, 255, 255]
size =[200, 200]
screen = pygame.display.set_mode(size)
player = Sprite([40, 50])
player.move = [pygame.K_LEFT, pygame.K_RIGHT, pygame.K_UP, pygame.K_DOWN]
player.vx = 5
player.vy = 5
wall = Sprite([100, 60])
wall_group = pygame.sprite.Group()
wall_group.add(wall)
player_group = pygame.sprite.Group()
player_group.add(player)
# I added loop for a better exit from the game
loop = 1
while loop:
for event in pygame.event.get():
if event.type == pygame.QUIT:
loop = 0
key = pygame.key.get_pressed()
for i in range(2):
if key[player.move[i]]:
player.rect.x += player.vx * [-1, 1][i]
for i in range(2):
if key[player.move[2:4][i]]:
player.rect.y += player.vy * [-1, 1][i]
screen.fill(bg)
# first parameter takes a single sprite
# second parameter takes sprite groups
# third parameter is a do kill command if true
# all group objects colliding with the first parameter object will be
# destroyed. The first parameter could be bullets and the second one
# targets although the bullet is not destroyed but can be done with
# simple trick bellow
hit = pygame.sprite.spritecollide(player, wall_group, True)
if hit:
# if collision is detected call a function in your case destroy
# bullet
player.image.fill((255, 255, 255))
player_group.draw(screen)
wall_group.draw(screen)
pygame.display.update()
clock.tick(fps)
pygame.quit()
# sys.exit
if __name__ == '__main__':
main()
Make a group for the bullets, and then add the bullets to the group.
What I would do is this:
In the class for the player:
def collideWithBullet(self):
if pygame.sprite.spritecollideany(self, 'groupName'):
print("CollideWithBullet!!")
return True
And in the main loop somewhere:
def run(self):
if self.player.collideWithBullet():
print("Game Over")
Hopefully that works for you!!!
Inside the Sprite class, try adding a self.mask attribute with
self.mask = pygame.mask.from_surface(self.image)
and a collide_mask function inside of the Sprite class with this code:
def collide_mask(self, mask):
collided = False
mask_outline = mask.outline()
self.mask_outline = self.mask.outline()
for point in range(len(mask_outline)):
mask_outline[point] = list(mask_outline[point])
mask_outline[point][0] += bullet.x
mask_outline[point][1] += bullet.y
for point in range(len(self.mask_outline)):
self.mask_outline[point] = list(mask_outline[point])
self.mask_outline[point][0] += self.x
self.mask_outline[point][1] += self.y
for point in mask_outline:
for self_mask_point in self.mask_outline:
if point = self_mask_point:
collided = True
return collided

line 475, in draw self.spritedict[spr] = surface_blit(spr.image, spr.rect)

import pygame
import random
WIDTH = 360
HEIGHT = 480
FPS = 30
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
pygame.init()
pygame.mixer.init()
screen = pygame.display.set_mode([WIDTH, HEIGHT])
pygame.display.set_caption("My Game")
clock = pygame.time.Clock()
class Player(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((10, 10))
self.image.fill(GREEN)
self.rect = self.image.get_rect()
self.rect.centerx = WIDTH / 2
self.rect = HEIGHT - 10
all_sprites = pygame.sprite.Group()
Player = Player()
all_sprites.add(Player)
running = True
while running:
clock.tick(FPS)
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
all_sprites.update()
screen.fill(BLACK)
all_sprites.draw(screen)
pygame.display.flip()
pygame.quit()
Hello I am getting this error when i am trying to run this program File , line 54, in
all_sprites.draw(screen)
line 475, in draw
self.spritedict[spr] = surface_blit(spr.image, spr.rect)
TypeError: invalid destination position for blit. Plz help me.
Your rect attribute must be an Rect object but you're overriding it with an integer in the line self.rect = HEIGHT - 10. Since a single integer isn't a valid location to blit a sprite on you get "invalid destination position for blit".

Rect error in Python 3.6.2, Pygame 1.9.3

I have recently been learning python and I just found out about sprites. They seem really useful and I have been trying to make a game where you have to eat all the red apples (healthy), and not the blue apples (mouldy). An error occurred when I tried to run it and it said:
line 32, in <module>
apples.rect.y = random.randrange(displayHeight - 20)
AttributeError: type object 'apples' has no attribute 'rect'
Sorry if I have made a really nooby error but I have been looking for an answer elsewhere and I couldn't find one. Here is my entire main code:
import pygame
import random
pygame.init()
displayWidth = 800
displayHeight = 600
black = (0, 0, 0)
white = (255, 255, 255)
red = (255, 0, 0)
green = (0, 255, 0)
blue = (0, 0, 255)
gameDisplay = pygame.display.set_mode((displayWidth, displayHeight))
gameCaption = pygame.display.set_caption("Eat The Apples")
gameClock = pygame.time.Clock()
class apples(pygame.sprite.Sprite):
def __init__(self, colour, width, height):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface([20, 10])
self.image.fill(red)
self.rect = self.image.get_rect()
applesList = pygame.sprite.Group()
allSpriteList = pygame.sprite.Group()
for i in range(50):
apple = apples(red, 20 , 20)
apples.rect.y = random.randrange(displayHeight - 20)
apples.rect.x = random.randrange(displayWidth - 20)
applesList.add(apple)
allSpriteList.add(apple)
player = apples(green, 20, 20)
def gameLoop():
gameExit = False
while not gameExit:
for event in pygame.event.get():
if event.type == pygame.QUIT:
gameExit = True
pygame.display.update()
gameClock.tick(60)
gameLoop()
pygame.quit()
quit()
Thank you for reading and I look forward to a response!
(P.S. This code isn't completely finished if you were wondering)
You're trying to change the rect attribute of the class here not the rect of the instance, and since the class doesn't have a rect, the AttributeError is raised.
apples.rect.y = random.randrange(displayHeight - 20)
apples.rect.x = random.randrange(displayWidth - 20)
Just change apples to apple (the instance) and it should work correctly.
apple.rect.y = random.randrange(displayHeight - 20)
apple.rect.x = random.randrange(displayWidth - 20)
Yes, like skrx said. you just have to single one out.
for apple in apples:
should work.

Resources