[英]How to write a Game Event Loop (e.g. setTimeout) in Haskell?
在Haskell中实现setTimeout函数是否存在或有多少大惊小怪?
主要想法是
setTimeout someFunction 1000 --someFunction will be called in 1000ms = 1s
我是Haskell的新手,可能不会使用它; 我正在学习Haskell。
游戏的事件循环通常需要你
在这里管理时间是很大的(因此setTimeout)以确保恒定的FPS。
Haskell如何看待这样的模式?
在Haskell中实现setTimeout函数是否存在或有多少大惊小怪?
JavaScript的setTimeout
和其他类似的方法(如Qt的QTimer)通常在单个线程中的单个事件循环中工作(不包括Web worker或QThread
)。 使用Haskell建模确切的行为有点困难,尽管并非不可能,因为GHC已经提供了(内部) 实现 。
如果您不关心实际的单线程行为(这也意味着具有几乎相同超时的两个函数可能同时执行),那么您可以简单地分叉一个新线程并延迟其操作:
doLater :: Int -> IO () -> IO ThreadId
doLater ms io = forkIO $ threadDelay (ms * 1000) >> io
对于任何进一步的想法,我们实际上需要了解更多有关您的特定事件循环。
Haskell如何看待这样的模式?
非常非常概括,你需要这样的东西:
mkGame :: World
-> (Input -> World -> World)
-> (TimeDiff -> World -> World)
-> (World -> GraphicalRepresentation)
-> IO ()
mkGame initialWorld handleInput handleProgression drawWorld = undefined
请注意,您可能会在其中抛出其他参数,例如每秒的最大世界更新数。
我们现在怎样建模setTimeout? 假设World
是这样的:
data World = World {
getWorldData :: WorldData,
getCurrentTime :: TimePoint, -- would get updated by your loop
getTimeouts :: Queue TimePoint (World -> World)
}
其中Queue
是任何工作优先级队列( 选择一个,有很多,或自己构建)。 现在您只需使用即可设置超时
setTimeout world delay action = world { getTimeouts = timeouts' }
where timeouts' = insert (getTimeouts world) t action
t = delay + getCurrentTime world
-- insert :: Queue p v -> p -> v -> Queue p v
如果你想要取消超时,你还需要一个关于Queue
的键,看看GHC.Event.PSQ的灵感。
除此之外,您现在可以通过简单地浏览队列并应用所有功能来检查时间是否已经过去并在游戏循环中相应地采取相应措施。
这基本上是一个非常简单粗暴的基础,可以用来激发你自己的工作。 根据你真正想做的事情,你可能想看看gloss
,它已经实现了一个非常相似的概念,虽然没有超时,但在这种情况下,你仍然可以为你的世界添加超时和总时差并简单地使用拟合更新功能( TimeDiff -> World -> World
part)。
这个问题对纯函数没有意义,因为Haskell做了懒惰的评估。 当需要其结果时,将调用函数,而不是之前的函数,而不是之后的函数。
但是,如果你想要延迟一些IO操作,你可以在GHC中使用Control.Concurrent
的threadDelay
函数。 然后,您的setTimeout
将实现为
setTimeout :: IO () -> Int -> IO ThreadId
setTimeout ioOperation ms =
forkIO $ do
threadDelay (ms*1000)
ioOperation
这是一个古老的问题,但我对答案并不满意(并且没有一个被标记为正确),因为过分关注重新实现的setTimeout
函数,我认为这是对setTimeout用于制作的错误解释用javascript游戏。
我可以假设你想要替换这个javascript模式吗?
function update() {
undefined;
setInterval(update, 1000)
}
Javascript之外的游戏循环通常不使用setTimeout
编写,setTimeout是一种解决浏览器中JS异步性质的工作。 另外,不建议将setTimeout
用于时间关键的可视代码, requestAnimationFrame
是最佳的功能。
编写游戏循环的惯用方法是一个简单的循环,伪haskell代码看起来像这样:
draw :: World -> IO ()
draw w = do
undefined
glSwapBuffers
updateWorld :: World -> World
updateWorld w = undefined
loop World -> Time -> IO ()
loop world time = do
inputs <- getInputs
time' <- getTime
let delta = time' - time
let world' = updateWorld delta world inputs
draw world'
loop world' time'
setup :: IO World
setup = do
undefined -- window setup etc
world <- undefined
return world
main :: IO ()
main = do
w <- setup
t <- getTime
loop w t
你的draw
函数应该调用glSwapBuffers
(或等效函数),在大多数设备上都会阻塞直到vsync,这将保持平滑的fps,但不要假设所有计算机都启用了vsync,并且不要假设所有显示器都是60hz,144hz和可变帧速率技术现在在游戏玩家社区中很普遍。
如果它不能制作16.67ms窗口(不像使用forkIO构建循环的那些东西),这样的循环也会安全地降级,虽然haskell的微线程,使用它们编程并不是疯了,但要确保使用它们是线程安全的,例如使用类似STM的东西。
Zeta的mkGame
是更具功能性的风格(因为它将函数作为参数),并且可以很容易地适应我的代码。
另请注意,像sdl这样的库提供了自己的循环和输入以及计时方法,这可能会简化(或复杂化)您的代码。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.