我将在Haskell使用netwire和OpenGL编写一个实时游戏。基本思想是,每个对象将用电线表示,它将获得一定数量的数据作为输入并输出其状态,然后将其连接到一条大线路上,将GUI状态作为输入并输出世界状态,然后将其传递到渲染器以及一些“全局”逻辑,比如碰撞检测。
有一件事我不确定,那就是:我要怎么打电线?并不是所有的实体都有相同的输入,玩家是唯一能够访问关键输入状态的实体,寻找导弹需要目标的位置等等。
我该怎么办?
发布于 2013-02-03 21:55:23
抑制类e是抑制异常的类型。它不是连线产生的东西,而是承担着与e在Either e a中的角色相同的角色。换句话说,如果通过<|>组合连接,则输出类型必须相等。
假设您的GUI事件通过输入传递到线路,并且您有一个连续的按键事件。最简单的一种建模方法是:
keyDown :: (Monad m, Monoid e) => Key -> Wire e m GameState ()这条线以当前游戏状态作为输入,如果按下键,则生成一个()。虽然键不是按下的,但它只是抑制了。大多数应用程序并不真正关心电线抑制的原因,所以大多数应用程序都使用mempty进行抑制。
表达这一事件的一种更方便的方法是使用读取器monad:
keyDown :: (Monoid e) => Key -> Wire e (Reader GameState) a a这个变体真正有用的是,现在您不必将游戏状态作为输入传递。相反,当偶数发生时,这种电线就像标识线,当不发生时就会抑制:
quitScreen . keyDown Escape <|> mainGame这样做的想法是,当按下转义键时,事件线keyDown Escape就会暂时消失,因为它的作用类似于标识线。所以整个电线的作用就像quitScreen,假设它不抑制自身。一旦释放了密钥,事件线就会抑制,所以使用quitScreen的组合也会受到抑制。因此,整个电线的作用类似于mainGame。
如果您想限制电线可以看到的游戏状态,您可以很容易地为此编写一个连接组合器:
trans :: (forall a. m' a -> m a) -> Wire e m' a b -> Wire e m a b这允许您应用withReaderT
trans (withReaderT fullGameStateToPartialGameState)发布于 2013-02-03 07:17:49
对此有一个非常简单和普遍的解决办法。关键的想法是,您永远不会合并不同类型的源。相反,您只合并相同类型的源。完成这项工作的诀窍是,将所有不同来源的输出封装为代数数据类型。
我不太熟悉netwire,所以如果您不介意的话,我将使用pipes作为示例。我们需要的是一个merge函数,它接收一个源列表并将它们合并到一个单一的源中,并发合并它们的输出,当它们都完成时完成。关键类型签名是:
merge
:: (Proxy p)
=> [() -> Producer ProxyFast a IO r] -> () -> Producer p a IO ()这就是说,它获取了一个Producer的a类型值的列表,并将它们组合成一个类型为a的值的Producer。下面是merge的实现,如果您对此感到好奇,并且希望遵循以下内容:
import Control.Concurrent
import Control.Concurrent.Chan
import Control.Monad
import Control.Proxy
fromNChan :: (Proxy p) => Int -> Chan (Maybe a) -> () -> Producer p a IO ()
fromNChan n0 chan () = runIdentityP $ loop n0 where
loop 0 = return ()
loop n = do
ma <- lift $ readChan chan
case ma of
Nothing -> loop (n - 1)
Just a -> do
respond a
loop n
toChan :: (Proxy p) => Chan ma -> () -> Consumer p ma IO r
toChan chan () = runIdentityP $ forever $ do
ma <- request ()
lift $ writeChan chan ma
merge
:: (Proxy p)
=> [() -> Producer ProxyFast a IO r] -> () -> Producer p a IO ()
merge producers () = runIdentityP $ do
chan <- lift newChan
lift $ forM_ producers $ \producer -> do
let producer' () = do
(producer >-> mapD Just) ()
respond Nothing
forkIO $ runProxy $ producer' >-> toChan chan
fromNChan (length producers) chan ()现在,让我们假设我们有两个输入源。第一个算法在一秒钟内生成从1到10的整数:
throttle :: (Proxy p) => Int -> () -> Pipe p a a IO r
throttle microseconds () = runIdentityP $ forever $ do
a <- request ()
respond a
lift $ threadDelay microseconds
source1 :: (Proxy p) => () -> Producer p Int IO ()
source1 = enumFromS 1 10 >-> throttle 1000000第二个源从用户输入中读取三个String:
source2 :: (Proxy p) => () -> Producer p String IO ()
source2 = getLineS >-> takeB_ 3我们希望将这两个源组合在一起,但是它们的输出类型不匹配,因此我们定义了一个代数数据类型来将它们的输出统一为一个类型:
data Merge = UserInput String | AutoInt Int deriving Show现在,通过将它们的输出封装在我们的代数数据类型中,我们可以将它们组合成一个类型相同的生产者列表:
producers :: (Proxy p) => [() -> Producer p Merge IO ()]
producers =
[ source1 >-> mapD UserInput
, source2 >-> mapD AutoInt
]我们可以很快地测试出来:
>>> runProxy $ merge producers >-> printD
AutoInt 1
Test<Enter>
UserInput "Test"
AutoInt 2
AutoInt 3
AutoInt 4
AutoInt 5
Apple<Enter>
UserInput "Apple"
AutoInt 6
AutoInt 7
AutoInt 8
AutoInt 9
AutoInt 10
Banana<Enter>
UserInput "Banana"
>>>现在你有了一个综合的来源。然后,您可以编写游戏引擎,只需从该源读取,输入上的模式匹配,然后行为得体:
engine :: (Proxy p) => () -> Consumer p Merge IO ()
engine () = runIdentityP loop where
loop = do
m <- request ()
case m of
AutoInt n -> do
lift $ putStrLn $ "Generate unit wave #" ++ show n
loop
UserInput str -> case str of
"quit" -> return ()
_ -> loop我们试试看:
>>> runProxy $ merge producers >-> engine
Generate unit wave #1
Generate unit wave #2
Generate unit wave #3
Test<Enter>
Generate unit wave #4
quit<Enter>
>>>我想同样的技巧也会在netwire上奏效。
发布于 2013-02-03 12:16:40
Elm有一个自动机库,我相信它与你所做的类似。
您可以为您想要访问的每一种状态使用类型类型。然后为游戏的整个状态实现这些类中的每一个(假设你有一个大胖子对象保存一切)。
-- bfgo = Big fat game object
class HasUserInput bfgo where
mouseState :: bfgo -> MouseState
keyState :: bfgo -> KeyState
class HasPositionState bfgo where
positionState :: bfgo -> [Position] -- Use your data structure然后,在创建用于使用数据的函数时,只需指定这些函数将要使用的类型。
{-#LANGUAGE RankNTypes #-}
data Player i = Player
{playerRun :: (HasUserInput i) => (i -> Player i)}
data Projectile i = Projectile
{projectileRun :: (HasPositionState i) => (i -> Projectile i)}https://stackoverflow.com/questions/14668075
复制相似问题