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

标签 haskell game-development

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

最佳答案

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

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

    {- 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函数需要很长时间来计算,输入字符会堆积在标准输入中,程序的 react 会被延迟。以下代码通过始终从输入中读取所有字符并仅获取最后一个字符来解决此问题。
        {- 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 - 如何在纯 Haskell 中编写一个简单的实时游戏循环?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41533451/

    相关文章:

    Haskell 程序内存不足(无限递归?循环?什么?)

    unity-game-engine - Unity插件纹理是不可变的

    camera - 如何在 LibGDX 中让相机跟随玩家

    node.js - Nodejs检查游戏服务器是否在线

    c++ - 学习 C++ 游戏编程的资源

    unity3d - Linux 上的 UnityHub "Not Enough Space"错误

    haskell - 无法使用 IO Monad 打印到文件

    Haskell SOCKS5 拒绝连接

    haskell - 将两个 monadic 值成对并返回

    multithreading - 简单的多线程Haskell占用大量内存