Pygame character face mouse - pygame

I am creating a basic 2d overhead view game, and am currently working to make the character "face" the mouse as I move it.
Here is my code so far:
class Player(pygame.sprite.Sprite):
def __init__(self,x,y):
super(Player,self).__init__()
self.image=player_image
self.original_image=self.image
self.rect=self.image.get_rect(center=(x,y))
self.change_x=0
self.change_y=0
self.speed=7.5
self.inventory={'wood':0}
def changespeed(self,x,y):
self.change_x+=x
self.change_y+=y
def update(self,rect):
player.rotate()
self.rect.x+=self.change_x
self.rect.y+=self.change_y
screen.blit(self.image,(rect.x,rect.y))
def check(self):
global tick
if pygame.mouse.get_pressed()[0]:
for tree in resources:
tree_rect=tree.rect
if self.rect.colliderect(tree_rect) and tick>=9:
self.inventory['wood']+=1
tick=0
def rotate(self):
mouse_x, mouse_y = pygame.mouse.get_pos()
rel_x, rel_y = mouse_x - self.rect.x, mouse_y -self.rect.y
angle = (180 / math.pi) * -math.atan2(rel_y, rel_x)
self.image = pygame.transform.rotate(self.original_image, int(angle))
self.rect = self.image.get_rect(center=(self.rect.x,self.rect.y))
To do this, I followed this answer. It supposedly calculated the vector between the mouse and the player, and turned the character around toward the mouse each step, but for me the sprite wobbled around when the mouse moved, and turned invisible.
Full code:
import pygame
from pygame.locals import *
import sys
import math
import pygame.gfxdraw
import random
pygame.init()
black=(0,0,0)
white=(255,255,255)
forest=(34,139,34)
red=(255,0,0)
blue=(0,0,255)
green=(0,255,0)
light_green=(0,120,0)
dark_green=(0,90,0)
skin=(255,224,189)
yellow=(255,255,0)
wood_image=pygame.image.load('./assets/images/wood.png')
stone_image=pygame.image.load('./assets/images/stone.png')
player_image=pygame.image.load('./assets/images/player.png')
tick=0
def terminate():
pygame.quit()
sys.exit()
def drawTextcenter(text,font,screen,x,y,color):
textobj=font.render(text,True,color)
textrect=textobj.get_rect(center=(x,y))
screen.blit(textobj,textrect)
def drawText(text, font, surface, x, y,color):
textobj=font.render(text, 1, color)
textrect=textobj.get_rect()
textrect.topleft=(x, y)
surface.blit(textobj, textrect)
class Button(object):
global screen_width,screen_height,screen
def __init__(self,x,y,width,height,text_color,background_color,text):
self.rect=pygame.Rect(x,y,width,height)
self.x=x
self.y=y
self.width=width
self.height=height
self.text=text
self.text_color=text_color
self.background_color=background_color
self.angle=0
def check(self):
return self.rect.collidepoint(pygame.mouse.get_pos())
def draw(self):
pygame.draw.rect(screen, self.background_color,(self.rect),0)
drawTextcenter(self.text,font,screen,self.x+self.width/2,self.y+self.height/2,self.text_color)
pygame.draw.rect(screen,self.text_color,self.rect,3)
class Bar(object):
def __init__(self,x,y,length,color):
self.rect=pygame.Rect(x,y,length,17.5)
self.image=pygame.draw.rect(screen,white,(self.rect),)
self.x=x
self.y=y
self.width=100
self.height=17.5
self.color=color
self.multiplier=length/100
def draw(self,num):
rect=pygame.Rect(self.x,self.y,num*self.multiplier,self.height)
pygame.draw.rect(screen,self.color,self.rect,3)
pygame.draw.rect(screen,self.color,rect,0)
class Player(pygame.sprite.Sprite):
def __init__(self,x,y):
super(Player,self).__init__()
self.image=player_image
self.original_image=self.image
self.rect=self.image.get_rect(center=(x,y))
self.change_x=0
self.change_y=0
self.speed=7.5
self.inventory={'wood':0}
def changespeed(self,x,y):
self.change_x+=x
self.change_y+=y
def update(self,rect):
self.rotate(camera)
self.rect.x += self.change_x
self.rect.y += self.change_y
rect = camera.apply(self)
screen.blit(self.image, rect)
def check(self):
global tick
if pygame.mouse.get_pressed()[0]:
for tree in resources:
tree_rect=tree.rect
if self.rect.colliderect(tree_rect) and tick>=9:
self.inventory['wood']+=1
tick=0
def rotate(self,camera):
mouse_x, mouse_y = pygame.mouse.get_pos()
mouse_x -= camera.state.x
mouse_y -= camera.state.y
rel_x, rel_y = mouse_x - self.rect.centerx, mouse_y -self.rect.centery
angle = -math.degrees(math.atan2(rel_y, rel_x))
self.image = pygame.transform.rotate(self.original_image, angle)
self.rect = self.image.get_rect(center=self.rect.center)
class Tree(pygame.sprite.Sprite):
def __init__(self,x,y):
super(Tree,self).__init__()
self.rect=pygame.Rect(x,y,100,100)
self.rect.x=x
self.rect.y=y
def update(self,rect):
pygame.gfxdraw.filled_circle(screen,rect.x,rect.y,80,light_green)
pygame.gfxdraw.aacircle(screen,rect.x,rect.y,80,light_green)
class Rock(pygame.sprite.Sprite):
def __init__(self):
pass
class Camera(object):
def __init__(self,camera_func,width,height):
self.camera_func=camera_func
self.state=pygame.Rect(0,0,width,height)
def apply(self,target):
return target.rect.move(self.state.topleft)
def update(self,target):
self.state=self.camera_func(self.state,target.rect)
def complex_camera(camera, target_rect):
l, t = target_rect.center
_,_,w,h = camera
l,t,_,_ = -l+screen_width/2, -t+screen_height/2, w, h
l = min(0, l)
l = max(-(camera.width-screen_width), l)
t = max(-(camera.height-screen_height), t)
t = min(0, t)
return pygame.Rect(l, t, w, h)
clock=pygame.time.Clock()
font=pygame.font.SysFont(None,40)
screen_width=1440
screen_height=800
screen=pygame.display.set_mode([screen_width,screen_height])
pygame.display.set_caption('Survival')
total_level_width=screen_width*5
total_level_height=screen_height*5
camera = Camera(complex_camera, total_level_width, total_level_height)
player=Player(random.randint(100,7900),random.randint(100,5900))
friendlies=pygame.sprite.Group()
friendlies.add(player)
player_health_bar=Bar(125,15,200,green)
player_health=100
player_food_bar=Bar(437.5,15,200,red)
player_food=100
player_thirst_bar=Bar(750,15,200,blue)
player_thirst=100
player_energy_bar=Bar(1100,15,200,yellow)
player_energy=100
resources=pygame.sprite.Group()
wood=False
inventory={}
for i in range(1000):
tree=Tree(random.randint(100,7900),random.randint(100,5900))
resources.add(tree)
done1=False
while not done1:
screen.fill(black)
font=pygame.font.SysFont(None, 90)
text_width,text_height=font.size('Survival')
drawText('Survival', font, screen, (screen_width/2-text_width/2), (screen_height / 2-375),white)
font=pygame.font.SysFont(None, 40)
start_button=Button(screen_width/2-125,650,250,50,white,black,'Start')
start_button.draw()
back_button=Button(screen_width/2-125,725,250,50,white,black,'Back')
back_button.draw()
pygame.display.flip()
done2=False
while not done2:
for event in pygame.event.get():
if event.type==QUIT:
terminate()
elif event.type==pygame.MOUSEBUTTONDOWN:
if start_button.check()==True:
done3=False
pause_back=False
while not done3:
tick+=1
for event in pygame.event.get():
if event.type==pygame.QUIT:
terminate()
elif event.type==pygame.KEYDOWN:
if event.key==pygame.K_a:
player.changespeed(-(player.speed), 0)
elif event.key==pygame.K_d:
player.changespeed(player.speed, 0)
elif event.key==pygame.K_w:
player.changespeed(0, -(player.speed))
elif event.key==pygame.K_s:
player.changespeed(0, player.speed)
elif event.key==pygame.K_p:
font=pygame.font.SysFont(None, 90)
text_width,text_height=font.size('Paused')
drawText('Paused', font, screen, (screen_width / 2-(text_width/2)), (screen_height / 2-375),white)
resume_button=Button(screen_width/2-125,650,250,50,white,black,'Resume')
resume_button.draw()
back_button.draw()
pygame.display.flip()
back=False
while not back:
for event in pygame.event.get():
if event.type==QUIT:
terminate()
elif event.type==pygame.MOUSEBUTTONDOWN:
if resume_button.check()==True:
back=True
elif back_button.check()==True:
done3=True
done2=True
pause_back=True
back=True
elif event.type == pygame.KEYUP:
if event.key == pygame.K_a:
player.changespeed(player.speed, 0)
elif event.key == pygame.K_d:
player.changespeed(-(player.speed), 0)
elif event.key == pygame.K_w:
player.changespeed(0, player.speed)
elif event.key == pygame.K_s:
player.changespeed(0, -(player.speed))
camera.update(player)
if player.rect.x<0:
player.rect.x=0
if player.rect.right>total_level_width:
player.rect.right=total_level_width
if player.rect.y<0:
player.rect.y=0
if player.rect.bottom>total_level_height:
player.rect.bottom=total_level_height
screen.fill(dark_green)
player.update(camera)
for resource in resources:
resource.update(camera.apply(resource))
player_health_bar.draw(player_health)
player_food_bar.draw(player_food)
player_thirst_bar.draw(player_thirst)
#player_endergy_bar.draw(player_energy)
font=pygame.font.SysFont(None, 40)
drawText('Health:',font,screen,20,10,green)
drawText('Food:',font,screen,350,10,red)
drawText('Thirst:',font,screen,650,10,blue)
#drawText('Energy:', font, screen, 975, 10, yellow)
pygame.draw.rect(screen,forest,(50,675,100,100))
pygame.draw.rect(screen,forest,(175,675,100,100))
pygame.draw.rect(screen,forest,(300,675,100,100))
#player.check()
for item in player.inventory:
if item=='wood' and player.inventory['wood']>0:
wood=True
if wood:
screen.blit(wood_image,(62.5,687.5))
drawTextcenter(str(player.inventory['wood']),font,screen,100,735,black)
pygame.display.flip()
clock.tick(100)
if pause_back==True:
break
if pause_back==True:
break
screen.fill(black)
font=pygame.font.SysFont(None, 90)
text_width,text_height=font.size("Game Over")
drawText('Game Over', font, screen, (screen_width/2-text_width/2), (screen_height / 2-200),white)
font=pygame.font.SysFont(None, 40)
retry_button=Button(screen_width/2-125,650,250,50,white,black,'Retry')
retry_button.draw()
back_button.draw()
pygame.display.flip()
back=False
while not back:
for event in pygame.event.get():
if event.type==QUIT:
terminate()
elif event.type==pygame.MOUSEBUTTONDOWN:
if retry_button.check()==True:
back=True
if back_button.check()==True:
back=True
done2=True
done3=True
elif back_button.check()==True:
done2=True
done1=True

I haven't checked out your complete program because it's too much code, but I can see a mistake in the Player class. You're assigning the (self.rect.x,self.rect.y) (the top left) coordinates to the center of the new rect in the rotate method, but you have to assign the center coords of the previous rect.
self.rect = self.image.get_rect(center=(self.rect.x,self.rect.y))
Change the line above to:
self.rect = self.image.get_rect(center=self.rect.center)
Here's a minimal, complete example:
import math
import pygame as pg
class Player(pg.sprite.Sprite):
def __init__(self, pos):
super(Player,self).__init__()
x, y = pos
self.image = pg.Surface((50, 30), pg.SRCALPHA)
pg.draw.polygon(
self.image,
pg.Color('dodgerblue1'),
((1, 1), (49, 15), (1, 29)))
self.original_image = self.image
self.rect=self.image.get_rect(center=(x,y))
self.change_x=0
self.change_y=0
self.speed=7.5
self.inventory={'wood':0}
def changespeed(self,x,y):
self.change_x+=x
self.change_y+=y
def update(self):
self.rotate()
self.rect.x+=self.change_x
self.rect.y+=self.change_y
def rotate(self):
mouse_x, mouse_y = pg.mouse.get_pos()
rel_x, rel_y = mouse_x - self.rect.x, mouse_y -self.rect.y
angle = (180 / math.pi) * -math.atan2(rel_y, rel_x)
self.image = pg.transform.rotate(self.original_image, angle)
# rect.x and .y are the topleft coords, but you need the to
# pass the center coordinates of the previous rect to the new one.
self.rect = self.image.get_rect(center=self.rect.center)
def main():
screen = pg.display.set_mode((640, 480))
clock = pg.time.Clock()
all_sprites = pg.sprite.Group()
player = Player((300, 200))
all_sprites.add(player)
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_d:
player.change_x = 4
elif event.key == pg.K_a:
player.change_x = -4
elif event.type == pg.KEYUP:
if event.key == pg.K_d:
player.change_x = 0
elif event.key == pg.K_a:
player.change_x = 0
all_sprites.update()
screen.fill((30, 30, 30))
all_sprites.draw(screen)
pg.display.flip()
clock.tick(30)
if __name__ == '__main__':
pg.init()
main()
pg.quit()
The problem is caused by the camera. You have to apply the camera offset to the mouse position as well.
Here's a simple solution. First, pass the camera instead of the rect to the player.update method.
player.update(camera)
Pass it to the rotate method as well.
def update(self, camera):
self.rotate(camera)
self.rect.x += self.change_x
self.rect.y += self.change_y
rect = camera.apply(self)
screen.blit(self.image, rect)
In the rotate method you have to subtract the camera position from the mouse position to get the world coordinates.
def rotate(self, camera):
mouse_x, mouse_y = pygame.mouse.get_pos()
# Subtract the camera offset from the mouse position.
mouse_x -= camera.state.x
mouse_y -= camera.state.y
# Use the center coordinates of the rect.
rel_x, rel_y = mouse_x - self.rect.centerx, mouse_y -self.rect.centery
angle = -math.degrees(math.atan2(rel_y, rel_x))
self.image = pygame.transform.rotate(self.original_image, angle)
# Set the new center to the previous center coords.
self.rect = self.image.get_rect(center=self.rect.center)
I also had to modify the complex_camera function, because it should work with the center coordinates.
def complex_camera(camera, target_rect):
# The camera works with the center instead of
# the topleft coordinates now.
l, t = target_rect.center
_,_,w,h = camera
l,t,_,_ = -l+screen_width/2, -t+screen_height/2, w, h
l = min(0, l)
l = max(-(camera.width-screen_width), l)
t = max(-(camera.height-screen_height), t)
t = min(0, t)
return pygame.Rect(l, t, w, h)

Related

How to make my sprite shoot fireballs (bullets) in pygame?

This is my Sprite1.py program which I import to my main.py program. It contains all the classes I use in my main.py. I tried to create bullets based on a program I saw online but it doesn't work for me. Could anyone help me figure out how to successfully make it work? I don't know how to add the Bullet class from my Sprite.py program to the main.py one.
import pygame
import sys
import os
import time
import random
from pygame import mixer
from pygame.locals import *
def showStartScreen(surface):
show = True
while show == True:
startbg = pygame.image.load(os.path.join('images', 'Starting_scr.png'))
surface.blit(startbg, (0,0))
pygame.display.flip()
for event in pygame.event.get():
if event.type == pygame.KEYDOWN:
show = False
pygame.init()
class Player(pygame.sprite.Sprite):
'''
Spawn a player
'''
def __init__(self):
pygame.sprite.Sprite.__init__(self)
super().__init__()
self.movex = 0
self.movey = 0
self.frame = 0
self.images = []
self.imagesleft = []
self.imagesright = []
self.alpha = (0,0,0)
self.ani = 4 # animation cycles
for i in range(1,5):
img = pygame.image.load(os.path.join('images','hero' + str(i) + '.png')).convert()
img.convert_alpha()
img.set_colorkey(self.alpha)
self.imagesright.append(img)
self.image = self.imagesright[0]
self.rect = self.image.get_rect()
#self.rect = self.image.get_rect(center=pos)
self.all_sprites = all_sprites
self.add(self.all_sprites)
self.bullets = bullets
self.bullet_timer = .1
for i in range(1,5):
img = pygame.image.load(os.path.join('images','hero' + str(i) + '.png')).convert()
img = pygame.transform.flip(img, True, False)
img.convert_alpha()
img.set_colorkey(self.alpha)
self.imagesleft.append(img)
self.image = self.imagesleft[0]
self.rect = self.image.get_rect()
#self.rect = self.image.get_rect(center=pos)
self.all_sprites = all_sprites
self.add(self.all_sprites)
self.bullets = bullets
self.bullet_timer = .1
def control(self,x,y):
'''
control player movement
'''
self.movex += x
self.movey -= y
self.bullets
def update(self, dt):
'''
Update sprite position
'''
self.rect.center = pg.mouse.get_pos()
self.rect.x = self.rect.x + self.movex
self.rect.y = self.rect.y + self.movey
# moving left
if self.movex < 0:
self.frame += 1
if self.frame > 3*self.ani:
self.frame = 0
self.image = self.imagesleft[self.frame//self.ani]
# moving right
if self.movex > 0:
self.frame += 1
if self.frame > 3*self.ani:
self.frame = 0
self.image = self.imagesright[self.frame//self.ani]
mouse_pressed = pg.mouse.get_pressed()
self.bullet_timer -= dt # Subtract the time since the last tick.
if self.bullet_timer <= 0:
self.bullet_timer = 0 # Bullet ready.
if mouse_pressed[0]: # Left mouse button.
# Create a new bullet instance and add it to the groups.
Bullet(pygame.mouse.get_pos(), self.all_sprites, self.bullets)
self.bullet_timer = .1 # Reset the timer.
class Bullet(pygame.sprite.Sprite):
def __init__(self, pos, *sprite_groups):
super().__init__(*sprite_groups)
BULLET_IMG = pygame.image.load(os.path.join('images','fireball.png')).convert_alpha()
self.image = BULLET_IMG
self.rect = self.image.get_rect(center=pos)
self.pos = pygame.math.Vector2(pos)
self.vel = pygame.math.Vector2(0, -450)
self.damage = 10
def update(self, dt):
# Add the velocity to the position vector to move the sprite.
self.pos += self.vel * dt
self.rect.center = self.pos # Update the rect pos.
if self.rect.bottom <= 0:
self.kill()
This is my main.py program
import pygame
import os
import time
from pygame import mixer
from pygame.locals import *
import Sprite1
width = 960
height = 720
fps = 40 # frame rate
#ani = 4 # animation cycles
clock = pygame.time.Clock()
pygame.init()
main = True
pygame.init()
FPSCLOCK = pygame.time.Clock()
DISLAYSURF = pygame.display.set_mode((width,height))
surface = pygame.display.set_mode([width,height])
pygame.display.set_caption('B.S.G.!!!')
background = pygame.image.load(os.path.join('images','Bg.png')).convert()
backdropbox = surface.get_rect()
pygame.mixer.music.load('.\\sounds\\Fairy.mp3')
pygame.mixer.music.play(-1, 0.0)
player = Sprite1.Player() # spawn player
fire = Sprite1.Bullet()
player.rect.x = 50
player.rect.y = 500
player_list = pygame.sprite.Group()
player_list.add(player)
steps = 10 # how fast to move
Sprite.showStartScreen(surface)
while main == True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit(); sys.exit()
main = False
#if event.type == pygame.MOUSEBUTTONDOWN:
#if Bullet.bullet_timer <= 0:
#Bullet.bullet_timer = 0 # Bullet ready.
#if mouse_pressed[0]: # Left mouse button.
## Create a new bullet instance and add it to the groups.
#Bullet(pg.mouse.get_pos(), self.all_sprites, self.bullets)
#Bullet.bullet_timer = .1 # Reset the timer.
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_LEFT:
player.control(-steps,0)
if event.key == pygame.K_RIGHT:
player.control(steps,0)
if event.key == pygame.K_UP:
player.rect.y -= 100
if event.type == pygame.KEYUP:
if event.key == pygame.K_LEFT:
player.control(steps,0)
if event.key == pygame.K_RIGHT:
player.control(-steps,0)
if event.key == pygame.K_UP:
player.rect.y += 100
if event.key == ord('q'):
pygame.quit()
sys.exit()
main = False
surface.blit(background, (0, 0))
player.update()
player_list.draw(surface) #refresh player position
pygame.display.flip()
clock.tick(fps)
You already import the sprite file in your main.py on line 6:
import Sprite1
So now your code can call functions from this file:
Sprite1.showStartScreen( window )
Alternatively, instead on line 6 you could use:
from Sprite1 import *
And then your code can reference everything in Sprite1.py as if it was defined in the main.py itself.
The import section of the Python Documentation makes for fairly dry reading, but might just be worth a quick look if you get stuck.
Your bullet sprite code looks mostly OK now, and should work. It seems to be adding the newly created sprite to a bullets - which should be a SpriteGroup, but the supplied code does not show a definition of this.
Typically to move the bullet sprites, the code would call bullets.update() inside the main loop every frame. I can see the code calling player.update() in the last few lines of the main loop. Maybe it just needs a similar call to update() for the bullets group.

my pygame bullet sprites only fire once each time through loop. would like to fire multiple times

i'm very new to coding, and i'm trying to get a ship to fire multiple bullets, but every time i push spacebar the bullet sort of re-triggers and doesn't make it to the end of the screen. it seems maybe only one instance of my Bullet class is called each time but i don't know how to fix it. here is the code i have going so far:
import sys
import pygame
from pygame.sprite import Sprite
class Sideship():
def __init__(self):
pygame.init()
self.screen = pygame.display.set_mode((1200,800))
self.screen_rect = self.screen.get_rect()
pygame.display.set_caption("Side Ship")
self.bg_color = (50, 50, 255)
self.bullets = pygame.sprite.Group()
self.jet = Jet()
def run_game(self):
self.jet.rect.x = -20
self.jet.rect.y = 290
self.bullet = Bullet()
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_DOWN:
self.jet.moving_down = True
if event.key == pygame.K_UP:
self.jet.moving_up = True
if event.key == pygame.K_SPACE:
self.new_bullet = Bullet()
self.bullets.add(self.new_bullet)
self.new_bullet.rect.x = self.jet.rect.x+200
self.new_bullet.rect.y = self.jet.rect.y+30
if event.type == pygame.KEYUP:
if event.key == pygame.K_DOWN:
self.jet.moving_down = False
if event.key == pygame.K_UP:
self.jet.moving_up = False
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_q:
sys.exit()
self.jet.update()
self.bullets.update()
self.screen.fill(self.bg_color)
self.bullets.update()
for bullet in self.bullets.sprites():
pygame.draw.rect(self.screen, self.new_bullet.bullet_color, self.new_bullet.rect)
self.screen.blit(self.jet.image, self.jet.rect)
pygame.display.flip()
class Jet():
def __init__(self):
self.image = pygame.image.load('jet.bmp')
self.rect = self.image.get_rect()
self.moving_down = False
self.moving_up = False
self.rect.y = float(self.rect.y)
self.rect.x = float(self.rect.x)
def update(self):
if self.moving_down and self.rect.bottom < 801:
self.rect.y += 1.9
if self.moving_up and self.rect.top > -14:
self.rect.y -= 1.4
class Bullet(Sprite):
def __init__(self,):
super().__init__()
self.bullet_width = 30
self.bullet_height = 5
self.bullet_color = (250,250,250)
self.rect = pygame.Rect(0,0, self.bullet_width, self.bullet_height)
def update(self):
self.rect.x += 4
side_ship = Sideship()
side_ship.run_game()
The issue is in the loop, which draws the bullets. When you iterate through the bullets, the current element is referenced by bullet. Hence, you have to draw bullet, rather than self.new_bullet:
pygame.draw.rect(self.screen, self.new_bullet.bullet_color, self.new_bullet.rect)
for bullet in self.bullets.sprites():
pygame.draw.rect(self.screen, bullet.bullet_color, bullet.rect)

Pygame - Issues creating projectiles, "add() argument after * must be an iterable, not int"

I am creating an Asteroids type game using Pygame and am having trouble firing projectiles. I have a sprite group for the projectile object and some code to add the projectile.
if pressed[pygame.K_SPACE]:
projectiles.add(Projectile(player.rect.x, player.rect.y, player.direction))
Problem is when I press space in game to fire the projectile I get the error "add() argument after * must be an iterable, not int".
I have a very similar statement for adding asteroids when the game first starts without any issues so I'm not really sure what the problem is. I'll leave the rest of the code below. The add statement giving issues is in the main function near the bottom. Any help is appreciated.
#Import Modules
import pygame
import math
import random
#Movement Function
def calculate_new_xy(old_xy,speed,direction):
new_x = old_xy[0] + (speed*math.cos(direction))
new_y = old_xy[1] + (speed*math.sin(direction))
return new_x, new_y
#Collision Function
def isColliding(x, y, xTo, yTo, size):
if x > xTo - size and x < xTo + size and y > yTo - size and y < yTo + size:
return True
return False
#Draw Text Function
def drawText(msg, color, x, y, s, center=True):
screen_text = pygame.font.SysFont("Impact", s).render(msg, True, color)
if center:
rect = screen_text.get_rect()
rect.center = (x, y-50)
else:
rect = (x, y)
display.blit(screen_text, rect)
#Initialize Variables
#Colors
white = (255, 255, 255)
black = (0, 0, 0)
#Display Height/Width
display_width = 800
display_height = 600
#Asteroid Class
class Asteroid(pygame.sprite.Sprite):
#Initialize values
def __init__(self, pos=(0, 0)):
#Initialize sprite class
pygame.sprite.Sprite.__init__(self)
#Asteroid sprite
self.asteroid = pygame.image.load("asteroid.png").convert()
self.image = self.asteroid
#Rectangle
self.rect = self.image.get_rect()
self.rect.center = pos
#Initialize random starting angle
self.angle = random.randint(0, 360)
#Asteroid random Speed
self.speed = random.randint(2, 3)
#Asteroid random direction
self.direction = math.radians(random.randrange(0, 360, 3))
#Update asteroid object
def update(self):
#Constantly rotate asteroid
self.angle -= 3 % 360
#Get image angle and position
self.image = pygame.transform.rotate(self.asteroid, self.angle*-1)
#Use rectangle to get center of image
#Save ship's current center.
x, y = self.rect.center
#Replace old rect with new rect.
self.rect = self.image.get_rect()
#Put the new rect's center at old center.
self.rect.center = (x, y)
#Move Asteroid
self.rect.center = calculate_new_xy(self.rect.center,self.speed,self.direction)
#Screen Border
#Moves the asteroid to the opposite side of the screen if they go outside the border
if self.rect.x > display_width:
self.rect.x = -20
elif self.rect.x < -20:
self.rect.x = display_width
elif self.rect.y > display_height:
self.rect.y = -20
elif self.rect.y < -20:
self.rect.y = display_height
#Projectile Class
class Projectile(pygame.sprite.Sprite):
#Initialize values
def _init_(self,x,y,direction):
self.x = x
self.y = y
self.dir = direction
self.ttl = 30
#Update projectile object
def update(self):
#Changing direction
self.x += projectilespd * math.cos(self.direction)
self.y += projectilespd * math.sin(self.direction)
#Draw projectile
pygame.draw.circle(display, white, (self.x,self.y),1)
#Screen Border
if self.x > display_width:
self.x = 0
elif self.x < 0:
self.x = display_width
elif self.y > display_height:
self.y = 0
elif self.y < 0:
self.y = display_height
self.ttl -= 1
#Player Class
class Player(pygame.sprite.Sprite):
#Initialize ship sprite, angle lines, and rectangle
def __init__(self, pos=(0, 0), size=(200, 200)):
#Player sprite
self.ship = pygame.image.load("ship.png").convert()
self.image = self.ship
#Rectangle
self.rect = self.image.get_rect()
self.rect.center = pos
#Initialize angle
self.angle = 0
#Initialize direction
self.direction = 0
#Update player object
def update(self):
#Rotation
pressed = pygame.key.get_pressed()
if pressed[pygame.K_LEFT]: self.angle -= 3 % 360
if pressed[pygame.K_RIGHT]: self.angle += 3 % 360
#Get image angle and position
self.image = pygame.transform.rotate(self.ship, self.angle*-1)
#Use rectangle to get center of image
#Save ship's current center.
x, y = self.rect.center
#Replace old rect with new rect.
self.rect = self.image.get_rect()
#Put the new rect's center at old center.
self.rect.center = (x, y)
#Convert angle to radians
self.direction = math.radians(self.angle-90)
#Increase speed if Up is pressed
if pressed[pygame.K_UP]: self.speed = 5
else: self.speed = 0
#Move Ship
self.rect.center = calculate_new_xy(self.rect.center,self.speed,self.direction)
#Screen Border
#Moves the player to the opposite side of the screen if they go outside the border
if self.rect.x > display_width:
self.rect.x = -50
elif self.rect.x < -50:
self.rect.x = display_width
elif self.rect.y > display_height:
self.rect.y = -50
elif self.rect.y < -50:
self.rect.y = display_height
#Main Function
def main(gameState):
#Player starting position
player = Player(pos=(400, 300))
#Asteroid group
asteroids = pygame.sprite.Group()
#Projectile group
projectiles = pygame.sprite.Group()
#Create asteroids
for x in range(8):
asteroids.add(Asteroid(pos=(100 + (x*120), 100 + (x*20))))
while True:
for event in pygame.event.get():
#closes game
if event.type == pygame.QUIT:
done = True
pygame.quit()
exit()
#Game Menu
while gameState == "Menu":
#Fill background
display.fill((0,0,0))
#Display menu text
drawText("ASTEROIDS", white, display_width / 2, display_height / 2, 150)
drawText("Press any key to START", white, display_width / 2, display_height / 2 + 120, 40)
#Check game start or end
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
pygame.quit()
exit()
if event.type == pygame.KEYDOWN:
gameState = "Playing"
pygame.display.update()
#Low frame rate for menu
clock.tick(5)
#Get key inputs
pressed = pygame.key.get_pressed()
#Fill background
display.fill(black)
#Check for player collision with asteroid
for asteroid in asteroids:
if gameState != "Game Over":
if isColliding(player.rect.x, player.rect.y, asteroid.rect.x, asteroid.rect.y, 30):
gameState = "Game Over"
#Update and draw player if not game over
if gameState != "Game Over":
#Update player
player.update()
#Draw player
display.blit(player.image, player.rect)
#Update asteroids
asteroids.update()
#Draw asteroids
asteroids.draw(display)
#Fire Projectiles
if pressed[pygame.K_SPACE]:
projectiles.add(Projectile(player.rect.x, player.rect.y, player.direction))
#Update projectiles
projectiles.update()
#Draw projectiles
projectiles.draw(display)
#Display Game Over and restart option
if gameState == "Game Over":
drawText("GAME OVER", white, display_width / 2, display_height / 2, 150)
drawText("Press R to restart", white, display_width / 2, display_height / 2 + 120, 40)
if pressed[pygame.K_r]:
main(gameState = "Playing")
#Makes updates to the game screen
pygame.display.update()
#Frame rate
clock.tick(60)
#Initialize Game
if __name__ == '__main__':
#initialize pygame
pygame.init()
#initialize display settings
display = pygame.display.set_mode((display_width,display_height))
pygame.display.set_caption('Asteroids')
#initialize game clock
clock = pygame.time.Clock()
#start main function
main(gameState = "Menu")
The issue is not the pygame.sprite.Group.add operation, but the obejct you want to add is not a pygame.sprite.Sprite object, because the object is not constructed at all.
You missed to the super call in the constructor of Projectile. Furthermore the name of the constructor has to be __init__ rather _init_:
class Projectile(pygame.sprite.Sprite):
#Initialize values
def __init__(self,x,y,direction):
super.__init__()
self.x = x
self.y = y
self.dir = direction
self.ttl = 30

Pygame, why does my rectangle not move

I don't get why the rectangle is not changing it's y position when i press the up key. I don't get any errors and everything is showing up.
import pygame
from pygame.locals import *
class SnekHead(object):
def __init__(self, screensize):
self.screensize = screensize
self.center_x = int(screensize[0]*0.5)
self.center_y = int(screensize[1]*0.5)
self.width = 50
self.height = 50
self.rect = pygame.Rect(self.center_x-25, self.center_y-50, self.width, self.height)
self.color = (100, 255, 100)
self.speed = 10
self.direction = 0
def update(self):
self.center_y += self.direction*self.speed
def render(self, screen):
pygame.draw.rect(screen, self.color, self.rect, 0)
def run_game():
pygame.init()
screensize = (640, 480)
background_image = pygame.image.load('Sky_back_layer.png')
screen = pygame.display.set_mode(screensize)
clock = pygame.time.Clock()
snake = SnekHead(screensize)
running = True
while running:
clock.tick(64)
for event in pygame.event.get():
if event.type == KEYDOWN:
if event.key == K_ESCAPE:
running = False
elif event.key == K_UP:
snake.direction = -1
snake.update()
screen.blit(background_image, (0, 0))
snake.render(screen)
pygame.display.flip()
pygame.quit()
run_game()
Ask yourself this question. What is the value of self.rect after the key is pressed? (You decrement snake.direction when the key is pressed, then you update snake.center_y, but snake.rect remains the same and so does the position of the rectangle because that is what you are passing to pygame.draw.rect() in your render() function)

Puzzled by my sprite's unequal +/ - velocity

I have two sprites in my game. The zombie sprite works perfectly, moving in all directions at a velocity of 1.0. My player sprite however, despite moves more slowly in the positive x/y direction, despite all values in all directions being 2.25.
For the life of me I can't seem to see what is wrong here.
Full working code:
import pygame
import random
import sys
import itertools
import math
import time
from datetime import datetime
from librarymodified import *
from pygame.locals import *
# prints text using the supplied font
def print_text(font, x, y, text, color=(255,255,255)):
imgText = font.render(text, True, color)
DISPLAYSURF.blit(imgText, (x,y))
# MySprite class extends pygame.sprite.Sprite
class MySprite(pygame.sprite.Sprite):
def __init__(self, target):
pygame.sprite.Sprite.__init__(self) #extend the base Sprite class
self.master_image = None
self.frame = 0
self.old_frame = -1
self.frame_width = 1
self.frame_height = 1
self.first_frame = 0
self.last_frame = 0
self.columns = 1
self.last_time = 0
self.direction = 0
self.times_hit = 0
self.direction = 0
self.velocity = Point(0.0,0.0)
# times_hit property
def _get_times_hit(self): return self.times_hit
def _set_times_hit(self, hits): self.times_hit += hits
number_hits_taken = property(_get_times_hit, _set_times_hit)
#X property
def _getx(self): return self.rect.x
def _setx(self,value): self.rect.x = value
X = property(_getx,_setx)
#Y property
def _gety(self): return self.rect.y
def _sety(self,value): self.rect.y = value
Y = property(_gety,_sety)
#position property
def _getpos(self): return self.rect.topleft
def _setpos(self,pos): self.rect.topleft = pos
position = property(_getpos,_setpos)
def load(self, filename, width, height, columns):
self.master_image = pygame.image.load(filename).convert_alpha()
self.frame_width = width
self.frame_height = height
self.rect = Rect(0,0,width,height)
self.columns = columns
#try to auto-calculate total frames
rect = self.master_image.get_rect()
self.last_frame = (rect.width // width) * (rect.height // height) - 1
def update(self, current_time, rate=30):
#update animation frame number
if current_time > self.last_time + rate:
self.frame += 1
if self.frame > self.last_frame:
self.frame = self.first_frame
self.last_time = current_time
#build current frame only if it changed
if self.frame != self.old_frame:
frame_x = (self.frame % self.columns) * self.frame_width
frame_y = (self.frame // self.columns) * self.frame_height
rect = Rect(frame_x, frame_y, self.frame_width, self.frame_height)
self.image = self.master_image.subsurface(rect)
self.old_frame = self.frame
def __str__(self):
return str(self.frame) + "," + str(self.first_frame) + \
"," + str(self.last_frame) + "," + str(self.frame_width) + \
"," + str(self.frame_height) + "," + str(self.columns) + \
"," + str(self.rect)
#Point class
class Point(object):
def __init__(self, x, y):
self.__x = x
self.__y = y
#X property
def getx(self): return self.__x
def setx(self, x): self.__x = x
x = property(getx, setx)
#Y property
def gety(self): return self.__y
def sety(self, y): self.__y = y
y = property(gety, sety)
def __str__(self):
return "{X:" + "{:.0f}".format(self.__x) + \
",Y:" + "{:.0f}".format(self.__y) + "}"
def calc_velocity(direction, vel = 1.0):
velocity = Point(0, 0)
if direction == 0: # North
velocity.y = -vel
elif direction == 2: # East
velocity.x = vel
elif direction == 4: # south
velocity.y = vel
elif direction == 6: # west
velocity.x = -vel
return velocity
def reverse_direction(sprite):
if sprite.direction == 0:
sprite.direction = 4
elif sprite.direction == 2:
sprite.direction = 6
elif sprite.direction == 4:
sprite.direction = 0
elif sprite.direction == 6:
sprite.direction = 2
# main
pygame.init()
DISPLAYSURF = pygame.display.set_mode((800,600))
pygame.display.set_caption("Collision Detection")
font = pygame.font.SysFont(None, 36)
fpsclock = pygame.time.Clock()
fps = 30
# create sprite groups
zombie_group = pygame.sprite.Group()
player_group = pygame.sprite.Group()
health_group = pygame.sprite.Group()
# create player sprite
player = MySprite(DISPLAYSURF)
player.load("farmer walk.png", 96, 96, 8)
player.position = (80,80)
player.direction = 4
player_group.add(player)
# create zombie sprite
zombie_image = pygame.image.load("zombie walk.png").convert_alpha()
for i in range(1):
zombie = MySprite(DISPLAYSURF)
zombie.load("zombie walk.png", 96, 96, 8)
zombie.position = (random.randint(0, 700), random.randint(0, 500))
zombie.direction = random.randint(0,3) * 2
zombie_group.add(zombie)
# create health sprite
health = MySprite(DISPLAYSURF)
health.load("health.png", 32, 32, 1)
health.position = (400, 300)
health_group.add(health)
game_over = False
player_moving = False
player_health = 100
# colors
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 0)
YELLOW = (255, 255, 0)
##DISPLAYSURF.fill(BLACK)
##pygame.mouse.set_visible(True)
# event loop
while True:
ticks = pygame.time.get_ticks() # ms since pygame.init() called
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
if event.type == MOUSEMOTION:
mousex, mousey = event.pos
# keyboard polling
keys = pygame.key.get_pressed()
if keys[K_ESCAPE]:
pygame.quit()
sys.exit()
elif keys[K_UP] or keys[K_w]:
player.direction = 0
player_moving = True
elif keys[K_RIGHT] or keys[K_d]:
player.direction = 2
player_moving = True
elif keys[K_LEFT] or keys[K_a]:
player.direction = 6
player_moving = True
elif keys[K_DOWN] or keys[K_s]:
player.direction = 4
player_moving = True
else:
player_moving = False
# these things should not happen if game is over
if not game_over:
# update player sprite
player_group.update(ticks, 50)
# use player direction to calculate frame range
player.first_frame = player.direction * player.columns
player.last_frame = player.first_frame + player.columns-1
if player.frame < player.first_frame:
player.frame = player.first_frame
if not player_moving:
# stop animating when player is not moving
player.frame = player.first_frame = player.last_frame
else:
# move player in that direction
player.velocity = calc_velocity(player.direction, 1.5)
player.velocity.x *= 1.5
player.velocity.y *= 1.5
# manually move player
if player_moving:
player.X += player.velocity.x
player.Y += player.velocity.y
if player.X <0: player.X = 0
elif player.X > 700: player.X = 700
if player.Y <0: player.Y = 0
elif player.Y > 500: player.Y = 500
# update zombie sprites
zombie_group.update(ticks, 50)
# manually update zombies
for z in zombie_group:
# set zombie animation range
z.first_frame = z.direction * z.columns
z.last_frame = z.first_frame + z.columns-1
if z.frame < z.first_frame:
z.frame = z.first_frame
z.velocity = calc_velocity(z.direction)
# keep zombie on screen
z.X += z.velocity.x
z.Y += z.velocity.y
if z.X < 0 or z.X > 700 or z.Y < 0 or z.Y > 500:
reverse_direction(z)
# check for sprite collision
attacker = 0
attacker = pygame.sprite.spritecollideany(player, zombie_group)
if attacker != None:
# more precise check
if pygame.sprite.collide_rect_ratio(0.5)(player, attacker):
player_health -= 10
if attacker.X < player.X: attacker.X -= 10
elif attacker.X > player.X: attacker.X += 10
else:
attacker = None
# update health drop
health_group.update(ticks, 50)
# check for collision with health
if pygame.sprite.collide_rect_ratio(0.5)(player, health):
player_health += 30
if player_health >100: player_health = 100
health.X = random.randint(0, 700)
health.Y = random.randint(0, 500)
# is player dead?
if player_health <= 0:
game_over = True
# clear screen
DISPLAYSURF.fill((50,50,100))
# draw sprites
player_group.draw(DISPLAYSURF)
zombie_group.draw(DISPLAYSURF)
health_group.draw(DISPLAYSURF)
# draw energy bar
pygame.draw.rect(DISPLAYSURF, WHITE, (299, 555, 203, 31), 2)
pygame.draw.rect(DISPLAYSURF, GREEN, (301, 557, player_health * 2, 28))
# print zombie and player velocities for purpose of testing
print_text(font, 350, 460, "Zombie X vel: " +\
str(zombie.velocity.x) + "\nY vel: " +\
str(zombie.velocity.y))
print_text(font, 350, 500, "Player X vel: " +\
str(player.velocity.x) + "\nY vel: " +\
str(player.velocity.y))
if game_over:
print_text(font, 300, 200, "G A M E O V E R")
pygame.display.update()
fpsclock.tick(fps)
Problem
The sprite group is drawing your sprite using the sprite's rect attribute. A pygame Rect object can only hold integers, so it'll truncate all floating point numbers.
Let's say you have a x = 5.
If you add 1.1: x += 1.1 <=> x = x + 1.1 <=> x = 5 + 1.1 <=> x = 6.1 which will be truncated to x = 6. It have increased by 1.
If you subtract 1.1: x -= 1.1 <=> x = x - 1.1 <=> x = 5 - 1.1 <=> x = 3.9 which will be truncated to x = 3. It have decreased by 2.
In other words: You'll move faster in the left direction than the right (the same principle applies to negative numbers). Here's an example demonstrating it:
import pygame
pygame.init()
class Player(pygame.sprite.Sprite):
def __init__(self, group):
super(Player, self).__init__(group)
self.image = pygame.Surface((32, 32))
self.rect = self.image.get_rect()
screen = pygame.display.set_mode((100, 100))
group = pygame.sprite.Group()
player = Player(group)
clock = pygame.time.Clock()
while True:
clock.tick(10)
for event in pygame.event.get():
if event.type == pygame.QUIT:
quit()
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_RIGHT:
x = player.rect.x + 1.1
print("Actual x:", x)
player.rect.x = player.rect.x + 1.1
print("Truncated x:", player.rect.x)
elif event.key == pygame.K_LEFT:
x = player.rect.x - 1.1
print("Actual x:", x)
player.rect.x = player.rect.x - 1.1
print("Truncated x:", player.rect.x)
screen.fill((255, 255, 255))
group.draw(screen)
pygame.display.update()
Solution
Using floating point numbers for position is great; it makes it possible to move a sprite less than a pixel every frame (if your game updates 120 times per second and you want your sprite to move only 30 pixels per second).
However, you have to compensate for the fact that the rect objects cannot hold them. The most straightforward solution is to have an attribute position which keep track of the position of the sprite in floating point precision. Then at every update change the rect to the position of the attribute. Like this:
import pygame
pygame.init()
class Player(pygame.sprite.Sprite):
def __init__(self, group):
super(Player, self).__init__(group)
self.image = pygame.Surface((32, 32))
self.rect = self.image.get_rect()
self.position = self.rect.x # Or whatever point of the rect you want the position to be.
screen = pygame.display.set_mode((100, 100))
group = pygame.sprite.Group()
player = Player(group)
clock = pygame.time.Clock()
while True:
clock.tick(10)
for event in pygame.event.get():
if event.type == pygame.QUIT:
quit()
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_RIGHT:
player.position += 1.1
player.rect.x = player.position
elif event.key == pygame.K_LEFT:
player.position -= 1.1
player.rect.x = player.position
screen.fill((255, 255, 255))
group.draw(screen)
pygame.display.update()
I've only showed how this movement works in the x-axis, but it's exactly the same on the y-axis.
Ok. I think the problem is that the number of pixels that the sprite is moved each update is rounded down, so that 2.25 becomes 2 pixels and -2.25 becomes -3 pixels. Moving by a fractional number of pixels doesn't make sense I think.
If you change lines 229 - 233 to
else:
# move player in that direction
player.velocity = calc_velocity(player.direction, 2.0)
player.velocity.x *= 2.0
player.velocity.y *= 2.0
The velocity is now an integer and there would be no rounding problems. It is faster though. Is there some reason why you don't just have the velocity as an integer instead of a float squared?