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?