How To Move A Sprite According To An Angle In Pygame
Solution 1:
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()
defcalculate_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
classBullet(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
defupdate(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=Truewhile 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()
Solution 2:
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)])
classBullet(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) * 9defupdate(self):
self.pos += self.velocity # Add velocity to pos to move the sprite.
self.rect.center = self.pos # Update rect coords.ifnot screen_rect.contains(self.rect):
self.kill()
defmain():
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.whileTrue:
for event in pg.event.get():
if event.type == pg.QUIT:
returnelif 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 -= 3elif keys[pg.K_d]:
angle += 3if 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)])
classBullet(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)
defupdate(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 = Truewhile 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.Rect
s 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
defupdate(self):
self.pos_x += self.speed_x
self.pos_y += self.speed_y
self.rect.center = (self.pos_x, self.pos_y)
Solution 3:
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
:
defcalculate_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:
classBullet(pygame.sprite.Sprite):
def__init__(self, x, y, direction, speed):
# [...]
self.rect = self.image.get_rect(center = (x, y))
self.pos = (x, y)
defupdate(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
defcalculate_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
classBullet(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
defupdate(self, screen):
self.pos = calculate_new_xy(self.pos, self.speed, -self.direction)
self.rect.center = round(self.pos[0]), round(self.pos[1])
ifnot 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 = 0while 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()
Post a Comment for "How To Move A Sprite According To An Angle In Pygame"