Ну, вот грубый набросок того, что я буду делать:
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
— это, как следует из названия, частота кадров в герцах, т. е. кадров в секунду. Функция runFrames
сначала преобразует каждый кадр в действие, которое его отрисовывает, затем отдает каждый кадр функции addDelay
, которая проверяет время до и после запуска действия, а затем приостанавливается, пока время кадра не истечет.
Мой собственный код выглядел бы немного иначе, чем этот, потому что у меня обычно был бы более сложный цикл, который делал бы другие вещи, например, опрос SDL для событий, выполнение фоновой обработки, передачу данных на следующую итерацию и т. д. Но основная идея та же.
Очевидно, преимуществом этого подхода является то, что, несмотря на простоту, вы получаете стабильную частоту кадров, когда это возможно, с четкими средствами указания целевой скорости.
person
C. A. McCann
schedule
16.09.2011