PyGame:平移平铺地图会导致间隙。

4

我正在使用Python 3.4.3 (Win8.1)上的PyGame 1.9.2。

我的游戏窗口由使用48x48px瓷砖的平铺地图组成。游戏框架根据玩家输入移动瓷砖,使用游戏框架类的以下方法:

def moveTiles(self):
    xmove = self.parent.dt * self.panning[0] * self.panning_speed
    ymove = self.parent.dt * self.panning[1] * self.panning_speed
    for tile in self.game_map:
        tile.x += xmove
        tile.y += ymove
        tile.rect.x = int(tile.x)
        tile.rect.y = int(tile.y)
        tile.pos = (tile.rect.x, tile.rect.y)

虽然"self.panning_speed"相当容易理解,但是self.panning是一个包含x和y平移两个值的列表(例如[0, 0]表示没有平移,[-1, 1]表示向下左平移等)。

然后,tile.x/tile.y的值被转换为整数以用作矩形和pos的x/y值(后者用于blitting)。

我对每个tile的x/y值添加了完全相同的数量,然后在它们上面使用"int",据我所知,这总是将float向下取整到下一个更低的整数。因此,从技术上讲,相对于任何其他tile的x/y值,每个单独tile的x/y值的相对变化都是完全相同的。

然而:我仍然会不时地出现行或列之间的间隙!这些间隙出现得不规则,但非常明显,当您停止平移时,如果有间隙可见,则该间隙将保留直到您再次平移。

请参见以下图像示例: 请参见此图像示例(红色正方形中的黑线是间隙;金色和粉色是tile)

我的代码中哪里可能会出现这些间隙?


浮点数无法保留每个值,因此我们会出现“0.1 + 0.2 != 0.3”的情况。 - furas
我会只使用整数值。或者十进制数。或者在一个变量中保留整数和浮点数之间的差异,以累积这种差异,并在累积值大于1时将其添加到所有变量中。 - furas
请提供一个[最小,完整和可验证的示例](https://stackoverflow.com/help/mcve),这样我们就可以更轻松地为您提供适当的解决方案。 - skrx
3个回答

1
我认为问题在于浮点数无法保留每个值,我们会出现这样的情况:0.1 + 0.2 != 0.3,这可能导致期望结果和实际结果之间的差异。
您可以仅使用整数值来更改所有图块的位置。
tile.rect.x = int(xmove)
tile.rect.y = int(ymove)

这样你只会失去很小的值(小于一个像素),但如果你累积许多小值,则可能会得到几个像素。
因此,您可以尝试累积浮点和整数值之间的差异。
我无法检查此功能是否正确,但我会做类似以下的操作。
def moveTiles(self):

    # add moves to already cumulated differences betweeen floats and integers
    self.xcululated += self.parent.dt * self.panning[0] * self.panning_speed
    self.ycululated += self.parent.dt * self.panning[1] * self.panning_speed

    # get only integer values
    xmove = int(self.xcululated)
    ymove = int(self.ycululated)

    # remove integers from floats
    self.xcululated += xmove
    self.ycululated += ymove

    for tile in self.game_map:
        # use the same integer values for all tiles
        tile.rect.x += xmove
        tile.rect.y += tmove

非常感谢您的回答。我决定采用相机方法。我不是移动瓷砖,而是将它们相对于相机位置进行blit。这样就不会有间隙了。 - Patric Hartmann

1
这里有一个更好的解决方案。当玩家移动时,只需将velocity添加到panning向量中,而不是移动瓷砖。然后在blit瓷砖时,将panning添加到瓷砖的rect.topleft位置以获得正确的位置。您必须先将panning转换为整数(例如,在示例中创建另一个名为offset的向量),然后再将其添加到瓷砖位置,否则仍会出现间隙。
import itertools
import pygame as pg
from pygame.math import Vector2


TILE_SIZE = 44
TILE_IMG1 = pg.Surface((TILE_SIZE, TILE_SIZE))
TILE_IMG1.fill(pg.Color('dodgerblue3'))
TILE_IMG2 = pg.Surface((TILE_SIZE, TILE_SIZE))
TILE_IMG2.fill(pg.Color('aquamarine3'))


class Tile(pg.sprite.Sprite):

    def __init__(self, pos, image):
        super().__init__()
        self.image = image
        self.rect = self.image.get_rect(topleft=pos)
        self.pos = Vector2(pos)


def main():
    screen = pg.display.set_mode((640, 480))
    clock = pg.time.Clock()
    dt = 0

    image_cycle = itertools.cycle((TILE_IMG1, TILE_IMG2))
    game_map = pg.sprite.Group()
    # Create tile instances and add them to the game map group.
    for y in range(16):
        for x in range(20):
            image = next(image_cycle)
            game_map.add(Tile((x*TILE_SIZE, y*TILE_SIZE), image))
        next(image_cycle)

    panning = Vector2(0, 0)
    velocity = Vector2(0, 0)

    done = False

    while not done:
        for event in pg.event.get():
            if event.type == pg.QUIT:
                done = True
            elif event.type == pg.KEYDOWN:
                if event.key == pg.K_a:
                    velocity.x = 100
                elif event.key == pg.K_d:
                    velocity.x = -100
            elif event.type == pg.KEYUP:
                if event.key == pg.K_d and velocity.x < 0:
                    velocity.x = 0
                elif event.key == pg.K_a and velocity.x > 0:
                    velocity.x = 0

        # Add the velocity to the panning when we're moving.
        if velocity.x != 0 or velocity.y != 0:
            panning += velocity * dt

        game_map.update()

        screen.fill((30, 30, 30))
        # Assigning screen.blit to a local variable improves the performance.
        screen_blit = screen.blit
        # Convert the panning to ints.
        offset = Vector2([int(i) for i in panning])
        # Blit the tiles.
        for tile in game_map:
            # Add the offset to the tile's rect.topleft coordinates.
            screen_blit(tile.image, tile.rect.topleft+offset)

        pg.display.flip()
        dt = clock.tick(30) / 1000


if __name__ == '__main__':
    pg.init()
    main()
    pg.quit()

另一个选择是在程序启动时将瓷砖贴到一个大的背景表面上。这样你就不会有间隙,而且还可以提高性能。

非常感谢您的回答!将所有瓷砖都绘制在一个大背景上的问题在于这些瓷砖是动画的。如果我正确理解PyGame,我需要进行“双重绘制”,即我需要将瓷砖绘制到表面上,然后再将表面绘制到屏幕上。那样会更消耗资源吗?还是我对此有什么误解?顺便说一下,我现在会使用“相机”方法。这样我就可以移动相机而不是瓷砖,并且只相对于相机绘制它们。 - Patric Hartmann
使用动画瓷砖时,您可能无法使用带有背景图像的解决方案。您如何精确地对瓷砖进行动画处理? - skrx
我已经编辑了答案,因为还有一些空缺。在将 panning(顺便说一下,这是一个相当基本的相机)添加到瓷砖位置之前,您需要将其转换为整数。 - skrx
非常感谢您的努力!我的相机方法实际上与您在这里展示的非常相似。尽管其他回答也给了我重要的见解,但我选择将其作为接受的答案。我注意到您代码中的一个细节:您使用fps限制和dt?这是否比仅使用dt更好的方法? - Patric Hartmann
如果您不调用clock.tick,游戏将以计算机可以实现的最大帧速率运行,因此通常应每帧调用一次。由于它还返回增量时间,因此我们可以直接使用它而不是自己计算。顺便说一句,如果您将速度乘以dt,那么您就有了一个“可变时间步长”,这对于在线多人游戏来说并不好(我认为对于单人游戏来说还好)。这是一个有趣的链接:http://gameprogrammingpatterns.com/game-loop.html - skrx
再次感谢!我目前还没有考虑多人游戏,但将来可能会这样做,所以非常感谢您提供的链接! - Patric Hartmann

0

我认为主要问题在于你的游戏素材没有应用填充

特别是当你使用相机和浮点值时,填充非常重要。

有时候使用相机时,浮点值不能提供所需的精度,会导致一些纹理渗透或者在游戏中不时出现的线条。

解决方案是给你的素材添加填充。

我的处理方式如下:

  1. 下载并安装GIMP。

  2. 这里安装可以添加填充的插件。

  3. 加载每个素材,在GIMP中使用插件添加填充。

无需更改任何素材的名称,只需添加填充并覆盖即可。

希望这能帮到你,如果你还有其他问题,请随时在下面留言!


谢谢你的回答。我通过使用简单的相机来移动地图解决了这个问题。到目前为止,它完美地运作。然而,给我的瓷砖添加填充是我追求了一段时间的另一个想法。我也会研究一下这个想法。 - Patric Hartmann
是的,添加填充将允许在未来需要时更复杂地移动相机,请确保在未来考虑它! - Micheal O'Dwyer
谢谢,我会看一下的! - Patric Hartmann

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