如何在纯Haskell中编写简单的实时游戏循环?

时间:2017-01-08 13:55:40

标签: haskell game-development

我刚刚开始使用Haskell,并希望在不安装其他库的情况下制作简单的实时游戏。我需要编写一个扫描键盘输入的循环,但如果没有输入,游戏也必须运行。我该怎么做?

1 个答案:

答案 0 :(得分:7)

当我找到新的解决方案时,这个答案正在更新

经过几个小时的学习后,我想出了以下代码:

    {- 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

一些注意事项:

  • 这个(不是真的)一个游戏只是简单地写出世界状态,这里只是一个数字,并用“&#39;”增加/减少它。或者&#39; a&#39;。 &#39; Q&#39;退出程序。
  • 显然这是一个非常简单的解决方案,并且不适用于更严肃的游戏。垮台是:
    • 代码不扫描键盘状态,但读取标准输入,这限制了输入处理。您无法同时阅读按键,并且会出现重复延迟。您可以通过编写另一种语言的脚本来解决这个问题,该脚本将以更复杂的方式处理键盘输入并通过管道将其传递给您的程序。在类Unix系统上,您还可以从/ dev /...
    • 中的文件中读取键盘状态
    • 您可以预期每个帧大约需要inputTimeout微秒,但不完全正确。非常快的输入理论上可以降低这一点,计算延迟会增加这个。
  • 我是Haskell的初学者,所以请随时改进并在此发布。我正在更新这个答案。

更新了代码

在之前的代码中,如果游戏nextGameState函数需要花费大量时间来计算,输入字符将在stdin中堆积,并且程序的反应将被延迟。以下代码通过始终从输入中读取所有字符并仅使用最后一个字符来解决此问题。

    {- 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