如何在纯 Haskell 中编写简单的实时游戏循环?
Posted
技术标签:
【中文标题】如何在纯 Haskell 中编写简单的实时游戏循环?【英文标题】:How can I write a simple real-time game loop in pure Haskell? 【发布时间】:2017-05-22 20:30:08 【问题描述】:我刚开始使用 Haskell,想在不安装额外库的情况下制作一个简单的实时游戏。我需要编写一个扫描键盘输入的循环,但如果没有输入,游戏也必须运行。我该怎么做?
【问题讨论】:
【参考方案1】:随着我找到新的解决方案,这个答案正在更新
经过几个小时的学习,我想出了以下代码:
- Simple game loop example. -
import System.IO
import System.Timeout
inputTimeout = 50000 -- in microseconds
initialGameState = 100
type GameState = Int -- change to your own gamestate type here
nextGameState :: GameState -> Char -> GameState
nextGameState previousGameState inputChar =
-- REPLACE THIS FUNCTION WITH YOUR GAME
case inputChar of
's' -> previousGameState + 1
'a' -> previousGameState - 1
_ -> previousGameState
loop :: GameState -> IO () -- game loop
loop gameState =
do
putStrLn (show gameState)
hFlush stdout
c <- timeout inputTimeout getChar -- wait for input, with timeout
case c of
-- no input given
Nothing -> do loop gameState
-- quit on 'q'
Just 'q' -> do putStrLn "quitting"
-- input was given
Just input -> do loop (nextGameState gameState input)
main =
do
hSetBuffering stdin NoBuffering -- to read char without [enter]
hSetBuffering stdout (BlockBuffering (Just 80000)) -- to reduce flickering, you can change the constant
hSetEcho stdout False -- turn off writing to console with keyboard
loop initialGameState
几点说明:
这个(不是真的)游戏只是简单地写出世界状态,这里只是一个数字,并用“s”或“a”来增加/减少它。 'q' 退出程序。 显然这是一个非常简单的解决方案,不适用于更严肃的游戏。缺点是: 代码不扫描键盘状态,而是读取标准输入,这限制了输入处理。您将无法同时读取按键,并且会有重复延迟。您可以通过用另一种语言编写脚本来解决此问题,该脚本将以更复杂的方式处理键盘输入并通过管道将其传递给您的程序。在类 Unix 系统上,您还可以从 /dev/...中的文件读取键盘状态。 您可以预计每一帧大约需要inputTimeout
微秒,但并非完全如此。理论上非常快的输入可以降低这一点,计算延迟会增加这一点。
我是 Haskell 初学者,因此请随时改进并在此处发布。我正在更新这个答案。
更新代码
在之前的代码中,如果游戏nextGameState
函数的计算时间很长,输入的字符会堆积在标准输入中,程序的反应就会延迟。下面的代码通过始终从输入中读取所有字符并只取最后一个字符来解决这个问题。
- Simple game loop example, v 2.0. -
import System.IO
import Control.Concurrent
frameDelay = 10000 -- in microseconds
initialGameState = 100
type GameState = Int -- change to your own gamestate type here
nextGameState :: GameState -> Char -> GameState
nextGameState previousGameState inputChar =
-- REPLACE THIS FUNCTION WITH YOUR GAME
case inputChar of
's' -> previousGameState + 1
'a' -> previousGameState - 1
_ -> previousGameState
getLastChar :: IO Char
getLastChar =
do
isInput <- hWaitForInput stdin 1 -- wait for char for 1 ms
if isInput
then
do
c1 <- getChar
c2 <- getLastChar
if c2 == ' '
then return c1
else return c2
else
do
return ' '
gameLoop :: GameState -> IO () -- game loop
gameLoop gameState =
do
threadDelay frameDelay
putStrLn (show gameState)
hFlush stdout
c <- getLastChar
case c of
'x' -> do putStrLn "quitting"
_ -> do gameLoop (nextGameState gameState c)
main =
do
hSetBuffering stdin NoBuffering -- to read char without [enter]
hSetBuffering stdout (BlockBuffering (Just 80000)) -- to reduce flickering, you can change the constant
hSetEcho stdout False -- turn off writing to console with keyboard
gameLoop initialGameState
【讨论】:
以上是关于如何在纯 Haskell 中编写简单的实时游戏循环?的主要内容,如果未能解决你的问题,请参考以下文章