how to create my games spikes like pipes in flappy bird [duplicate] - python

I am making a flappy bird clone game, using pygame. I want to draw pillars by using Sprite.draw. I made a Pillar class and initialized it with two rectangles p_upper and p_lower on the left side of the screen, coming towards the right side with the help of the update function of the sprite. But the screen is only showing the p_lower pillar. Can anyone help?
class Pillar(pygame.sprite.Sprite):
# the "h" parameter is height of upper pillar upto gap
# "w" is the width of the pillar
# pillar is coming from left to right
def __init__(self, w, h, gap):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((w, h))
self.image.fill(green)
self.p_upper = self.rect = self.image.get_rect()
self.p_upper.topleft = (-w, 0)
self.image = pygame.Surface((w, HEIGHT - (h + gap)))
self.image.fill(green)
self.p_lower = self.rect = self.image.get_rect()
self.p_lower.topleft = (-w, h + gap)
def update(self):
self.p_upper.x += 1
self.p_lower.x += 1

Because of the following two lines:
self.p_upper = self.rect = self.image.get_rect()
and...
self.p_lower = self.rect = self.image.get_rect()
These are both grabbing the same reference to the self.rect. The first line runs and assigns the rect reference to p_upper. Then the same reference is assigned to p_lower. Because it's the same reference, when you update the location of the lower rectangle, you're actually updating both.

Using a sprite that consists of two rects and images isn't a good solution for this problem. I suggest to create two separate sprites with their own image and rect. To create two sprite instances at the same time and to add them to a sprite group, you can write a short function as you can see in this example:
import pygame as pg
from pygame.math import Vector2
green = pg.Color('green')
HEIGHT = 480
class Pillar(pg.sprite.Sprite):
def __init__(self, x, y, w, h):
pg.sprite.Sprite.__init__(self)
self.image = pg.Surface((w, h))
self.image.fill(green)
self.rect = self.image.get_rect(topleft=(x, y))
def update(self):
self.rect.x += 1
def create_pillars(w, h, gap, sprite_group):
sprite_group.add(Pillar(0, 0, w, h-gap))
sprite_group.add(Pillar(0, HEIGHT-(h+gap), w, h+gap))
def main():
screen = pg.display.set_mode((640, 480))
clock = pg.time.Clock()
all_sprites = pg.sprite.Group()
create_pillars(50, 170, 0, all_sprites)
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:
create_pillars(50, 170, 15, all_sprites)
elif event.key == pg.K_s:
create_pillars(50, 170, 30, all_sprites)
elif event.key == pg.K_d:
create_pillars(50, 100, -60, all_sprites)
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()

Related

How do I make a image move towards another? [duplicate]

I'm having trouble with moving sprites. I can move them in the x and y axis with no problem at all. What I can't figure out is how can I move them according to a certain angle. What I mean is, I'm trying to create a function which includes the object I'm trying to move, its speed, and its direction(which should be measured in degrees). Something like:
MovingObject(obj,speed,direction) #this is the function I'm trying to define
It's more like a "spawning function" rather than just movement... Take for example that I would like to create an object of the class "Bullet" and want it to follow certain direction (different from the x and y axis, of course)
Actually I have no clear idea of how to do such thing and I would like some advice in order to achieve so.
Thanks for reading this!
EDIT:
#Joran Beasley I tried to do what you told me...but I guess I did it wrong...
import pygame, math, time
screen=pygame.display.set_mode((320,240))
clock=pygame.time.Clock()
pygame.init()
def calculate_new_xy(old_xy,speed,angle_in_radians):
new_x = old_xy.x + (speed*math.cos(angle_in_radians))
new_y = old_xy.y + (speed*math.sin(angle_in_radians))
return new_x, new_y
class Bullet(pygame.sprite.Sprite):
def __init__(self,x,y,direction,speed):
pygame.sprite.Sprite.__init__(self)
self.image=pygame.Surface((16, 16))
self.image.fill((255,0,0))
self.rect=self.image.get_rect()
self.rect.center=(x,y)
self.direction=math.radians(direction)
self.speed=speed
def update(self):
self.rect.center=calculate_new_xy(self.rect,self.speed,self.direction)
spr=pygame.sprite.Group()
bullet=Bullet(160,120,45,1); spr.add(bullet)
play=True
while play:
clock.tick(60)
for ev in pygame.event.get():
if ev.type == pygame.QUIT:
play=False
screen.fill((0,0,0))
spr.update()
spr.draw(screen)
pygame.display.flip()
pygame.quit()
The object moves but... not in the specified direction...
you just need a little basic trig
def calculat_new_xy(old_xy,speed,angle_in_radians):
new_x = old_xy.X + (speed*math.cos(angle_in_radians))
new_y = old_xy.Y + (speed*math.sin(angle_in_radians))
return new_x, new_y
--- edit ---
Here is your code from above edited to work
import pygame, math, time
screen=pygame.display.set_mode((320,240))
clock=pygame.time.Clock()
pygame.init()
def calculate_new_xy(old_xy,speed,angle_in_radians):
new_x = old_xy[0] + (speed*math.cos(angle_in_radians))
new_y = old_xy[1] + (speed*math.sin(angle_in_radians))
return new_x, new_y
class Bullet(pygame.sprite.Sprite):
def __init__(self,x,y,direction,speed):
pygame.sprite.Sprite.__init__(self)
self.image=pygame.Surface((16, 16))
self.image.fill((255,0,0))
self.rect=self.image.get_rect()
self.rect.center=(x,y)
self.direction=math.radians(direction)
self.speed=speed
def update(self):
self.rect.center=calculate_new_xy(self.rect.center,self.speed,self.direction)
spr=pygame.sprite.Group()
bullet=Bullet(160,120,45,2); spr.add(bullet)
play=True
while play:
clock.tick(60)
for ev in pygame.event.get():
if ev.type == pygame.QUIT:
play=False
screen.fill((0,0,0))
spr.update()
spr.draw(screen)
pygame.display.flip()
pygame.quit()
I recommend to use vectors. To get the velocity, rotate the start direction vector Vector2(1, 0) by the angle and multiply it by the desired speed. Then just add this velocity vector to the position vector in the update method and also update the rect position to move the sprite. (Press 'a' or 'd' to rotate, mouse 1 or space to shoot.)
import pygame as pg
from pygame.math import Vector2
pg.init()
screen = pg.display.set_mode((640, 480))
screen_rect = screen.get_rect()
FONT = pg.font.Font(None, 24)
BULLET_IMAGE = pg.Surface((20, 11), pg.SRCALPHA)
pg.draw.polygon(BULLET_IMAGE, pg.Color('aquamarine1'),
[(0, 0), (20, 5), (0, 11)])
class Bullet(pg.sprite.Sprite):
def __init__(self, pos, angle):
super().__init__()
self.image = pg.transform.rotate(BULLET_IMAGE, -angle)
self.rect = self.image.get_rect(center=pos)
# To apply an offset to the start position,
# create another vector and rotate it as well.
offset = Vector2(40, 0).rotate(angle)
# Then add the offset vector to the position vector.
self.pos = Vector2(pos) + offset # Center of the sprite.
# Rotate the direction vector (1, 0) by the angle.
# Multiply by desired speed.
self.velocity = Vector2(1, 0).rotate(angle) * 9
def update(self):
self.pos += self.velocity # Add velocity to pos to move the sprite.
self.rect.center = self.pos # Update rect coords.
if not screen_rect.contains(self.rect):
self.kill()
def main():
clock = pg.time.Clock()
cannon_img = pg.Surface((40, 20), pg.SRCALPHA)
cannon_img.fill(pg.Color('aquamarine3'))
cannon = cannon_img.get_rect(center=(320, 240))
angle = 0
bullet_group = pg.sprite.Group() # Add bullets to this group.
while True:
for event in pg.event.get():
if event.type == pg.QUIT:
return
elif event.type == pg.MOUSEBUTTONDOWN:
# Left button fires a bullet from center with
# current angle. Add the bullet to the bullet_group.
if event.button == 1:
bullet_group.add(Bullet(cannon.center, angle))
keys = pg.key.get_pressed()
if keys[pg.K_a]:
angle -= 3
elif keys[pg.K_d]:
angle += 3
if keys[pg.K_SPACE]:
bullet_group.add(Bullet(cannon.center, angle))
# Rotate the cannon image.
rotated_cannon_img = pg.transform.rotate(cannon_img, -angle)
cannon = rotated_cannon_img.get_rect(center=cannon.center)
bullet_group.update()
# Draw
screen.fill((30, 40, 50))
screen.blit(rotated_cannon_img, cannon)
bullet_group.draw(screen)
txt = FONT.render('angle {:.1f}'.format(angle), True, (150, 150, 170))
screen.blit(txt, (10, 10))
pg.display.update()
clock.tick(30)
if __name__ == '__main__':
main()
pg.quit()
Regarding the code in your added example, the easiest solution is to calculate the speed_x and speed_y ("velocity" would be more fitting) in the __init__ method and then just update the self.rect.x and y attributes in the update method.
import math
import pygame
pygame.init()
screen = pygame.display.set_mode((640, 480))
clock = pygame.time.Clock()
BULLET_IMAGE = pygame.Surface((20, 11), pygame.SRCALPHA)
pygame.draw.polygon(BULLET_IMAGE, pygame.Color('aquamarine1'),
[(0, 0), (20, 5), (0, 11)])
class Bullet(pygame.sprite.Sprite):
def __init__(self, x, y, angle, speed):
pygame.sprite.Sprite.__init__(self)
# Rotate the bullet image (negative angle because y-axis is flipped).
self.image = pygame.transform.rotate(BULLET_IMAGE, -angle)
self.rect = self.image.get_rect(center=(x, y))
angle = math.radians(angle)
self.speed_x = speed * math.cos(angle)
self.speed_y = speed * math.sin(angle)
def update(self):
self.rect.x += self.speed_x
self.rect.y += self.speed_y
spr = pygame.sprite.Group()
bullet = Bullet(10, 10, 60, 3)
bullet2 = Bullet(10, 10, 30, 3)
spr.add(bullet, bullet2)
play = True
while play:
clock.tick(60)
for ev in pygame.event.get():
if ev.type == pygame.QUIT:
play = False
screen.fill((30,30,40))
spr.update()
spr.draw(screen)
pygame.display.flip()
pygame.quit()
There's a problem, because pygame.Rects can only have ints as the x and y attributes, so the movement won't be 100% correct. To solve this problem, you need to store the coords/position of the sprite in separate variables, add the speed to them and afterwards update the rect:
# In `__init__`.
self.pos_x = x
self.pos_y = y
def update(self):
self.pos_x += self.speed_x
self.pos_y += self.speed_y
self.rect.center = (self.pos_x, self.pos_y)
If you are using Pygame, I suggest to use pygame.math.Vector2 for this task. Set a direction vector from its Polar coordinates (speed, angle_in_degrees) with from_polar(). Add this vector to the current position of the bullet. This is more comprehensible than using sin and cos:
def calculate_new_xy(old_xy, speed, angle_in_degrees):
move_vec = pygame.math.Vector2()
move_vec.from_polar((speed, angle_in_degrees))
return old_xy + move_vec
Be aware, that a pygame.Rect can only store integer coordinates. This is because a pygame.Rect is supposed to represent an area on the screen:
The coordinates for Rect objects are all integers. [...]
If you want the bullet to go straight in a certain direction at an angle that is not divisible by 45 °, you must store object positions with floating point accuracy. You have to store the location of the object in separate variables respectively attributes and to synchronize the pygame.Rect object. round the coordinates and assign it to the location (e.g. .topleft) of the rectangle:
class Bullet(pygame.sprite.Sprite):
def __init__(self, x, y, direction, speed):
# [...]
self.rect = self.image.get_rect(center = (x, y))
self.pos = (x, y)
def update(self, screen):
self.pos = calculate_new_xy(self.pos, self.speed, -self.direction)
self.rect.center = round(self.pos[0]), round(self.pos[1])
See also Move towards target
Minimal example
import pygame
def calculate_new_xy(old_xy, speed, angle_in_degrees):
move_vec = pygame.math.Vector2()
move_vec.from_polar((speed, angle_in_degrees))
return old_xy + move_vec
class Bullet(pygame.sprite.Sprite):
def __init__(self, x, y, direction, speed):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((16, 8), pygame.SRCALPHA)
self.image.fill((255, 0, 0))
self.image = pygame.transform.rotate(self.image, direction)
self.rect = self.image.get_rect(center = (x, y))
self.pos = (x, y)
self.direction = direction
self.speed = speed
def update(self, screen):
self.pos = calculate_new_xy(self.pos, self.speed, -self.direction)
self.rect.center = round(self.pos[0]), round(self.pos[1])
if not screen.get_rect().colliderect(self.rect):
self.kill()
pygame.init()
screen = pygame.display.set_mode((320,240))
clock = pygame.time.Clock()
spr = pygame.sprite.Group()
play = True
frame_count = 0
while play:
clock.tick(60)
for ev in pygame.event.get():
if ev.type == pygame.QUIT:
play = False
spr.update(screen)
if (frame_count % 10) == 0:
spr.add(Bullet(*screen.get_rect().center, frame_count, 2))
frame_count += 1
screen.fill((0,0,0))
spr.draw(screen)
pygame.draw.circle(screen, (64, 128, 255), screen.get_rect().center, 10)
pygame.display.flip()
pygame.quit()
exit()

How do I fix movement in my pygame script?

I am trying to make a replica for pong in pygame for my first project but when I try to move my paddles they stretch instead. I believe the reason is it creates a new rect every time I try to move it but I can't seem to figure out why. Please review the code and help rectify my mistake.
Here is my code:
import pygame
W, H = 600, 500
screen = pygame.display.set_mode((W, H))
FPS = 30
class Paddle(pygame.sprite.Sprite):
def __init__(self, x, y, width, height):
super(Paddle, self).__init__()
self.x = x
self.y = y
self.width = width
self.height = height
self.surf = pygame.Surface((width, height))
self.surf.fill((255, 255, 255))
self.rect = self.surf.get_rect()
self.rect.center = (x, y)
def move(self, distance):
self.rect.move_ip(0, distance)
paddleA = Paddle(15, 250, 10, 50)
paddleB = Paddle(585, 250, 10, 50)
allSprites = pygame.sprite.Group()
allSprites.add(paddleA)
allSprites.add(paddleB)
run = True
clock = pygame.time.Clock()
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
run = False
pressedKeys = pygame.key.get_pressed()
if pressedKeys[pygame.K_UP]:
paddleB.move(-5)
elif pressedKeys[pygame.K_DOWN]:
paddleB.move(5)
elif pressedKeys[pygame.K_w]:
paddleA.move(-5)
elif pressedKeys[pygame.K_s]:
paddleA.move(5)
for sprite in allSprites:
screen.blit(sprite.surf, sprite.rect)
pygame.display.update()
clock.tick(FPS)
pygame.quit()
quit()
Before drawing the new rect you should fill the screen with the background color, to remove the old rect. Otherwise the old one is still drawn there and you are just drawing new over the old one. Its' like painting a new picture on an old one.
screen.fill(color, rect) should do the trick.

How to animate multiple objects of the same class?

I want to have 3 workers (objects of the class Worker) that move on the screen in different random directions and with a different speed. In other words, I just want to run something like this: worker1.makeRandomStep(x,y,1), worker2.makeRandomStep(x,y,2) and worker1.makeRandomStep(x,y,3).
This is my current code in which I have only one worker:
import pygame, random
import sys
WHITE = (255, 255, 255)
GREEN = (20, 255, 140)
GREY = (210, 210 ,210)
RED = (255, 0, 0)
PURPLE = (255, 0, 255)
SCREENWIDTH=1000
SCREENHEIGHT=578
class Background(pygame.sprite.Sprite):
def __init__(self, image_file, location):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load(image_file)
self.rect = self.image.get_rect()
self.rect.left, self.rect.top = location
class Worker(pygame.sprite.Sprite):
def __init__(self, image_file, location):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load(image_file)
self.rect = self.image.get_rect()
self.direction = 2
self.rect.left, self.rect.top = location
def makeRandomStep(self,x,y,step):
# there is a less than 1% chance every time that direction is changed
if random.uniform(0,1)<0.005:
self.direction = random.randint(1,4)
if self.direction == 1:
return x, y+step # up
elif self.direction == 2:
return x+step, y # right
elif self.direction == 3:
return x, y-step # down
elif self.direction == 4:
return x-step, y # left
else:
return x, y # stop
pygame.init()
size = (SCREENWIDTH, SCREENHEIGHT)
screen = pygame.display.set_mode(size)
screen_rect=screen.get_rect()
pygame.display.set_caption("TEST")
worker = Worker("worker.png", [0,0])
w_x = worker.rect.left
w_y = worker.rect.top
bg = Background("background.jpg", [0,0])
#sprite_group = pygame.sprite.Group()
#sprite_group.add(worker)
carryOn = True
clock=pygame.time.Clock()
while carryOn:
for event in pygame.event.get():
if event.type==pygame.QUIT:
carryOn=False
pygame.display.quit()
pygame.quit()
quit()
# Draw floor layout
screen.blit(pygame.transform.scale(bg.image, (SCREENWIDTH, SCREENHEIGHT)), bg.rect)
# Draw geo-fences
geofence1 = pygame.draw.rect(screen, GREEN, [510,150,75,52])
geofence2 = pygame.draw.rect(screen, GREEN, [450,250,68,40])
w_x,w_y = worker.makeRandomStep(w_x,w_y,1)
# Worker should not go outside the screen area
if (w_x + worker.rect.width > SCREENWIDTH): w_x = SCREENWIDTH - worker.rect.width
if (w_x < 0): w_x = 0
if (w_y + worker.rect.height > SCREENHEIGHT): w_y = SCREENHEIGHT - worker.rect.height
if (w_y < 0): w_y = 0
worker.rect.clamp_ip(screen_rect)
screen.blit(worker.image, (w_x,w_y))
# Refresh Screen
pygame.display.flip()
#sprite_group.update()
#sprite_group.draw(screen)
#Number of frames per secong e.g. 60
clock.tick(20)
pygame.display.quit()
pygame.quit()
quit()
It is not very clear to me how should I proceed to my goal. I was thinking to use sprite_group, but I misunderstand how to correctly update all sprites (workers) inside the while loop.
Any help is highly appreciated. Thanks.
You have to move all worker logic into the Worker class (setting random values, updating position etc). Then create multiple instances.
Here's a running example. Note the comments for explanations:
import pygame, random
import sys
WHITE = (255, 255, 255)
GREEN = (20, 255, 140)
GREY = (210, 210 ,210)
RED = (255, 0, 0)
PURPLE = (255, 0, 255)
SCREENWIDTH=1000
SCREENHEIGHT=578
class Background(pygame.sprite.Sprite):
def __init__(self, image_file, location, *groups):
# we set a _layer attribute before adding this sprite to the sprite groups
# we want the background to be actually in the back
self._layer = -1
pygame.sprite.Sprite.__init__(self, groups)
# let's resize the background image now and only once
# always call convert() (or convert_alpha) after loading an image
# so the surface will have to correct pixel format
self.image = pygame.transform.scale(pygame.image.load(image_file).convert(), (SCREENWIDTH, SCREENHEIGHT))
self.rect = self.image.get_rect(topleft=location)
# we do everthing with sprites now, because that will make our live easier
class GeoFence(pygame.sprite.Sprite):
def __init__(self, rect, *groups):
# we set a _layer attribute before adding this sprite to the sprite groups
self._layer = 0
pygame.sprite.Sprite.__init__(self, groups)
self.image = pygame.surface.Surface((rect.width, rect.height))
self.image.fill(GREEN)
self.rect = rect
class Worker(pygame.sprite.Sprite):
def __init__(self, image_file, location, *groups):
# we set a _layer attribute before adding this sprite to the sprite groups
# we want the workers on top
self._layer = 1
pygame.sprite.Sprite.__init__(self, groups)
self.image = pygame.transform.scale(pygame.image.load(image_file).convert_alpha(), (40, 40))
self.rect = self.image.get_rect(topleft=location)
# let's call this handy function to set a random direction for the worker
self.change_direction()
# speed is also random
self.speed = random.randint(2, 4)
def change_direction(self):
# let's create a random vector as direction, so we can move in every direction
self.direction = pygame.math.Vector2(random.randint(-100, 100), random.randint(-100, 100))
# we don't want a vector of length 0, because we want to actually move
# it's not enough to account for rounding errors, but let's ignore that for now
while self.direction.length() == 0:
self.direction = pygame.math.Vector2(random.randint(-100, 100), random.randint(-100, 100))
# always normalize the vector, so we always move at a constant speed at all directions
self.direction = self.direction.normalize()
def update(self, screen):
# there is a less than 1% chance every time that direction is changed
if random.uniform(0,1)<0.005:
self.change_direction()
# now let's multiply our direction with our speed and move the rect
vec = [int(v) for v in self.direction * self.speed]
self.rect.move_ip(*vec)
# if we're going outside the screen, move back and change direction
if not screen.get_rect().contains(self.rect):
self.change_direction()
self.rect.clamp_ip(screen.get_rect())
pygame.init()
# currently, one group would be enough
# but if you want to use some collision handling in the future
# it's best to group all sprites into special groups (no pun intended)
all_sprites = pygame.sprite.LayeredUpdates()
workers = pygame.sprite.Group()
fences = pygame.sprite.Group()
screen = pygame.display.set_mode((SCREENWIDTH, SCREENHEIGHT))
pygame.display.set_caption("TEST")
# create multiple workers
for pos in ((0,0), (100, 100), (200, 100)):
Worker("worker.png", pos, all_sprites, workers)
# create multiple of these green thingies
for rect in (pygame.Rect(510,150,75,52), pygame.Rect(450,250,68,40)):
GeoFence(rect, all_sprites, fences)
# and the background
Background("background.jpg", [0,0], all_sprites)
carryOn = True
clock = pygame.time.Clock()
while carryOn:
for event in pygame.event.get():
if event.type==pygame.QUIT:
carryOn = False
# see how clean our main loop is
# just calling update and draw on the all_sprites group
all_sprites.update(screen)
all_sprites.draw(screen)
pygame.display.flip()
clock.tick(20)

How do you move a sprite in pygame?

I know that this question has already been answered in other forms, but I can't seem to make it work for my game. I'm trying to make the sprite move and keep him in the place he moved to. This is part of my code:
class player(pygame.sprite.Sprite):
def __init__(self, x, y):
pygame.sprite.Sprite.__init__(self)
def draw(self, x, y):
player = pygame.image.load("stand_down.png").convert_alpha()
player = pygame.transform.scale(player, (100, 200))
screen.blit(player, (x, y))
pygame.display.update()
for event in pygame.event.get():
key = pygame.key.get_pressed()
if event.type == KEYDOWN :
if key [K_LEFT] :
player = pygame.image.load("standleft.png").convert_alpha()
player = pygame.transform.scale(player, (100, 200))
x -= 50
screen.blit(player, (x, y))
pygame.display.update()
pygame.sprite.Sprite.update(player)
if key [K_RIGHT]:
player = pygame.image.load("standright.png").convert_alpha()
player = pygame.transform.scale(player, (100, 200))
x += 50
screen.blit(player, (x, y))
pygame.display.update()
pygame.sprite.Sprite.update(player)
def update(self):
pygame.sprite.Sprite.update(player)
And then this is part of my main function:
player.draw(screen, x, y)
player.update(screen)
I've tried everything I can think of, but the player just keeps flashing at a different point, then returning to the previous position. If you could help, it would be much appreciated!
I can't test it but I would do this
# --- classes ---
class Player(pygame.sprite.Sprite): # UpperCaseName for class
def __init__(self, x, y):
super().__init__()
temp_image = pygame.image.load("stand_down.png").convert_alpha()
self.image_down = pygame.transform.scale(temp_image, (100, 200))
temp_image = pygame.image.load("standleft.png").convert_alpha()
self.image_left = pygame.transform.scale(temp_image, (100, 200))
temp_image = pygame.image.load("standright.png").convert_alpha()
self.image_right = pygame.transform.scale(temp_image, (100, 200))
self.image = self.image_down
# keep position and size in pygame.Rect()
# to use it in collision checking
self.rect = self.image.get_rect(x=x, y=y)
def draw(self, screen):
screen.blit(self.image, self.rect)
def handle_event(self):#, event)
self.image = self.image_down
key = pygame.key.get_pressed()
if key[K_LEFT]:
self.rect.x -= 50
self.image = self.image_left
if key[K_RIGHT]:
self.rect.x += 50
self.image = self.image_right
def update(self, enemy):
# check collision with eneny
if self.rect.colliderect(enemy.rect):
print("I'm killed by enemy")
# --- main ---
player = Player(400, 400)
enemy = Enemy(100, 100)
while True:
# - events -
for event in pygame.event.get():
#player.handle_event(event)
player.handle_event()
# - updates -
enemy.update()
player.update(enemy)
# - draws -
screen.fill( (0,0,0) )
player.draw(screen)
enemy.draw(screen)
pygame.display.update()

How to move a sprite according to an angle in Pygame

I'm having trouble with moving sprites. I can move them in the x and y axis with no problem at all. What I can't figure out is how can I move them according to a certain angle. What I mean is, I'm trying to create a function which includes the object I'm trying to move, its speed, and its direction(which should be measured in degrees). Something like:
MovingObject(obj,speed,direction) #this is the function I'm trying to define
It's more like a "spawning function" rather than just movement... Take for example that I would like to create an object of the class "Bullet" and want it to follow certain direction (different from the x and y axis, of course)
Actually I have no clear idea of how to do such thing and I would like some advice in order to achieve so.
Thanks for reading this!
EDIT:
#Joran Beasley I tried to do what you told me...but I guess I did it wrong...
import pygame, math, time
screen=pygame.display.set_mode((320,240))
clock=pygame.time.Clock()
pygame.init()
def calculate_new_xy(old_xy,speed,angle_in_radians):
new_x = old_xy.x + (speed*math.cos(angle_in_radians))
new_y = old_xy.y + (speed*math.sin(angle_in_radians))
return new_x, new_y
class Bullet(pygame.sprite.Sprite):
def __init__(self,x,y,direction,speed):
pygame.sprite.Sprite.__init__(self)
self.image=pygame.Surface((16, 16))
self.image.fill((255,0,0))
self.rect=self.image.get_rect()
self.rect.center=(x,y)
self.direction=math.radians(direction)
self.speed=speed
def update(self):
self.rect.center=calculate_new_xy(self.rect,self.speed,self.direction)
spr=pygame.sprite.Group()
bullet=Bullet(160,120,45,1); spr.add(bullet)
play=True
while play:
clock.tick(60)
for ev in pygame.event.get():
if ev.type == pygame.QUIT:
play=False
screen.fill((0,0,0))
spr.update()
spr.draw(screen)
pygame.display.flip()
pygame.quit()
The object moves but... not in the specified direction...
you just need a little basic trig
def calculat_new_xy(old_xy,speed,angle_in_radians):
new_x = old_xy.X + (speed*math.cos(angle_in_radians))
new_y = old_xy.Y + (speed*math.sin(angle_in_radians))
return new_x, new_y
--- edit ---
Here is your code from above edited to work
import pygame, math, time
screen=pygame.display.set_mode((320,240))
clock=pygame.time.Clock()
pygame.init()
def calculate_new_xy(old_xy,speed,angle_in_radians):
new_x = old_xy[0] + (speed*math.cos(angle_in_radians))
new_y = old_xy[1] + (speed*math.sin(angle_in_radians))
return new_x, new_y
class Bullet(pygame.sprite.Sprite):
def __init__(self,x,y,direction,speed):
pygame.sprite.Sprite.__init__(self)
self.image=pygame.Surface((16, 16))
self.image.fill((255,0,0))
self.rect=self.image.get_rect()
self.rect.center=(x,y)
self.direction=math.radians(direction)
self.speed=speed
def update(self):
self.rect.center=calculate_new_xy(self.rect.center,self.speed,self.direction)
spr=pygame.sprite.Group()
bullet=Bullet(160,120,45,2); spr.add(bullet)
play=True
while play:
clock.tick(60)
for ev in pygame.event.get():
if ev.type == pygame.QUIT:
play=False
screen.fill((0,0,0))
spr.update()
spr.draw(screen)
pygame.display.flip()
pygame.quit()
I recommend to use vectors. To get the velocity, rotate the start direction vector Vector2(1, 0) by the angle and multiply it by the desired speed. Then just add this velocity vector to the position vector in the update method and also update the rect position to move the sprite. (Press 'a' or 'd' to rotate, mouse 1 or space to shoot.)
import pygame as pg
from pygame.math import Vector2
pg.init()
screen = pg.display.set_mode((640, 480))
screen_rect = screen.get_rect()
FONT = pg.font.Font(None, 24)
BULLET_IMAGE = pg.Surface((20, 11), pg.SRCALPHA)
pg.draw.polygon(BULLET_IMAGE, pg.Color('aquamarine1'),
[(0, 0), (20, 5), (0, 11)])
class Bullet(pg.sprite.Sprite):
def __init__(self, pos, angle):
super().__init__()
self.image = pg.transform.rotate(BULLET_IMAGE, -angle)
self.rect = self.image.get_rect(center=pos)
# To apply an offset to the start position,
# create another vector and rotate it as well.
offset = Vector2(40, 0).rotate(angle)
# Then add the offset vector to the position vector.
self.pos = Vector2(pos) + offset # Center of the sprite.
# Rotate the direction vector (1, 0) by the angle.
# Multiply by desired speed.
self.velocity = Vector2(1, 0).rotate(angle) * 9
def update(self):
self.pos += self.velocity # Add velocity to pos to move the sprite.
self.rect.center = self.pos # Update rect coords.
if not screen_rect.contains(self.rect):
self.kill()
def main():
clock = pg.time.Clock()
cannon_img = pg.Surface((40, 20), pg.SRCALPHA)
cannon_img.fill(pg.Color('aquamarine3'))
cannon = cannon_img.get_rect(center=(320, 240))
angle = 0
bullet_group = pg.sprite.Group() # Add bullets to this group.
while True:
for event in pg.event.get():
if event.type == pg.QUIT:
return
elif event.type == pg.MOUSEBUTTONDOWN:
# Left button fires a bullet from center with
# current angle. Add the bullet to the bullet_group.
if event.button == 1:
bullet_group.add(Bullet(cannon.center, angle))
keys = pg.key.get_pressed()
if keys[pg.K_a]:
angle -= 3
elif keys[pg.K_d]:
angle += 3
if keys[pg.K_SPACE]:
bullet_group.add(Bullet(cannon.center, angle))
# Rotate the cannon image.
rotated_cannon_img = pg.transform.rotate(cannon_img, -angle)
cannon = rotated_cannon_img.get_rect(center=cannon.center)
bullet_group.update()
# Draw
screen.fill((30, 40, 50))
screen.blit(rotated_cannon_img, cannon)
bullet_group.draw(screen)
txt = FONT.render('angle {:.1f}'.format(angle), True, (150, 150, 170))
screen.blit(txt, (10, 10))
pg.display.update()
clock.tick(30)
if __name__ == '__main__':
main()
pg.quit()
Regarding the code in your added example, the easiest solution is to calculate the speed_x and speed_y ("velocity" would be more fitting) in the __init__ method and then just update the self.rect.x and y attributes in the update method.
import math
import pygame
pygame.init()
screen = pygame.display.set_mode((640, 480))
clock = pygame.time.Clock()
BULLET_IMAGE = pygame.Surface((20, 11), pygame.SRCALPHA)
pygame.draw.polygon(BULLET_IMAGE, pygame.Color('aquamarine1'),
[(0, 0), (20, 5), (0, 11)])
class Bullet(pygame.sprite.Sprite):
def __init__(self, x, y, angle, speed):
pygame.sprite.Sprite.__init__(self)
# Rotate the bullet image (negative angle because y-axis is flipped).
self.image = pygame.transform.rotate(BULLET_IMAGE, -angle)
self.rect = self.image.get_rect(center=(x, y))
angle = math.radians(angle)
self.speed_x = speed * math.cos(angle)
self.speed_y = speed * math.sin(angle)
def update(self):
self.rect.x += self.speed_x
self.rect.y += self.speed_y
spr = pygame.sprite.Group()
bullet = Bullet(10, 10, 60, 3)
bullet2 = Bullet(10, 10, 30, 3)
spr.add(bullet, bullet2)
play = True
while play:
clock.tick(60)
for ev in pygame.event.get():
if ev.type == pygame.QUIT:
play = False
screen.fill((30,30,40))
spr.update()
spr.draw(screen)
pygame.display.flip()
pygame.quit()
There's a problem, because pygame.Rects can only have ints as the x and y attributes, so the movement won't be 100% correct. To solve this problem, you need to store the coords/position of the sprite in separate variables, add the speed to them and afterwards update the rect:
# In `__init__`.
self.pos_x = x
self.pos_y = y
def update(self):
self.pos_x += self.speed_x
self.pos_y += self.speed_y
self.rect.center = (self.pos_x, self.pos_y)
If you are using Pygame, I suggest to use pygame.math.Vector2 for this task. Set a direction vector from its Polar coordinates (speed, angle_in_degrees) with from_polar(). Add this vector to the current position of the bullet. This is more comprehensible than using sin and cos:
def calculate_new_xy(old_xy, speed, angle_in_degrees):
move_vec = pygame.math.Vector2()
move_vec.from_polar((speed, angle_in_degrees))
return old_xy + move_vec
Be aware, that a pygame.Rect can only store integer coordinates. This is because a pygame.Rect is supposed to represent an area on the screen:
The coordinates for Rect objects are all integers. [...]
If you want the bullet to go straight in a certain direction at an angle that is not divisible by 45 °, you must store object positions with floating point accuracy. You have to store the location of the object in separate variables respectively attributes and to synchronize the pygame.Rect object. round the coordinates and assign it to the location (e.g. .topleft) of the rectangle:
class Bullet(pygame.sprite.Sprite):
def __init__(self, x, y, direction, speed):
# [...]
self.rect = self.image.get_rect(center = (x, y))
self.pos = (x, y)
def update(self, screen):
self.pos = calculate_new_xy(self.pos, self.speed, -self.direction)
self.rect.center = round(self.pos[0]), round(self.pos[1])
See also Move towards target
Minimal example
import pygame
def calculate_new_xy(old_xy, speed, angle_in_degrees):
move_vec = pygame.math.Vector2()
move_vec.from_polar((speed, angle_in_degrees))
return old_xy + move_vec
class Bullet(pygame.sprite.Sprite):
def __init__(self, x, y, direction, speed):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((16, 8), pygame.SRCALPHA)
self.image.fill((255, 0, 0))
self.image = pygame.transform.rotate(self.image, direction)
self.rect = self.image.get_rect(center = (x, y))
self.pos = (x, y)
self.direction = direction
self.speed = speed
def update(self, screen):
self.pos = calculate_new_xy(self.pos, self.speed, -self.direction)
self.rect.center = round(self.pos[0]), round(self.pos[1])
if not screen.get_rect().colliderect(self.rect):
self.kill()
pygame.init()
screen = pygame.display.set_mode((320,240))
clock = pygame.time.Clock()
spr = pygame.sprite.Group()
play = True
frame_count = 0
while play:
clock.tick(60)
for ev in pygame.event.get():
if ev.type == pygame.QUIT:
play = False
spr.update(screen)
if (frame_count % 10) == 0:
spr.add(Bullet(*screen.get_rect().center, frame_count, 2))
frame_count += 1
screen.fill((0,0,0))
spr.draw(screen)
pygame.draw.circle(screen, (64, 128, 255), screen.get_rect().center, 10)
pygame.display.flip()
pygame.quit()
exit()

Categories