【问题标题】:How do I deal with pixels in pygame?如何处理pygame中的像素?
【发布时间】:2015-01-31 22:28:10
【问题描述】:

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

例如:

xmove = 5
ymove = -5

我定义了精灵ball 的两个坐标的移动增量,它在屏幕底部留下一个以 45° 角移动的平台。我希望这个方向相对于它将遇到的障碍有所改变。

以下决定了精灵与屏幕“墙壁”的碰撞:

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 sprite(平台)发生冲突后会发生什么:

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

这里的想法是让球根据它撞击平台的位置以不同的角度弹跳,同时保持相同的速度(每刻移动的距离)

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

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

【问题讨论】:

  • 四舍五入?每种方式一个像素会有那么大的不同吗?
  • @jonrsharpe 是的,四舍五入有很大的不同

标签: python-3.x pygame pixel


【解决方案1】:

您应该跟踪对象的“真实” 位置,并且仅在您在屏幕上绘制对象的那一刻进行。这可以防止舍入误差相加。


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

这是一个您可以使用的小示例类:

import math

# some simple vector helper functions, stolen from http://stackoverflow.com/a/4114962/142637
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 字段)移动球。

【讨论】:

  • 好的,谢谢!那么向量会是它的组件列表吗?属性 _direction 是否因为 _ 而特殊?
  • 1.是的。 2. 这只是一个约定,您不能直接从类外访问此类成员。表示不要直接设置_direction,使用set_dir函数
【解决方案2】:

因为 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 &lt;= ball.x &lt;= player.left + 15: 之类的东西(等等)。请注意,range(..) 不能可靠地使用 float 值,因为该范围仅包含整数。

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

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

【讨论】:

  • 好的,感谢这个好主意。请问@property 是做什么的?我不熟悉这个命令。
  • 据我了解,这使得return 命令在类中输出,这是正确的吗?
  • @AndresStadelmann @property 是一个简化属性分配的包装器。我对描述property 工作原理的能力并不完全有信心,所以这里有几个链接! [python-course.eu/python3_properties.php](这里简要介绍了它们是什么,)和 [blaag.haard.se/What-s-the-point-of-properties-in-Python/… 有点关于包装器。)
  • 如果我必须解释它,我会说 property 介于 属性 和 @987654373 之间,它本身不能执行操作@(或lambda),需要调用并且不能很好地保存数据。在这种情况下,ball.rect 实际上是一个简单的函数,它基于ball 中的其他属性返回一个新的(未绑定的)Rect,但仅在对其执行一些操作之后。常规属性通常不能执行操作,但调用函数会很笨拙。 (我不知道我是否在做概念正义;四处寻找更好的解释!)
  • docs.python.org/2/library/functions.html#property 我在这里找到了类似的文档,谢谢
猜你喜欢
  • 1970-01-01
  • 2015-05-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-02-19
  • 2016-12-18
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多