如何在Pygame中根据角度移动精灵

4

我在移动精灵方面遇到了麻烦。我可以轻松地在x和y轴上移动它们。但问题是,我不知道如何根据特定的角度来移动它们。我的意思是,我正在尝试创建一个包含要移动的对象、速度和方向(应以度数为单位测量)的函数。就像这样:

MovingObject(obj,speed,direction) #this is the function I'm trying to define

它更像是一个“生成函数”,而不仅仅是移动...例如,我想创建一个类“子弹”的对象,并希望它沿着特定方向移动(当然不同于x和y轴)。实际上,我没有清楚的想法如何做到这一点,我想寻求一些建议来实现它。感谢阅读!编辑:@Joran Beasley,我尝试按照你告诉我的方式去做...但我想我做错了...
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()

对象移动了,但是...没有按指定方向移动...


将代码中的 self.rect.center 改为 calculate_new_xy(self.rect.center,...),然后通过 [0] 访问 x 坐标,通过 [1] 访问 y 坐标,并将速度设置为 2,这样应该就可以正常运行了。 - Joran Beasley
3个回答

6

你只需要一点基本的三角函数知识。

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

这是您上面的代码进行了编辑后可以使用:

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()

谢谢!我不记得在学校学过这个...你知道这个更新方程叫什么吗?或者与之相关的基本关键词是什么?出于某种原因,我很难找到这个更新方程来自哪里... - Brett Young
三角函数应该是你想要搜索的... cos(angle) 给出 x 分量,sin(angle) 给出 y 分量... - Joran Beasley

2
如果您正在使用Pygame,我建议使用pygame.math.Vector2来完成此任务。使用极坐标speedangle_in_degrees)设置方向向量,并使用from_polar()将其添加到子弹的当前位置中。这比使用sincos更易理解:
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

请注意,pygame.Rect 只能存储整数坐标。这是因为 pygame.Rect 应该表示屏幕上的一个区域:

Rect 对象的坐标都是整数。[...]

如果你希望子弹以某个不可被 45° 整除的角度直线前进,你必须使用浮点精度存储对象位置。你需要将对象位置存储在单独的变量或属性中,并同步 round 坐标并将其分配给矩形的位置 (例如 .topleft)。
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])

请参见 Move towards target

最小示例

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()

1

我建议使用向量。要获得速度,请通过角度旋转起始方向向量Vector2(1,0)并将其乘以所需速度。然后只需在update方法中将此速度向量添加到位置向量中,并更新rect位置以移动精灵。(按“a”或“d”进行旋转,鼠标1或空格键进行射击。)

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()

关于您添加的示例中的代码,最简单的解决方案是在__init__方法中计算speed_xspeed_y(更适合的术语应该是“速度”),然后只需在update方法中更新self.rect.xy属性。

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()

存在问题,因为pygame.Rect只能将x和y属性设置为整数,所以移动不会100%正确。要解决此问题,您需要将精灵的坐标/位置存储在单独的变量中,将速度添加到它们中,然后更新矩形:

    # 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)

我非常感谢你的回答...但是我不得不说...有点让人困惑...而且...我不确定那是否是我想要的... 也许我应该这样说:我想定义一个函数,它可以创建一个“移动物体”,并朝着某个方向移动...也许...如果你能用简单一些的话解释一下...?(对不起,这有点难以理解...) - DarkBlood202
我猜... 我不太确定。 - DarkBlood202
Pygame的向量在旋转时使用三角函数,因此您无需直接处理它。移动非常简单:想象一下您有单独的pos_xpos_yvelocity_xvelocity_y属性,那么您只需通过添加pos_x += velocity_xpos_y += velocity_y来移动精灵。使用向量,您可以通过添加两个向量来在一行中完成此操作:pos += velocity - skrx
我已经实现了您的解决方案,以解决三角函数解决方案引起的不准确问题,但是当精灵沿非轴直线移动时,例如,如果角度= 110度,则会产生振动效果。有什么想法是什么原因引起的? - Mat Whiteside
@MatWhiteside 这是因为图像必须在整数坐标处进行blit,因为计算机显示器没有分数像素。当您将其分配给'rect.center'时,实际位置由浮点数截断为整数。我不知道是否有避免或减少这种震动的方法(如果您找到解决方案,请告诉我)。如果子弹移动得足够快,用户可能不会注意到它。 - skrx
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接