さて、これが私がすることの大まかなスケッチです:
import Graphics.UI.SDL.Time (getTicks)
import Control.Concurrent (threadDelay)
type Frame = [[Char]]
type Animation = [Frame]
displayFrame :: Frame -> IO ()
displayFrame = mapM_ putStrLn
timeAction :: IO () -> IO Integer
timeAction act = do t <- getTicks
act
t' <- getTicks
return (fromIntegral $ t' - t)
addDelay :: Integer -> IO () -> IO ()
addDelay hz act = do dt <- timeAction act
let delay = calcDelay dt hz
threadDelay $ fromInteger delay
calcDelay dt hz = max (frame_usec - dt_usec) 0
where frame_usec = 1000000 `div` hz
dt_usec = dt * 1000
runFrames :: Integer -> Animation -> IO ()
runFrames hz frs = mapM_ (addDelay hz . displayFrame) frs
明らかに、私はここで純粋にSDLを使用してgetTicks
います。これは、以前に使用したものだからです。現在の時刻を取得するには、他の関数に自由に置き換えてください。
の最初の引数runFrames
は、名前が示すように、ヘルツ単位のフレームレート、つまり1秒あたりのフレーム数です。このrunFrames
関数は、最初に各フレームを描画するアクションに変換し、次に各フレームをaddDelay
関数に渡します。関数は、アクションの実行前後の時間をチェックし、フレーム時間が経過するまでスリープします。
私自身のコードはこれとは少し異なって見えます。これは、通常、イベントのSDLのポーリング、バックグラウンド処理の実行、次の反復へのデータの受け渡しなど、他の処理を行うより複雑なループがあるためです。しかし、基本的な考え方は同じです。
明らかに、このアプローチの良いところは、かなりシンプルでありながら、可能な場合は一貫したフレームレートが得られ、ターゲット速度を明確に指定できることです。