3

基本的にスクロールする背景である Pygame で簡単なプログラムを作成し、定期的なラグ スパイクに気付きました。長い間コードをいじった後、pygame.display.update() の呼び出しの実行に時間がかかることがあることがわかりました。

問題を実際に取り除いて再現するために、次のコードを書きました。

import pygame
import sys
import time

FRAME_RATE = 30

# don't mind the screen and time_passed variables; they aren't used in this script

def run_game():
    pygame.init()
    clock = pygame.time.Clock()
    screen = pygame.display.set_mode((500, 500))
    prev_spike = 0
    time_passed = 0
    while 1:
        start = time.clock()
        pygame.display.update()
        timenow = time.clock()
        time_spent = timenow - start
        if time_spent > 0.01:
            print time_spent 
            if prev_spike:
                print "Last spike was: {} seconds ago".format(timenow - prev_spike)
            prev_spike = timenow
        time_passed = clock.tick(FRAME_RATE)

if __name__ == "__main__":
    run_game()

そのフレームレートでの出力のスニペット:

0.0258948412828
Last spike was: 1.01579813191 seconds ago
0.0186809297657
Last spike was: 0.982841934526 seconds ago
0.0225958783907
Last spike was: 2.01697784257 seconds ago
0.0145269648427
Last spike was: 1.01603407404 seconds ago
0.0186094554386
Last spike was: 2.01713885195 seconds ago
0.0283046020628
Last spike was: 1.03270104172 seconds ago
0.0223322687757
Last spike was: 1.01709735072 seconds ago
0.0152536205013
Last spike was: 1.01601639759 seconds ago

何が起こっているのかまったくわかりません。洞察力が欲しいです。

詳細:

すべてのループ反復で time_spent を出力するときの出力のスニペット (> 0.01 の場合のみではなく):

0.000204431946257
0.000242090462673
0.000207890381438
0.000272447838151
0.000230178074828
0.0357667523718          <-- update taking two orders of magnitude longer than normal
0.000293582719813
0.000343153624075
0.000287818661178
0.000249391603611

60 FPS で実行すると、各スパイクの間隔はほとんどの場合 1 秒で、まれに 2 秒になります (スパイクは約 2 倍長く続きます)。低いフレーム レートでは、スパイク間の間隔はより変化し始めますが、値は常に整数に近くなります。

別のコンピューターでスクリプトを実行しようとしましたが、問題は再現されませんでした。pygame.display.update() の実行時間はかなり速く、一貫していました。ただし、そのマシンで元のプログラムを実行すると、1 秒間隔のラグ スパイクが残りました (テストする他のマシンを探すことになるでしょう...)。

テストした両方のマシンで Windows 7 が実行されました。

編集:

Pygame Web サイトでホストされているいくつかのランダムなゲームを取得しましたが、同様の動作が発生しています。pygame.display.update(またはflip) への呼び出しは定期的に 10 ~ 40 ミリ秒かかりますが、通常は 2 ミリ秒未満かかります。

他の誰もこの問題を抱えていないようです (または、少なくとも不平を言っています。これは、ほとんどのゲームがこの問題があまり目立たない 30 FPS 未満で実行されているためかもしれません)、私の環境に問題がある可能性があります。ただし、(上記のように)2台目のマシンで問題を(ちょっと)再現したので、問題を無視せず、エンドユーザーが経験しないことを願っています...

4

3 に答える 3

2

ゲーム開発でこれを尋ねてみてください。より良い答えが得られるかもしれません。

編集: 次のコードは、発生した問題を修正するようには見えませんが、アニメーションのテストを提供し、メイン ゲーム ループの時間指定されたコールバックを使用します

レンダー関数への時間指定されたコールバックを使用してみてください。

import pygame
import time
import math
from pygame.locals import *

desiredfps = 60
updaterate = int(1000 / desiredfps)
print "Aiming for {0}fps, update every {1} millisecond".format(desiredfps, updaterate)
lasttime = 0
rectx = 0
recty = 0

def run_game():
    pygame.init()
    screen = pygame.display.set_mode((500, 500))
    pygame.time.set_timer(USEREVENT+1, updaterate)

    def mainloop():
        global lasttime
        global rectx
        global recty
        screen.fill(pygame.Color("white"))
        screen.fill(pygame.Color("red"), pygame.Rect(rectx,recty,20,20))
        screen.fill(pygame.Color("blue"), pygame.Rect(480-rectx,480-recty,20,20))
        screen.fill(pygame.Color("green"), pygame.Rect(rectx,480-recty,20,20))
        screen.fill(pygame.Color("yellow"), pygame.Rect(480-rectx,recty,20,20))
        rectx += 5
        if rectx > 500:
            rectx = 0
            recty += 20
        beforerender = time.clock()
        pygame.display.update()
        afterrender = time.clock()
        renderdelta = afterrender - beforerender
        framedelta = beforerender - lasttime
        lasttime = beforerender
        if renderdelta > 0.01:
            print "render time: {0}".format(renderdelta)
            print "frame delta: {0}".format(framedelta)
            print "-------------------------------------"            
    while(1):
        for event in pygame.event.get():
            if event.type == USEREVENT+1:
                mainloop()
            if event.type == QUIT:
                pygame.quit()
                return
# Run test           
run_game()

これで問題はないようですが、まだ問題が発生している場合はお知らせください。

于 2012-07-24T10:26:41.337 に答える
1

いくつかのテストの後、ここにいくつかの結果があります。まず、次の質問に答えます。ラグ スパイクの原因は pygame.display.update() ではありません。ラグ スパイクの原因は、clock.tick(FRAME_RATE) です。FRAME_RATE パラメータのない clock.tick() はスパイクを引き起こさないことに注意してください。python の time.sleep() メソッドを使用して、pygame の clock.tick() をフレーム レートの手動追跡に置き換えようとしても、問題は解決しませんでした。内部的には、python の time.sleep() と pygame の clock.tick() の両方が不正確であることが知られている同じ関数を使用しているためだと思います。その関数を 1ms スリープ状態にすると (ゲームが単純な場合にすべての CPU を占有しないようにするため)、関数はそれよりもはるかに長く (約 10 ~ 15ms) スリープすることがあるようです。これは、OS のスリープ メカニズムの実装と関連するスケジューリングに依存します。

解決策は、スリープ関連の関数を使用しないことです。

第二部もあります。sleep() を使用しない場合でも、個々のフレーム間のデルタ時間に一貫性がないという問題があり、これを考慮しないと、視覚的なジッタリング/スタッタリングが発生する可能性があります。この問題は、このチュートリアルで詳しく説明されていると思います。

そこで、このチュートリアルで紹介したソリューションを python と pygame で実装したところ、完全に機能しました。わずか30fpsで「物理」を更新しているのに、とても滑らかに見えます。CPUをかなり食いますが、見栄えは良いです。コードは次のとおりです。

from __future__ import division
import pygame
from random import randint
from math import fabs

PHYS_FPS = 30
DT = 1 / PHYS_FPS
MAX_FRAMETIME = 0.25



def interpolate(star1, star2, alpha):
    x1 = star1[0]
    x2 = star2[0]
    # since I "teleport" stars at the end of the screen, I need to ignore
    # interpolation in such cases. try 1000 instead of 100 and see what happens
    if fabs(x2 - x1) < 100:
        return (x2 * alpha + x1 * (1 - alpha), star1[1], star1[2])
    return star2


def run_game():
    pygame.init()
    clock = pygame.time.Clock()
    screen = pygame.display.set_mode((500, 500))

    # generate stars
    stars = [(randint(0, 500), randint(0, 500), randint(2, 6)) for i in range(50)]
    stars_prev = stars

    accumulator = 0
    frametime = clock.tick()

    play = True
    while play:
        frametime = clock.tick() / 1000
        if frametime > MAX_FRAMETIME:
            frametime = MAX_FRAMETIME

        accumulator += frametime

        # handle events to quit on 'X' and escape key
        for e in pygame.event.get():
            if e.type == pygame.QUIT:
                play = False
            elif e.type == pygame.KEYDOWN:
                if e.key == pygame.K_ESCAPE:
                    play = False

        while accumulator >= DT:
            stars_prev = stars[:]
            # move stars
            for i, (x, y, r) in enumerate(stars):
                stars[i] = (x - r * 50 * DT, y, r) if x > -20 else (520, randint(0, 500), r)
            accumulator -= DT

        alpha = accumulator / DT
        stars_inter = [interpolate(s1, s2, alpha) for s1, s2 in zip(stars_prev, stars)]

        # clear screen
        screen.fill(pygame.Color('black'))

        # draw stars
        for x, y, r in stars_inter:
            pygame.draw.circle(screen, pygame.Color('white'), (int(x), y), r)

        pygame.display.update()

if __name__ == "__main__":
    run_game()
于 2012-07-24T09:26:36.050 に答える
-2
import pygame
import time
import math
from pygame.locals import *
desiredfps = 60
updaterate = int(1000 / desiredfps)
lasttime = 0
rectx = 0
recty = 0

def run_game():
    pygame.init()
    screen = pygame.display.set_mode((500, 500))
    pygame.time.set_timer(USEREVENT+1, updaterate)
    def mainloop():
        global lasttime
        global rectx
        global recty
        screen.fill(pygame.Color("white"))
        screen.fill(pygame.Color("red"), pygame.Rect(rectx,recty,20,20))
        screen.fill(pygame.Color("blue"), pygame.Rect(480-rectx,480-recty,20,20))
        screen.fill(pygame.Color("green"), pygame.Rect(rectx,480-recty,20,20))
        screen.fill(pygame.Color("yellow"), pygame.Rect(480-rectx,recty,20,20))
        rectx += 5
        if rectx > 500:
            rectx = 0
            recty += 20
        beforerender = time.clock()
        pygame.display.update()
        afterrender = time.clock()
        renderdelta = afterrender - beforerender
        framedelta = beforerender - lasttime
        lasttime = beforerender
        if renderdelta > 0.01:
            print ("render time: {0}").format(renderdelta)
            print ("frame delta: {0}").format(framedelta)
            print ("-------------------------------------")      

    while(1):
        for event in pygame.event.get():
            if event.type == USEREVENT+1:
                mainloop()
            if event.type == QUIT:
                pygame.quit()
                return # 
于 2015-09-02T12:09:48.163 に答える