Skip to content Skip to sidebar Skip to footer

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. Wha

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.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

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"