どのフレームレートでも滑らかに
CoddyのPygameジャーニー「サウンドとゲームの手応え」セクションの一部。レッスン 19/23。
x += (target_x - x) * 0.1は、1フレームごとに10分の1ずつ移動します。1秒あたり60フレームなら1秒間に60回進みますが、30フレームしか処理できない低速なコンピューターでは30回となり、円が到着するまでに2倍の時間がかかります。Fキーを押して確認してみましょう。ゲームが30フレームで動作し、すべての動きがゆっくりになります。
解決策は、時間を計測することです。clock.tick()は、前のフレームからの経過時間をミリ秒単位で返します。それを1000で割った値がdt、つまりフレームの長さを秒単位で表した値です。1秒あたり60フレームなら約0.017、30フレームなら0.033です。
dt = 1 / 60 # ループの前
...
step = min(1, 6 * dt)
x += (target_x - x) * step
...
dt = clock.tick(fps) / 1000フレームが長いほどステップが大きくなるため、フレームレートにかかわらず、円は同じ時間で同じ距離を進みます。6は速度を設定します。1秒あたり60フレームの場合、6 * dtは約0.1となり、前と同じ10分の1です。min(1, ...)は、非常に長いフレームで目標を通り過ぎないようにします。
通常の移動も同様です。player_x += 240 * dtなら、フレームレートにかかわらず1秒あたり240ピクセル移動します。小規模なゲームの多くはtick(60)でフレームレートを固定するため、この処理は必要ありません。フレームレートが変化する場合はdtを使いましょう。
チャレンジ
中級F キーでゲームのフレームレートを 1 秒あたり 60 フレームと 30 フレームの間で切り替えます。どちらの場合も、イージングにかかる時間が同じになるようにします。
- ループの前に
dt = 1 / 60を設定します。 - ループの最後に
dt = clock.tick(fps) / 1000を設定します。 - 0.1 の代わりに
step = min(1, 6 * dt)を使って移動させます。x += (target_x - x) * stepとし、yも同様にします。
自分で試してみよう
import pygame
pygame.init()
screen = pygame.display.set_mode((480, 360))
pygame.display.set_caption("Any Frame Rate")
clock = pygame.time.Clock()
font = pygame.font.Font(None, 28)
x, y = 240, 180
target_x, target_y = 240, 180
fps = 60
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.MOUSEBUTTONDOWN:
target_x, target_y = event.pos
elif event.type == pygame.KEYDOWN and event.key == pygame.K_f:
fps = 30 if fps == 60 else 60
x += (target_x - x) * 0.1
y += (target_y - y) * 0.1
screen.fill((30, 30, 46))
pygame.draw.circle(screen, (80, 200, 220), (x, y), 12)
label = font.render(f"{fps} FPS (F to change)", True, (240, 240, 240))
screen.blit(label, (10, 10))
pygame.display.flip()
clock.tick(fps)
pygame.quit()
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
サウンドとゲームの手応えのすべてのレッスン
自分で練習してみよう: Pygameプレイグラウンド