我正在尝试使用Haskell生成一些非常快速和脏的动画显示。最简单的尝试似乎是ASCII艺术 - 换句话说,就是:
type Frame = [[Char]] -- each frame is given as an array of characters
type Animation = [Frame]
display :: Animation -> IO ()
display = ??
我怎样才能做到最好?
我根本无法弄清楚的部分是如何确保帧之间的最小暂停;使用来自ansi-terminal的this answer包中的putStrLn
和clearScreen
可以直接使用{{1}}。
答案 0 :(得分:5)
嗯,这是我要做的事情的粗略草图:
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,进行后台处理,将数据传递给下一次迭代,&amp; C。但基本思路是一样的。
显然,这种方法的优点在于,虽然仍然相当简单,但在可能的情况下,您可以获得一致的帧速率,并且可以明确指定目标速度。
答案 1 :(得分:2)
这建立在C. A. McCann的anwer之上,它很好地工作但从长远来看不是时间稳定的,特别是当帧率不是滴答率的整数分数时。
import GHC.Word (Word32)
-- import CAMcCann'sAnswer (Frame, Animation, displayFrame, getTicks, threadDelay)
atTick :: IO () -> Word32 -> IO ()
act `atTick` t = do
t' <- getTicks
let delay = max (1000 * (t-t')) 0
threadDelay $ fromIntegral delay
act
runFrames :: Integer -> Animation -> IO ()
runFrames fRate frs = do
t0 <- getTicks
mapM_ (\(t,f) -> displayFrame f `atTick` t) $ timecode fRate32 t0 frs
where timecode ν t0 = zip [ t0 + (1000 * i) `div` ν | i <- [0..] ]
fRate32 = fromIntegral fRate :: Word32