I'm new to pygame, and I'm trying to practice skill with 'Plants and Zombies', but here comes a problem when I run the code: the next bullet does not move to the edge of the screen, it ends at a point at which the previous bullet collide with a zombie. I can NOT find out where is the problem, hope someone can help me out. Just run main.py, click on a top menu(weapon), right click on any point of a grass, u'll know the problem.
project directory:
helper.py:
import os
from PIL import Image
import pygame
current_dir = os.getcwd()
def get_path(image_name):
return os.path.join(current_dir, "resource", image_name)
def get_image_size(image_name):
with Image.open(get_path(image_name)) as im:
return im.size
def load_image_source(image_name, with_bullet=False):
if with_bullet:
bullet_name = "bullet_"+image_name.replace("shooter_", "")
return pygame.image.load(get_path(image_name)), pygame.image.load(get_path(bullet_name))
else:
return pygame.image.load(get_path(image_name))
def load_all_shooters():
weapon = {"selected": [], "unselected": []}
shooter_dir = os.path.join(current_dir, "resource")
for shooter in os.listdir(shooter_dir):
if "shooter" in shooter:
if "selected" in shooter:
weapon["selected"].append(shooter)
else:
weapon["unselected"].append(shooter)
weapon["selected"] = sorted(weapon["selected"])
weapon["unselected"] = sorted(weapon["unselected"])
return weapon
def get_nearest_position(source_list, target_number):
return min(source_list, key=lambda x: abs(x - target_number))
config.py:
LINES = 5
ZOMBIE_SPEED_X = 1
ZOMBIE_REFRESH_TIME = 3000
BULLET_REFRESH_TIME = 1000
BULLET_SPEED = 3
SHOOTER_SIZE = (50, 50)
main.py:
import pygame
import sys
import random
from ZombiesVSPlants.common.helper import get_nearest_position, load_image_source, load_all_shooters, get_image_size, get_path, get_path
from ZombiesVSPlants.config.config import BULLET_SPEED, BULLET_REFRESH_TIME, ZOMBIE_REFRESH_TIME, SHOOTER_SIZE, ZOMBIE_SPEED_X, LINES
pygame.init()
# top weapon menu
menu_height = 60
# get background grass and other resource path and size...
background_grass_path = get_path("background_grass.png")
zombie_path = get_path("enemy_zombie.png", )
single_line_height = get_image_size("background_grass.png")[1]
zombie_size = get_image_size("enemy_zombie.png")
# screen size
screen_height = LINES*single_line_height+menu_height
screen_width = 800
# background colour, zombie speed
background_color = 255, 255, 255
ZOMBIE_SPEED = [-ZOMBIE_SPEED_X, 0]
# others
LINES_LIST = [i for i in range(1, LINES+1)]
zombie_start_x = screen_width-zombie_size[0]
zombie_start_y = (single_line_height-zombie_size[1])/2
shooter_centered_position__list_y = [line*single_line_height+zombie_start_y+menu_height for line in range(5)]
# resource boom
boom = load_image_source("boom.png")
# dragging and other global variables
dragging = False
mouse_follow = None
mouse_follow_rect = None
added_shooters = []
shooter_bullets = []
bullets_rect = []
collide_zombies = []
collide_bullets = []
# screen size
screen = pygame.display.set_mode((screen_width, screen_height))
# load top weapon surface
res = load_all_shooters()
menu_start_position = 5, 5
menu_shooters = [load_image_source(unselected_shooter, with_bullet=True) for unselected_shooter in res["unselected"]]
menu_shooters_selected = [load_image_source(selected_shooter) for selected_shooter in res["selected"]]
menu_shooters_rect = [pygame.Rect(menu_start_position[0]+55*i, menu_start_position[1], SHOOTER_SIZE[0], SHOOTER_SIZE[1]) for i in range(len(menu_shooters))]
# background grass surface
grass = load_image_source(background_grass_path)
grass_rect = [pygame.Rect(0, i * single_line_height+menu_height, screen_width, single_line_height) for i in range(LINES)]
# the very first zombie surface
zombie = load_image_source(zombie_path)
zombies_rect = [pygame.Rect(zombie_start_x, zombie_start_y+menu_height, zombie_size[0], zombie_size[1])]
# ZOMBIE_REFRESH_TIME
NEW_ZOMBIE_EVENT = pygame.USEREVENT+1
pygame.time.set_timer(NEW_ZOMBIE_EVENT, ZOMBIE_REFRESH_TIME)
# BULLET_REFRESH_TIME
NEW_BULLET_EVENT = pygame.USEREVENT+2
pygame.time.set_timer(NEW_BULLET_EVENT, BULLET_REFRESH_TIME)
while 1:
screen.fill(background_color)
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
# left click on a top menu(weapon), dragging is enabled
if event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
for i in range(len(menu_shooters)):
if menu_shooters_rect[i].collidepoint(event.pos):
dragging = not dragging
mouse_follow = menu_shooters[i]
# right click when dragging, new weapon will be set on a point
if event.type == pygame.MOUSEBUTTONDOWN and event.button == 3:
x = event.pos[0]
if event.pos[1] > menu_height and dragging:
dragging = not dragging
y = get_nearest_position(shooter_centered_position__list_y, event.pos[1])
added_shooters.append([mouse_follow[0], x, y])
shooter_bullets.append([x, y, mouse_follow[1]])
# spawn new bullet
if event.type == NEW_BULLET_EVENT:
for j in range(len(shooter_bullets)):
bullets_rect.append([shooter_bullets[j][2], pygame.Rect(shooter_bullets[j][0], shooter_bullets[j][1], 15, 15)])
# spawn new zombie
if event.type == NEW_ZOMBIE_EVENT:
# random in roads that new zombies will appear
new_zombies_count = random.randint(1, LINES)
new_zombies_lines = random.sample(LINES_LIST, new_zombies_count)
# add to zombies list
for line in new_zombies_lines:
new_zombie_rect = pygame.Rect(zombie_start_x, line * single_line_height + zombie_start_y + menu_height, zombie_size[0], zombie_size[1])
zombies_rect.append(new_zombie_rect)
# blit top weapons menu
for i in range(len(menu_shooters)):
menu_rect = menu_shooters_rect[i]
screen.blit(menu_shooters[i][0], menu_rect)
# blit selected weapon if mouse hover on a weapon menu
for i in range(len(menu_shooters)):
if menu_shooters_rect[i].collidepoint(pygame.mouse.get_pos()):
screen.blit(menu_shooters_selected[i], menu_shooters_rect[i])
# blit background grass
for r in grass_rect:
screen.blit(grass, r)
# blit all the weapons on the grass
for new in added_shooters:
shooter_rect = pygame.Rect(new[1], new[2], 50, 50)
screen.blit(new[0], shooter_rect)
# blit bullets
for j in range(len(bullets_rect)):
bullets_rect[j][1].x += 1
screen.blit(bullets_rect[j][0], bullets_rect[j][1])
# blit zombies
for i in range(len(zombies_rect)):
zombies_rect[i].x -= ZOMBIE_SPEED_X
screen.blit(zombie, zombies_rect[i])
# blit weapon follows mouse move position
if dragging and mouse_follow[0]:
pos_follow_mouse = pygame.mouse.get_pos()
mouse_follow_rect = pygame.Rect(pos_follow_mouse[0], pos_follow_mouse[1], 50, 50)
screen.blit(mouse_follow[0], mouse_follow_rect)
# collide between zombie and bullet
for i in range(len(bullets_rect)):
for j in range(len(zombies_rect)):
if bullets_rect[i][1].colliderect(zombies_rect[j]):
print("collide!")
screen.blit(boom, zombies_rect[j])
collide_bullets.append(bullets_rect[i])
collide_zombies.append(zombies_rect[j])
bullets_rect = [i for i in bullets_rect if i not in collide_bullets]
zombies_rect = [j for j in zombies_rect if j not in collide_zombies]
pygame.display.flip()
I now got a compromise like solution, when need to remove surface in a collide between 2 rects in 2 lists, just draw the 2 lists in 2 separate for loop and remove the collided rect in another for loop, the demo code would be:
# draw bullets
for b in bullets_rect:
b_r = b[1]
b_r.x += BULLET_SPEED
screen.blit(b[0], b_r)
# draw zombies
for z in zombies_rect:
z.x += -ZOMBIE_SPEED_X
screen.blit(zombie, z)
# when collide, use list.remove to remove collided rect
for i, j in itertools.product(bullets_rect, zombies_rect):
b_r = i[1]
z_r = j
if z_r.contains(b_r):
# when collide, remove the collided rect
screen.blit(boom, z_r)
bullets_rect.remove(i)
zombies_rect.remove(j)
FYI.
Related
I can't seem to figure out why my groups Pygame code keeps flickering when we run it. pygame.display.update is only called once, but our background seems to be potentially causing the issue.
Any an all help would be greatly appreciated! :)
Here's a link to my Replit with the code:
https://replit.com/#LukaLUSV6912/Santa-vs-Evil-Elves-Game?v=1
We are trying to get rid of the flickering from the Pygame animation and implement a winning and loosing screen based off the visibility of the elves(enemies) when the timer runs down to 0
Instructions: move with right + left arrow keys and fire snowballs with spacebar
'''"""
import pygame, random
pygame.init() # initialize pygame managers
# create a window
w = 600
h = 600
win = pygame.display.set_mode((w,h)) # define window variable
# pygame.display.set_caption("Read carefully.") # uncomment & edit to caption the window
#======================== Variables & functions ===================================================
img_size = (50,100)
img_size2 = (30,30)
# class that creates Santa
class Santa:
def __init__(self,color,x,y):
self.color = color
self.x = x
self.y = y
self.img = pygame.image.load("santaimg.png").convert_alpha()
self.img = pygame.transform.scale(self.img, img_size)
self.center=(x,y)
# The methods Right and Left allow Santa to move right and left and have boundries
def Right(self):
if self.x <551:
self.x +=20
self.center=(self.x,self.y)
# self.rect=self.img.get_rect(center=self.center)
def Left(self):
if self.x >0:
self.x -=20
self.center=(self.x,self.y)
# self.rect=self.img.get_rect(center=self.center)
# method that allows the bubbles to be drawn on the screen
def show(self):
# self.img = pygame.image.load("santaimg.png").convert()
# self.rect=self.img.get_rect(center=self.center)
win.blit(self.img, self.center)
#pygame.draw.ellipse(win,self.color,self.img)
#Class that creates the elves
class Elves():
def __init__(self, color, pos_x, pos_y):
self.color = color
self.pos_x= pos_x
self.pos_y = pos_y
self.square = 20
self.velocity = 0 # elves start stationary
self.box = pygame.Rect(self.pos_x,self.pos_y,self.square,self.square)
self.img2 = pygame.image.load("elfimg.png").convert_alpha()
self.img2 = pygame.transform.scale(self.img2,img_size2)
#self.area = pygame.Surface((self.square,self.square))
self.draw = True
# method that displays the elves
def Show(self):
if (self.draw == True):
win.blit(self.img2, (self.pos_x, self.pos_y))
# method that makes elves dissapear if snowball hits them
def dissapear(self, box):
global Dis
if (self.draw):
if self.box.colliderect(box): # box is the rect for elf
Dis += 1 # Each time user hits elf with snowball, value of Dis goes up one
self.draw = False
if (Dis == 20): # New victory image shows once 20 collisions occur
print("Victory! You Saved Christmas!")
global BG
BG = False # Makes it so background image stops refreshing
global SUp
SUp = False #Snowballs stop moving
gameDisplay = pygame.display.set_mode((w,h)) # Background Code - https://stackoverflow.com/questions/65009141/in-pygame-how-do-i-make-an-image-the-background#:~:text=Load%20a%20back%20ground%20image%20%28e.g.%20%27background.jpg%27%29%2C%20scale,pygame.transform.smoothscale%20%28background%2C%20gameDisplay.get_size%20%28%29%29%20gameDisplay.blit%20%28background%2C%20%280%2C%200%29%29
background = pygame.image.load('Victory.png').convert() #Retrieve the image
background = pygame.transform.smoothscale(background, gameDisplay.get_size()) # Sets the image size to the window size
gameDisplay.blit(background, (0, 0)) # Draws the image onto the screen
global running
running = False
# allows the elves to move up and down continuously
def Moving (self):
self.pos_y += self.velocity # Elves move based on velocity value
if self.pos_y >= 0 and self.pos_y < h/2.4: # Elves accelerate downwards when in top half of screen
self.velocity +=.3 # Velocity goes up, pos_y goes down
if self.pos_y > h/2.4: # Elves accelerate upwards when in bottom half of screen
self.velocity -= .3 # Velocity goes down, pos_y goes up
self.box = pygame.Rect(self.pos_x,self.pos_y,self.square,self.square)
#Class that creates snowballs
class Snow:
def __init__(self,color,x,y):
self.x = x
self.y = y
self.color = color
self.height = 15
self.length = 15
self.draw = True
self.snowy = pygame.draw.ellipse(win,self.color,(self.x,self.y,self.height,self.length))
# Show snowballs on screen
def show(self):
if (self.draw == True):
self.snowy = pygame.draw.ellipse(win,self.color,self.snowy)
#Snowballs go up
def upward(self):
global SUp
if SUp == True:
self.snowy.y -= 5
self.snowy = pygame.draw.ellipse(win,self.color,self.snowy)
# class that creates a timer
class Timer:
def __init__(self,limit):
self.limit = limit
self.clock = pygame.time.Clock()
self.start=pygame.time.get_ticks() # get starter tick at Timer instantiation
self.timeLeft = self.countDown()
def countDown(self):
'''Note: Will count down based on time passed, not frame rate, or call frequency.
Retuurns whole value time in seconds (int)'''
currTime=(self.start + pygame.time.get_ticks())/1500 #calculate how many seconds
# if self.timeLeft > 0:
return int(self.limit - currTime) # returns whole second numbers
#================================ Animation loop ===================================================
class Manager:
def __init__(self):
self.colors = ((255,255,255),(0,0,0),(173,216,230),(252,36,2),(0,0,255))
self.ElvesList = []
self.snowballs=[]
global SUp
SUp = True
global BG
BG = True
global Dis
Dis = 0
def main(self):
global running
running = True
clock = pygame.time.Clock()
# code that allows elves to show, everytime you play the game the elves have a different x and y
for i in range(20):
x = random.randint(0,w-20)
y= random.randint(0,h/3)
self.ElvesList.append(Elves(self.colors[2],x,y)) # instantiates the elves class
# calls Santa class in a variable called "SantaC"
SantaC= Santa(self.colors[3],w/2,h-50)
while running:
# clear window (comment out to have trace behind animation)
if (BG == True): # Refresh of image can be turned on/off
gameDisplay = pygame.display.set_mode((w,h)) # Background Code - https://stackoverflow.com/questions/65009141/in-pygame-how-do-i-make-an-image-the-background#:~:text=Load%20a%20back%20ground%20image%20%28e.g.%20%27background.jpg%27%29%2C%20scale,pygame.transform.smoothscale%20%28background%2C%20gameDisplay.get_size%20%28%29%29%20gameDisplay.blit%20%28background%2C%20%280%2C%200%29%29
background = pygame.image.load('SnowBackground.webp').convert() #Retrieve the image
background = pygame.transform.smoothscale(background, gameDisplay.get_size()) # Sets the image size to the window size
gameDisplay.blit(background, (0, 0)) # Draws the image onto the screen
# for every elf created in line 128 they will all show and move, methods of the elves are being called
for evil in self.ElvesList:
evil.Show()
evil.Moving()
# method that allows Santa to be shown
SantaC.show()
#borrowed code in order to display score in left corner (line 141-152)
font = pygame.font.Font("freesansbold.ttf",32)
textx = 10
texty = 10
timer = Timer(27) # 27 is used because there is a 2 second delay to run the code
timeLeft =timer.countDown()
# function that displays timer
def showtimer(x,y):
timing = font.render("Timer :" +str(timeLeft),True,(self.colors[4]))
win.blit(timing,(x,y))
showtimer(textx,texty) # determines where the timer will be displayed
# when the game reaches 0 seconds on the timer, "gameover" is displayed to the terminal
if timeLeft<=0:
gameDisplay = pygame.display.set_mode((w,h)) # Background Code - https://stackoverflow.com/questions/65009141/in-pygame-how-do-i-make-an-image-the-background#:~:text=Load%20a%20back%20ground%20image%20%28e.g.%20%27background.jpg%27%29%2C%20scale,pygame.transform.smoothscale%20%28background%2C%20gameDisplay.get_size%20%28%29%29%20gameDisplay.blit%20%28background%2C%20%280%2C%200%29%29
background = pygame.image.load('Defeat.png').convert() #Retrieve the image
background = pygame.transform.smoothscale(background, gameDisplay.get_size()) # Sets the image size to the window size
gameDisplay.blit(background, (0, 0)) # Draws the image onto the screen
for s in self.snowballs:
s.show()
s.upward()
# Sees if the rect of snowball and rect of elves has collided, if so the elf dissapears
for evil in self.ElvesList:
for s in self.snowballs:
evil.dissapear(s.snowy)
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False # stops animation
if event.type == pygame.KEYDOWN:
# handles key press event
if event.key == pygame.K_SPACE:
# When the spacebar is pressed, a snowball will be shot out of Santas location
self.snowballs.append(Snow(self.colors[0],17.5+ SantaC.x,SantaC.y))
if event.key == pygame.K_RIGHT or event.key == pygame.K_d: # santa moves right if right key is clicked. Calls method from Santa's class.
SantaC.Right()
if event.key == pygame.K_LEFT or event.key == pygame.K_a: # santa moves left if left key is clicked. Calls method from Santa's class.
SantaC.Left()
# snowballs will be shown and move upward
#================== Animation control ===================
pygame.display.update()
clock.tick(30) # framerate in fps (30-60 is typical)
Manager().main()
The code is doing a lot of image and font loading repeatedly in the main loop. It's just re-loading the same thing, so this should be moved to before the loop. But I suspect the flickering is caused by the repeated calls to pygame.display.set_mode((w,h)) which is typically only called once, or when handling a window-resize event.
So maybe you want something like this for your main loop:
def showtimer(x,y,font,timeLeft,win):
timing = font.render("Timer :" +str(timeLeft),True,(self.colors[4]))
win.blit(timing,(x,y))
# Set the window size, and setup the timer
gameDisplay = pygame.display.set_mode((w,h))
timer = Timer(27) # 27 because there is a 2 second delay to run the code
timer_x = 10
timer_y = 10
# Load & scale any images, fonts, etc.
font = pygame.font.Font("freesansbold.ttf",32)
snowBackground = pygame.image.load('SnowBackground.webp').convert()
snowBackground = pygame.transform.smoothscale( snowBackground, gameDisplay.get_size())
defBackground = pygame.image.load('Defeat.png').convert()
defBackground = pygame.transform.smoothscale( defBackground, gameDisplay.get_size())
victBackground = pygame.image.load('Victory.png').convert()
victBackground = pygame.transform.smoothscale( victBackground, gameDisplay.get_size())
while running:
timeLeft = timer.countDown()
# clear window (comment out to have trace behind animation)
if (BG == True): # Refresh of image can be turned on/off
# when the game reaches 0 seconds on the timer, "gameover" is displayed to the terminal. Otherwise it's a snowy background
if Dis >= 20:
background = victBackground # player won!
elif timeLeft<=0:
background = defBackground # player defeated
else:
background = snowBackground # still playing
gameDisplay.blit( background, (0, 0) )
# paint & update Evil Elves
for evil in self.ElvesList:
evil.Show()
evil.Moving()
# method that allows Santa to be shown
SantaC.show()
# paint the timer
showtimer( timer_x, timer_y, font, timeLeft, win )
# flush the updates to the window
pygame.display.update()
# handle events & user input
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False # stops animation
# ... etc.
clock.tick( 60 )
I'm not 100% sure how the timer system works, so I just re-arranged it to how I guess it functions. Errors and Omissions should be expected.
The upshot of the changes above is that anything that can be pre-calculated is done outside of the loop. There absolutely no need to re-load and re-scale a background image to the window size every update loop. If you do need to handle window re-size events, do that in an event handler.
I have been working on a pygame project where a player controls a rectangle that hits a ball. When the ball hit the side walls, it bounces off perfectly, same with the floor, but when it hits the top, it works very weirdly in a way I can't describe. If anyone wants to test this, my code is below, just hit the ball onto the roof and it will show what I am trying to explain. I would like it so you can't ram the ball into the ceiling so it goes off the screen and for it to be a clean bounce instead of what it dose right now and sort of rolls down if it touches the ceiling.
import pygame as pg
from pygame.math import Vector2
pg.init()
LIGHTBLUE = pg.Color('lightskyblue2')
DARKBLUE = pg.Color(11, 8, 69)
screen = pg.display.set_mode((800, 600))
width, height = screen.get_size()
clock = pg.time.Clock()
# You need surfaces with an alpha channel for the masks.
bluecar = pg.Surface((60, 30), pg.SRCALPHA)
bluecar.fill((0,0,255))
BALL = pg.Surface((30, 30), pg.SRCALPHA)
pg.draw.circle(BALL, [0,0,0], [15, 15], 15)
ball_pos = Vector2(395, 15)
ballrect = BALL.get_rect(center=ball_pos)
ball_vel = Vector2(0, 0)
mask_blue = pg.mask.from_surface(bluecar)
mask_ball = pg.mask.from_surface(BALL)
pos_blue = Vector2(740, 500) # Just use the pos vector instead of x, y.
bluerect = bluecar.get_rect(center = pos_blue)
vel_blue = Vector2(0, 0) # Replace x_change, y_change with vel_blue.
# A constant value that you add to the y-velocity each frame.
GRAVITY = .5
on_ground = False
ground_y = height - 100
done = False
while not done:
for event in pg.event.get():
if event.type == pg.QUIT:
done = True
elif event.type == pg.KEYDOWN:
if event.key == pg.K_a:
vel_blue.x = -5
elif event.key == pg.K_d:
vel_blue.x = 5
elif event.key == pg.K_w:
#if on_ground: # Only jump if the player is on_ground.
vel_blue.y = -12
on_ground = False
elif event.type == pg.KEYUP:
if event.key == pg.K_a and vel_blue.x < 0:
vel_blue.x = 0
elif event.key == pg.K_d and vel_blue.x > 0:
vel_blue.x = 0
ball_vel.y += GRAVITY # Accelerate downwards.
ball_pos += ball_vel # Move the ball.
ballrect.center = ball_pos # Update the rect.
# Bounce when the ball touches the bottom of the screen.
if ballrect.bottom >= ground_y:
# Just invert the y-velocity to bounce.
ball_vel.y *= -0.7 # Change this value to adjust the elasticity.
ball_vel.x *= .95 # Friction
# Don't go below the ground.
ballrect.bottom = ground_y
ball_pos.y = ballrect.centery
# Left and right wall collisions.
if ballrect.left < 0:
ball_vel.x *= -1
ballrect.left = 0
ball_pos.x = ballrect.centerx
elif ballrect.right > width:
ball_vel.x *= -1
ballrect.right = width
ball_pos.x = ballrect.centerx
if ballrect.top <= 0:
# Just invert the y-velocity to bounce.
ball_vel.y *= 0.4 # Change this value to adjust the elasticity.
# Add the GRAVITY value to vel_blue.y, so that
# the object moves faster each frame.
vel_blue.y += GRAVITY
pos_blue += vel_blue
bluerect.center = pos_blue # You have to update the rect as well.
# Stop the object when it's near the bottom of the screen.
if bluerect.bottom >= ground_y:
bluerect.bottom = ground_y
pos_blue.y = bluerect.centery
vel_blue.y = 0
on_ground = True
if bluerect.x < 0:
bluerect.x = 0
pos_blue.x = bluerect.centerx
elif bluerect.right > width:
bluerect.right = width
pos_blue.x = bluerect.centerx
offset_blue = bluerect[0] - ballrect[0], bluerect[1] - ballrect[1]
overlap_blue = mask_ball.overlap(mask_blue, offset_blue)
if overlap_blue: # Blue collides with the ball.
if vel_blue.x != 0: # Player is moving.
ball_vel = Vector2(vel_blue.x, -17)
else: # If the player is standing, I just update the vel.y.
ball_vel.y = -17
# Draw everything.
screen.fill(LIGHTBLUE)
pg.draw.line(screen, (0, 0, 0), (0, ground_y), (width, ground_y))
screen.blit(bluecar, bluerect) # Blit it at the rect.
screen.blit(BALL, ballrect)
pg.display.update()
clock.tick(60)
pg.quit()
Just set the ballrect.top coordinate to 1, so that it stays in the game area and also update the ball_pos.y afterwards.
if ballrect.top <= 0:
# Just invert the y-velocity to bounce.
ball_vel.y *= -0.4 # Change this value to adjust the elasticity.
ballrect.top = 1
ball_pos.y = ballrect.centery
I'm making a CheckButton Widget to be able to use in game menus.
Wondering how I might be able to check if the mouse is colliding with the circle?
Using the self.surface.get_rect() method doesn't seem to work. is there a way to calculate where the circle is based on its location in its surface object? I was just going to just draw a smaller black circle inside the circle when self.active == True then back to its default color if its False. should I be using Sprites for this?
class CheckButton():
"""add label?"""
def __init__(self, screen, pos,size=(10,10),color=GREY):
self.screen = screen
self.pos = pos
self.size = size
self.color = color
self.active = False
self.surface = pygame.surface.Surface(self.size)
self.rect = self.surface.get_rect()
self.center = (5,5)
def check_for_click(self):
pos = pygame.mouse.get_pos()
mouseClicked = pygame.mouse.get_pressed()
if self.rect.collidepoint(pos) and mouseClicked == (1,0,0):
self.active = True
print(self.active)
def draw(self):
self.surface.fill(BG_COLOR)
pygame.draw.circle(self.surface,self.color, self.center,5, 0)
self.screen.blit(self.surface, self.pos)
When using pygame, don't give your objects attributes like pos or center (if you don't have to for whatever reasons). Just use the pygame's Sprite and Rect classes, which will handle all these things for you.
Here's a running example. Note the comments for further explanations:
import pygame
from math import hypot
pygame.init()
screen = pygame.display.set_mode((400, 400))
BG_COLOR=(55,0,200)
# note that pygame comes with a lot of colors already defined in THECOLORS
GREY=pygame.color.THECOLORS['grey']
# pygame's Group is great for sprite handling, but it does not offer a function for event handling
# so we create our own simple Group
class EventAwareGroup(pygame.sprite.Group):
def handle(self, event):
for spr in self.sprites():
if hasattr(spr, 'handle'):
spr.handle(event)
class CheckButton(pygame.sprite.Sprite):
def __init__(self, pos, size=(10,10), color=(255,100,200)):
super().__init__()
self.image = pygame.surface.Surface(size)
# we use a random color as colorkey, which means this color acts
# as a substitute for 'transparent'; so we don't have to care about the
# actual background
self.image.set_colorkey((99,32,127))
self.image.fill((99,32,127))
self.rect = self.image.get_rect()
# our image is a simple circle
# note how we can use the attributes of Rect to easily find the center of our Surface
pygame.draw.circle(self.image, color, self.rect.center, size[0]//2, 0)
# when the checkbox is active, we want to show another image, so let's create it here
# we want to do the drawing once, so we do it in the __init__ function
self.toggle_image = self.image.copy()
pygame.draw.circle(self.toggle_image, (0, 0, 0), self.rect.center, size[0]//3, 0)
# now let's position our checkbox at 'pos'
self.rect.center = pos
self.active = False
def handle(self, event):
# since we want to toggle the active state of the checkbox when a mouse click occurs,
# it's better to listen for the MOUSEBUTTONDOWN event
if event.type == pygame.MOUSEBUTTONDOWN:
# to check if the mouse click was actually in the circle, we simple calculate the distance
d = hypot(event.pos[0] - self.rect.center[0], event.pos[1] - self.rect.center[1])
if d <= self.rect.width/2 and event.button == 1:
# now let's toggle the active flag and the images
self.active = not self.active
self.image, self.toggle_image = self.toggle_image, self.image
c = CheckButton([150, 100], [100, 100])
g = EventAwareGroup(c)
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
g.handle(event)
# note how simple and clean our main loop is
# when we need other sprites, just add them to the g Group
# no need to change the main loop for that
screen.fill(BG_COLOR)
g.update()
g.draw(screen)
pygame.display.update()
This question already has answers here:
How to turn the sprite in pygame while moving with the keys
(1 answer)
Image rotation while moving
(2 answers)
Closed 2 years ago.
Im trying to move a sprite in the direction of an angle in pygame, using the left & right arrow keys to change the direction of the sprite but when I press the up key, the sprite just moves right. I have 2 variables that take speed and a velocity calculation and adds them together (vel_x & vel_y), I then add this to the position (orientation) of the sprite but it isnt following the sprite orientation when it moves forward (if keybd_tupl[K_UP]).
import pygame
import random
import math
from pygame.locals import *
window_wid = 800
window_hgt = 600
frame_rate = 50
delta_time = 1 / frame_rate
STATE_READY = 2
def game_loop_inputs():
# look in the event queue for the quit event
quit_ocrd = False
for evnt in pygame.event.get():
if evnt.type == QUIT:
quit_ocrd = True
return quit_ocrd
def game_loop_update(circle_hitbox):
# start by assuming that no collisions have occurred
circle_hitbox["col"] = False
# return the new state of the rotating line and the circle hitbox
return circle_hitbox
def game_loop_render(circle_hitbox, window_sfc):
# clear the window surface (by filling it with black)
window_sfc.fill( (0,0,0) )
# draw the circle hitbox, in red if there has been a collision or in white otherwise
if circle_hitbox["col"]:
#pygame.draw.circle(window_sfc, (255, 0, 0), circle_hitbox["pos"], circle_hitbox["rad"])
rotated_damage = pygame.transform.rotate(circle_hitbox["damage"], circle_hitbox["angle"])
window_sfc.blit(rotated_damage, circle_hitbox["pos"])
else:
#pygame.draw.circle(window_sfc, (255, 255, 255), circle_hitbox["pos"], circle_hitbox["rad"])
rotated_image = pygame.transform.rotate(circle_hitbox["sprite"], circle_hitbox["angle"])
window_sfc.blit(rotated_image, circle_hitbox["pos"])
# update the display
pygame.display.update()
def main():
# initialize pygame
pygame.init()
# create the window and set the caption of the window
window_sfc = pygame.display.set_mode( (window_wid, window_hgt) )
pygame.display.set_caption('"Toy" for the MDA Exercise')
# create a clock
clock = pygame.time.Clock()
# this is the initial game state
game_state = STATE_READY
#####################################################################################################
# these are the initial game objects that are required (in some form) for the core mechanic provided
#####################################################################################################
# this game object is a circulr
circle_hitbox = {}
circle_hitbox["pos"] = (window_wid // 2, window_hgt // 2)
circle_hitbox["rad"] = 30
circle_hitbox["col"] = False
circle_hitbox["sprite"] = pygame.image.load("cars_racer_{}.png".format(random.randint(1, 3)))
circle_hitbox["damage"] = pygame.image.load("cars_racer_red.png")
circle_hitbox["crash"] = pygame.image.load("explosion.png")
circle_hitbox["damaged"] = False
circle_hitbox["angle"] = 0
speed = 10.0
vel_x = speed * math.cos(circle_hitbox["angle"] * (math.pi / 180))
vel_y = speed * math.sin(circle_hitbox["angle"] * (math.pi / 180))
# the game loop is a postcondition loop controlled using a Boolean flag
closed_flag = False
while not closed_flag:
#####################################################################################################
# this is the "inputs" phase of the game loop, where player input is retrieved and stored
#####################################################################################################
closed_flag = game_loop_inputs()
keybd_tupl = pygame.key.get_pressed()
if keybd_tupl[K_UP]:
circle_hitbox["pos"] = (circle_hitbox["pos"][0] + vel_x, circle_hitbox["pos"][1] + vel_y)
print(vel_y)
if keybd_tupl[K_LEFT]:
circle_hitbox["angle"] = (circle_hitbox["angle"] + 10.0)
if keybd_tupl[K_RIGHT]:
circle_hitbox["angle"] = (circle_hitbox["angle"] - 10.0)
#####################################################################################################
# this is the "update" phase of the game loop, where the changes to the game world are handled
#####################################################################################################
circle_hitbox = game_loop_update(circle_hitbox)
#####################################################################################################
# this is the "render" phase of the game loop, where a representation of the game world is displayed
#####################################################################################################
game_loop_render(circle_hitbox, window_sfc)
# enforce the minimum frame rate
clock.tick(frame_rate)
if __name__ == "__main__":
main()
It just isnt working & I dont know why.
You have to calculate the vel_x and vel_y in the while loop.
while not closed_flag:
closed_flag = game_loop_inputs()
keybd_tupl = pygame.key.get_pressed()
if keybd_tupl[K_UP]:
circle_hitbox["pos"] = (circle_hitbox["pos"][0] + vel_x, circle_hitbox["pos"][1] + vel_y)
print(vel_y)
if keybd_tupl[K_LEFT]:
circle_hitbox["angle"] -= 1.0
if keybd_tupl[K_RIGHT]:
circle_hitbox["angle"] += 1.0
# `math.radians` can be used instead of `* (math.pi / 180)`
vel_x = speed * math.cos(math.radians(circle_hitbox["angle"]))
vel_y = speed * math.sin(math.radians(circle_hitbox["angle"]))
Also, pass the negative angle to pygame.transform.rotate in the game_loop_render function:
rotated_damage = pygame.transform.rotate(circle_hitbox["damage"], -circle_hitbox["angle"])
The rotation probably still doesn't look right (I'm using some replacement images and they don't rotate correctly). Take a look at this answer if you want to know how to rotate pygame sprites and images around their center in pygame.
Ok so for our couse,we have pretty much been thrown alot of code and told to make a project of it...been fixing bugs all week.Now i finally got a scrolling background working,but when I add it.The shoot functionality stops working,as well as the controls...I need help as to why this is happening? The project is due in a week and I'm running out of options..
""" Import al the necessary classes/sprites and pygame libs """
import pygame
from sys import exit
from LaserSprite import Laser
from PlayerShipSprite import PlayerShip
from EnenySprite import Asteroid
from Vector_Calc import Vector
from CursorSprite import Cursor
from ScoreBoardSprite import ScoreBoard
import math
""" Set up the screen size and sound mixer """
pygame.init()
screen = pygame.display.set_mode((640, 480))
pygame.mixer.init()
""" This function updates the score board with
information retrieved from the player class """
def updateScoreBoard(RefreshScore):
(ship, scoreBoard) = RefreshScore
scoreBoard.text = ("Score: %d Fired: %d Shield: %d" %(ship.Get_Score(),ship.Get_Ammo(),ship.Get_Shield()))
""" This function updates the position of the ship on the
screen based on what keys are pressed """
def checkKeys(myData):
(event, ship) = myData
if event.key == pygame.K_LEFT:
print 'LEFT'
ship.MoveLeft()
if event.key == pygame.K_RIGHT:
print 'RIGHT'
ship.MoveRight()
if event.key == pygame.K_UP:
ship.MoveUp()
print 'UP'
if event.key == pygame.K_DOWN:
print 'DOWN'
ship.MoveDown()
""" This is the main game loop, when the game is over the player will
be returned to the introduction screen """
def game():
""" Set up the title of the game, the background size, color and then
blit to the screen """
pygame.display.set_caption("Asteroids Version 2.0")
background = pygame.Surface(screen.get_size())
background.fill((0, 0, 0))
screen.blit(background, (0, 0))
""" Set the mouse cursor to be hidden """
pygame.mouse.set_visible(False)
""" Create a new instance of a player and cursor, here
I have set up the cursor as a class the will be updated during the game """
ship = PlayerShip(screen)
cursor = Cursor()
""" Create a new instance of the ScoreBoard class, this is then
given a text value and a position on the screen. Pay note
as to how the score, ammo and shield are retrieved from the PlayerShip class"""
scoreBoard = ScoreBoard()
scoreBoard.text = ("Score: %d Fired: %d Shield: %d" %(ship.Get_Score(),ship.Get_Ammo(),ship.Get_Shield()))
scoreBoard.center = (320,470)
""" Create empty sprite groups as shown below """
all_sprites_list = pygame.sprite.Group()
laser_list = pygame.sprite.Group()
asteroid_list = pygame.sprite.Group()
""" Create 20 asteroids and add them to the asteroid list group """
for i in range(10):
asteroid = Asteroid()
asteroid_list.add(asteroid)
""" Add the ship, cursor, asteroid list group and the scoreBoard to the
all sprites list. In doing this we can have the transparent effect some
were looking for """
all_sprites_list.add(ship)
all_sprites_list.add(cursor)
all_sprites_list.add(asteroid_list)
all_sprites_list.add(scoreBoard)
""" Set up the refresh rate and key repeat for the game"""
clock = pygame.time.Clock()
pygame.key.set_repeat(10,10)
keepGoing = True
while keepGoing:
b1 = "ocean1.gif"
back = pygame.image.load(b1)
back2 = pygame.image.load(b1)
h = 0
screenWidth = 600
clock.tick(30)
""" Check for any events - keyboard, mouse etc. """
for event in pygame.event.get():
if event.type == pygame.QUIT:
keepGoing = False
screen.blit(back, (h,0))
screen.blit(back2, (h-screenWidth,0))
h = h + 0.5
if h == screenWidth:
h = 0
if event.type == pygame.KEYDOWN:
""" If a key is pressed, bundle the event and the ship
and pass it to the function that handles events """
myData = (event, ship)
checkKeys(myData)
if event.type == pygame.MOUSEBUTTONDOWN:
""" If a mouse event occurs check to see if it is the left click """
if event.button==1:
""" reduce the ammo of the ship
get the center of the ship sprite
get where the mouse was clicked on the screen
bundle the (x,y) position for the above
pass them to PlotVector function and have the
angle and vector returned
create a new instance a laser
pass to it the start position, angle and vector it must travel
add the laser to the laser_list group and the all sprites group """
ship.Set_Ammo();
shipCenter = ship.returnPosition()
mousePos = pygame.mouse.get_pos()
print "Ship: %s, Mouse: %s " %(shipCenter, mousePos)
data= (shipCenter, mousePos)
angle, vect = PlotVector(data)
laser = Laser()
laser.fire.play()
laser.AnglePoints(shipCenter, angle, vect)
laser_list.add(laser)
all_sprites_list.add(laser)
""" update all sprites """
all_sprites_list.update()
""" For every laser that was fired we are going to do the following: """
for laser in laser_list:
""" Create a list of asteroids that were hit by the laser """
asteroid_hit_list = pygame.sprite.spritecollide(laser, asteroid_list, False)
""" For each asteroid hit,
Update the ship score
play a bang sound
reset the asteroid to the top of the screen again
remove the laser from the laser list and all sprites """
for asteroid in asteroid_hit_list:
ship.Set_Score()
asteroid.bang.play()
asteroid.reset()
laser_list.remove(laser)
all_sprites_list.remove(laser)
""" Remove the laser if it flies up off the screen """
if laser.rect.y < -10 or laser.rect.y > screen.get_height():
laser_list.remove(laser)
all_sprites_list.remove(laser)
if laser.rect.x < -10 or laser.rect.x > screen.get_width():
laser_list.remove(laser)
all_sprites_list.remove(laser)
""" Now we are going to create a list of asteroids that hit the ship """
asteroid_hit_Ship = pygame.sprite.spritecollide(ship, asteroid_list, False)
""" For each asteroid that hits the ship,
Update the ship shields
play a bang sound
reset the asteroid to the top of the screen again """
for asteroid in asteroid_hit_Ship:
asteroid.bang.play()
ship.Set_Shield()
asteroid.reset()
""" if the ship's shields are less than 0 then the game will finish """
if ship.Get_Shield()<0:
keepGoung=False
break
""" Update the score and refresh the scoreboard using the updateScoreBoard function """
RefreshScore = (ship, scoreBoard)
updateScoreBoard(RefreshScore)
""" The last part refreshes the sprites """
asteroid_list.clear(screen, background)
asteroid_list.update()
asteroid_list.draw(screen)
all_sprites_list.clear(screen, background)
all_sprites_list.update()
all_sprites_list.draw(screen)
pygame.display.flip()
return ship.Get_Score()
def PlotVector(PlotVect):
"""
This PlotVector function handles the calculation of the new
vector and also the calculation of the angle the arrow/bullet/projectile
will be transformed and travel to.
REFER TO YOUR NOTES FROM LECTURE 5
"""
""" Un-bundle the data that is passed into this function """
(start, dest) = PlotVect
""" Create a new Vector object and call it vect """
vect = Vector()
""" Pass the start and dest coordinates and get back a new vector which the arrow must travel """
vect = Vector.from_points(start, dest)
""" Calculate the magnitude (Distance) between the two points """
mag = vect.get_magnitude()
""" Get the values for the vector, i.e. the change in x and change in y """
x = vect.x
y = vect.y
""" This variable will be used to calculate and store the angle between points """
angDEG = (math.atan2(y, x)*(180/math.pi))*-1
""" Print the coordinates and angle to the screen for testing """
print "Start: %s \nEnd: %s\nVector: %s\nMagnitude: %d" %(start, dest, vect, mag)
print "Angle : %d" %(angDEG)
""" Bundle and return the angle and vector which will be used in the TheArrow Sprite """
return (angDEG, vect)
"""
The next if statement runs the main function as it is in the primary program scope.
This was covered in lecture 5 available on Moodle
"""
def instructions(score):
shipIntro = PlayerShip(screen)
allSprites = pygame.sprite.Group(shipIntro)
insFont = pygame.font.SysFont(None, 30)
instructions = (
"Asteroids Version 2.0" ,
"Previous Score: %d" %score,
"Instructions: You must clear the asteroid",
"field so that you can reach your home-world",
"",
"Fly around the field and fire to hit the ",
"asteroids but be careful not to fly too close",
"to any asteroids. Your ship has a limited amount of ",
"off shielding and if your shielding drops below ",
"0 your done for",
"good luck!",
"",
"click to start, escape to quit..."
)
insLabels = []
for line in instructions:
tempLabel = insFont.render(line, 1, (255, 255, 0))
insLabels.append(tempLabel)
keepGoing = True
clock = pygame.time.Clock()
pygame.mouse.set_visible(False)
while keepGoing:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
keepGoing = False
donePlaying = True
if event.type == pygame.MOUSEBUTTONDOWN:
keepGoing = False
donePlaying = False
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
keepGoing = False
donePlaying = True
allSprites.update()
allSprites.draw(screen)
for i in range(len(insLabels)):
screen.blit(insLabels[i], (50, 30*i))
pygame.display.flip()
pygame.mouse.set_visible(True)
return donePlaying
def main():
donePlaying = False
score = 0
while not donePlaying:
donePlaying = instructions(score)
if not donePlaying:
score = game()
if __name__ == "__main__":
main()
Check indentions - you have if event.type == pygame.KEYDOWN: and if event.type == pygame.MOUSEBUTTONDOWN: outside of for event in pygame.event.get():. You have some screen.blit() betwin for and if with smaller indentions.
Maybe you put this in wrong place:
screen.blit(back, (h,0))
screen.blit(back2, (h-screenWidth,0))
h = h + 0.5
if h == screenWidth:
h = 0
EDIT:
Probably your code should look like this:
while keepGoing:
b1 = "ocean1.gif"
back = pygame.image.load(b1)
back2 = pygame.image.load(b1)
h = 0
screenWidth = 600
clock.tick(30)
""" Check for any events - keyboard, mouse etc. """
for event in pygame.event.get():
if event.type == pygame.QUIT:
keepGoing = False
elif event.type == pygame.KEYDOWN:
""" If a key is pressed, bundle the event and the ship
and pass it to the function that handles events """
myData = (event, ship)
checkKeys(myData)
elif event.type == pygame.MOUSEBUTTONDOWN:
""" If a mouse event occurs check to see if it is the left click """
if event.button == 1:
""" reduce the ammo of the ship
get the center of the ship sprite
get where the mouse was clicked on the screen
bundle the (x,y) position for the above
pass them to PlotVector function and have the
angle and vector returned
create a new instance a laser
pass to it the start position, angle and vector it must travel
add the laser to the laser_list group and the all sprites group """
ship.Set_Ammo();
shipCenter = ship.returnPosition()
mousePos = pygame.mouse.get_pos()
print "Ship: %s, Mouse: %s " %(shipCenter, mousePos)
data= (shipCenter, mousePos)
angle, vect = PlotVector(data)
laser = Laser()
laser.fire.play()
laser.AnglePoints(shipCenter, angle, vect)
laser_list.add(laser)
all_sprites_list.add(laser)
screen.blit(back, (h,0))
screen.blit(back2, (h-screenWidth,0))
h = h + 0.5
if h == screenWidth:
h = 0
# rest of main loop
btw: I use elif because event.type can't be pygame.QUIT, pygame.KEYDOWN and pygame.MOUSEBUTTONDOWN in one event.