pygame 如果 window 较小,精灵移动得更快

pygame sprite move faster if window is smaller

如果我的游戏处于 window 模式,我的角色精灵移动得更快。为了设置我使用的速度 ROOTwidth,理论上应该按比例缩放速度... 这是我的代码(简化版)

#MAIN CODE
#ROOT dimension don't change (window can't be resized while playing,
#only in main menu function where ROOTwidth, ROOTheight are obtained) 
ROOTwidth, ROOTheight = pygame.display.get_surface().get_size()

velocity = ROOTheight/450
playertopx = ROOTwidth/2.2
playertopy = ROOTwidth/2

playermovement = PlayerMovement(playertopx, playertopy)

while True:
   key = pygame.key.get_pressed()
   if key[pygame.K_w]:
      playermovement.human_moveup(velocity)

#PLAYER MOVEMENT CLASS
import pygame

class PlayerMovement:
    #init
    def __init__(self, playertopx, playertopy):
        self.x = playertopx
        self.y = playertopy
    
    #movement
    def human_moveup(self, velocity):
        self.y -= velocity
#MAIN CODE
   ROOT.blit(playermovement.spritesheet_human, (playermovement.x, playermovement.y), (0, 50, 25, 18))

我不知道该怎么做...对于我游戏中的每个元素,使用 ROOT 维度都可以正常工作,只有 velocity 我有问题

我想这可能是因为您的主事件循环的循环速度取决于在 window 上绘制所需的时间,而没有考虑到这一点。

执行速度

假设您将此代码作为主事件循环:

while NotExited:
  doGameLogic() # Your velocity computations and other stuff
  drawOnWindow() # Filling entire window with background, drawing sprites over, refreshing it, etc

现在,假设 doGameLogic() 总是需要 1 毫秒(0.001 秒)的时间,而 drawOnWindow() 总是需要 50 毫秒。虽然此循环是 运行ning,因此,该循环总共将占用 51 毫秒,因此 doGameLogic() 将每 51 毫秒调用一次。

然后你在那里进行速度计算。为简单起见,假设您每次都在其中 playermovement.x += 5

因此,您的玩家的 X 坐标每 51 毫秒增加 5 个单位。这相当于在一秒钟内增加了大约 98 个单位。


执行速度的差异

现在假设 drawOnWindow() 开始花费 20 毫秒的时间。然后循环总共花费 21 毫秒的时间到 运行,这也会导致每 21 毫秒从 doGameLogic() 到 运行。在这种情况下,X 坐标每 21 毫秒增加 5 个单位,相当于每秒增加 238 个单位。

这比之前的每秒 98 个单位快多了。因为现在绘图花费的时间更少,所以您的角色最终移动得更快。

我认为这就是您的情况。当您使 window 更小时,绘图调用(例如用颜色绘制 background/filling 它)花费的时间更少,因为要绘制的像素更少,因此改变了 drawOnWindow() 花费的时间,因此 doGameLogic() 为 运行 的频率发生变化。


修复

有许多不同的方法可以解决这个问题。这里有一些:

强制循环速度

其中之一是确保您的循环始终花费完全相同的时间到 运行,而不管调用花费多少时间:

import time

while NotExited:
  startTime = time.time() # Record when the loop was started

  doGameLogic()
  drawOnWindow()

  # Calculate how long did it take the loop to run.
  HowLong = time.time() - startTime 

  # Sleep until this loop takes exactly 0.05 seconds.
  # The "max" call is to ensure we don't try to sleep
  # for a negative value if the loop took longer than that.
  time.sleep(max(0, 0.05-HowLong))

或者,您用于渲染的库可能允许您设置 FPS(每秒帧数)的上限,这也可以确保绘制所需的时间恒定。

这种方法有一个缺点,如果循环时间超过指定的时间,它就会失效,并且在相反的情况下限制你的游戏速度 运行s,但它很容易实现。

随速度扩展

与其确保 playermovement.x += 5 和其余逻辑恰好每 50 毫秒一次 运行,您可以确保它 运行 的值按​​比例缩放通常是 运行,产生相同的结果。

换句话说,运行ning playermovement.x += 5 每 50 毫秒一次完全等同于 运行ning playermovement.x += 1 每 10 毫秒一次:作为任一结果,每 50 毫秒该值增加了 5 个单位。

我们可以计算渲染最后一帧所花费的时间,然后按比例调整计算中的值:

import time
# This will store when was the last frame started.
# Initialize with a reasonable value for now.
previousTime = time.time()

while NotExited:
  # Get how long it took to run the loop the last time.
  difference = time.time() - previousTime
  # Get a scale value to adjust for the delay.
  # The faster the game runs, the smaller this value is.
  # If difference is 50ms, this returns 1.
  # If difference is 100ms, this returns 2.
  timeScale = difference / 0.05

  doGameLogic(timeScale)
  drawOnWindow()

  previousTime = time.time()


# ... in the game logic:
def doGameLogic(timeScale): 
    # ...
    # Perform game logic proportionally to the loop speed.
    playermovement.x += 5 * timeScale

此方法根据速度更具适应性,但无论何时执行此类依赖于时间的操作都需要考虑。

它也可能是独特问题的根源:例如,如果您的游戏 运行 即使是一帧也非常非常慢,时间比例值可能会变得不成比例地大,从而导致 playermovement.x增加 5*100000,将您的玩家角色传送到很远的地方。如果循环速度不稳定,它也会产生不稳定的结果,并且由于它是用浮点数学执行的,所以会产生更多问题。

解耦逻辑和渲染

另一种比其他方法更可靠但更难实现的方法是将 doGameLogic()drawOnWindow() 分离,允许一个 运行 独立于另一个。这通常通过使用多线程来实现。

您可以同时进行两个循环 运行ning:一个 运行s doGameLogic() 在固定的时间间隔内,例如 10 毫秒,使用上述“强制循环速度”方法,以及另一个 运行s drawOnWindow() 尽可能快地以任意速度在 window 上呈现。

这个方法还涉及到插值的问题(如果drawOnWindow()运行比doGameLogic()快一倍,你可能不希望每次都画出一模一样的图像,但中间的一个看起来更平滑)和线程管理(确保你不在 window 上绘制而 doGameLogic() 仍然是 运行ning,因为你可能绘制不完整的游戏状态正在处理中)。

不幸的是,我的知识不足以提供代码示例,我什至不确定这在 Python 或 PyGame.

中是否可行