如何处理 pygame 中的像素?

How do I deal with pixels in pygame?

在 pygame 中使用 sprite 时,我最终意识到(由于差异很小)不能将 sprite 移动一个像素的一小部分。尽管这是一个合乎逻辑的过程,但我得出的结论是,在不同角度下无法获得相同的速度。

例如:

xmove = 5
ymove = -5

我定义了精灵 ball 的两个坐标的移动增量,该精灵离开屏幕底部的平台以 45° 角移动。我希望这个方向相对于它会遇到的障碍而改变。

下面判断精灵与屏幕"walls"的碰撞:

if ball.rect.left<=0:
    xmove = 5
elif ball.rect.right>=700:
    xmove = -5  
elif ball.rect.top<=0:
    ymove = 5
elif ball.rect.bottom>=400:
    ymove = -5

这里有一些代码决定在检测到与 player 精灵(平台)发生碰撞后会发生什么:

if pygame.sprite.spritecollide(ball, player_sprite, False):# and ball.rect.bottom>=player.rect.top:
    ymove=-5
    if ball.rect.bottomright [0] in range (player.rect.topleft [0], player.rect.topleft [0] + 15):
        xmove = -5
    elif ball.rect.bottomleft [0] in range (player.rect.topright [0] - 14, player.rect.topright [0] + 1):
        xmove = +5
    elif ball.rect.bottomright [0] in range (player.rect.topleft [0] + 15, player.rect.topleft [0] + 26):
        xmove = -4
        ymove = -5.83
    elif ball.rect.bottomleft [0] in range (player.rect.topright [0] - 26, player.rect.topright [0] - 15):
        xmove = +4
        ymove = -5.83

The idea here is to have the ball bounce at different angles based on where it hits the platform, all while keeping the same velocity (distance traveled per tick)

所以这里的问题是为了补偿x坐标上一个像素的减少我使用勾股定理输出y坐标(-5.83)。由于 pygame 没有记录像素的分数,这实际上会显着降低球的速度。

所以我的问题是,我该如何解决这个问题(因为我不想被限制在 45° 角...)?

您应该跟踪对象的 "real" 位置,并且仅在您在屏幕上绘制对象的那一刻左右。这可以防止舍入误差累加。


另外,考虑使用矢量来跟踪对象的方向,因为它可以很容易地随角度改变方向并保持恒定速度。

这里有一个小例子 class 你可以使用:

import math

# some simple vector helper functions, stolen from 
def magnitude(v):
    return math.sqrt(sum(v[i]*v[i] for i in range(len(v))))

def add(u, v):
    return [ u[i]+v[i] for i in range(len(u)) ]

def sub(u, v):
    return [ u[i]-v[i] for i in range(len(u)) ]    

def dot(u, v):
    return sum(u[i]*v[i] for i in range(len(u)))

def normalize(v):
    vmag = magnitude(v)
    return [ v[i]/vmag  for i in range(len(v)) ]

class Ball(object):

    def __init__(self):
        self.x, self.y = (0, 0)
        self.speed = 2.5
        self.color = (200, 200, 200)
        self._direction = (1, 0)

    # the "real" position of the object
    @property
    def pos(self):
        return self.x, self.y

    # for drawing, we need the position as tuple of ints
    # so lets create a helper property
    @property
    def int_pos(self):
        return map(int, self.pos)

    def set_dir(self, direction):
        self._direction = normalize(direction)

    def set_dir_d(self, degrees):
        self.set_dir_r(math.radians(degrees))

    def set_dir_r(self, radians):
        self._direction = (math.cos(radians), math.sin(radians))

    def update(self):
        # apply the balls's speed to the vector
        move_vector = [c * self.speed for c in self._direction]
        # update position
        self.x, self.y = add(self.pos, move_vector)

    def draw(self):
        pygame.draw.circle(screen, self.color, self.int_pos, 4)

这样,您可以通过调用 set_dir(提供向量)、set_dir_d(以度为单位提供所需的方向)或 set_dir_r(以弧度为单位提供所需的方向)。

调用 update 将根据当前方向以恒定速度(speed 字段)移动球。

因为 Pygame 的 Rect 是用来处理像素的,而像素基本上是不可分割的,所以你必须使用浮点数来存储 ball 的精确位置。 pygame.Rect 接受 浮动,但仅 存储 整数下限值。

考虑提供 ball 新的 float 属性 ball.xball.y(如果尚未分配)以保持精确(十进制)位置,然后应用您的 xmoveymove 值更新为那些值。在每次移动后(可能是同一更新的一部分),修改 ball.rect 使其 topleft 匹配 x 和 y 值,使用您最喜欢的舍入方法给出 int 值。您还可以将 ball.rect 设为 property,它会在每次调用时生成一个新的 Rect。考虑:

class MyBall:
    @property
    def rect(self):
        return pygame.Rect(round(self.x), round(self.y), *self.size)

    def __init__(self, startingLoc, size, *args, **kwargs):
        self.x = float(startingLoc[0])
        self.y = float(startingLoc[1])
        self.size = size  # Size does not need to be floats, but can be.
          # ...

请注意,如果您将 ball.rect 设为 property,就地 pygame.Rect 操作 将不起作用, 因为 ball.rect是现场生成的,它的属性没有被ball保存。矩形移动只能通过改变 ball.xy 来完成,但只要你不直接对 ball.rect 采取行动(或调用 pygame 这样做),没关系,您将获得 ball.

的精确 x 和 y 位置

如果你想要超级精确,你甚至可以在 player-ball 碰撞检测中使用浮点数,方法是将 if ball.rect.bottomright [0] in range (player.rect.topleft [0], player.rect.topleft [0] + 15): 替换为 if player.left <= ball.x <= player.left + 15: 之类的东西(等等)。请注意,range(..) 不能可靠地处理 float 值,因为该范围仅包含整数。

当您 blit ball 时,您必须为其 xy 位置发送 int 值。 ball.rect.topleft 仍然可能是要走的路(因为它间接地从 ball.xy 现场提取这些值)。

xmoveymove 不需要 是浮点数;只要操作中至少有一个元素是 float,结果也将是 float。根据球的速度和方向计算出球的x和y位移后,你可以将这些float值发送到ball.xy,它会保留帧之间的精度。