我试图进入Netwire,我已经挖掘过找到文档,介绍,教程和诸如此类的东西,但几乎每个教程都是如此。现有代码已经过时了Netwire 5,并使用了Netwire 4中不再与我们合作的功能。 README有点乐于助人,但并非所有内容都可以编译,但它仍然无法提供足够的信息来开始使用。
我要求解释或示例只是为了让游戏循环运行并能够响应事件,所以我寻求信息以便我最终知道:
main
。还有其他任何相关内容。
我认为从那里我可以得到一些运行的东西,所以我可以通过实验来学习其余的东西(因为第五版中的文档和教程的状态非常不存在,我希望有些会很快出现)。
谢谢!
答案 0 :(得分:9)
免责声明:我还没有能够找到任何使用Netwire的大型程序,所以我要写的所有内容都应该带有一点点,因为它是基于它的根据我自己使用Netwire的经验。我在这里使用的示例主要是from my own library和attempt at writing a game using FRP,可能不是"正确的方式"做事。
<强>会话强>
netwire库的作者给出了关于netwire程序基本结构的really good answer。由于它有点旧,我将在此概述一些要点。在我们看看电线之前,让我们先来看看netwire如何处理时间,这是FRP的潜在驱动因素。在不使用测试工具testWire
的情况下推进时间的唯一方法是生成一个将有状态地返回时间增量的Session
。 Sessions
保存状态的方式封装在其类型中:
newtype Session m s = Session { stepSession :: m (s, Session m s) }
此处,Session
位于Monad(通常为IO
)内,每次评估时,都会返回&#34;时间状态&#34;类型s
和新Session
的值。通常,任何有用的状态s
都可以写为Timed
值,可以返回Real t
的某个实例:
data Timed t s
class (Monoid s, Real t) => HasTime t s | s -> t where
-- | Extract the current time delta.
dtime :: s -> t
instance (Monoid s, Real t) => HasTime t (Timed t s)
例如,在游戏中,您通常want a fixed timestep进行更新调用。 netwire用以下内容编码这个概念:
countSession_ :: Applicative m => t -> Session m (Timed t ())
countSession_
将时间步长作为输入,在本例中为t
类型的固定值,并生成状态值为Session
的{{1}}。这意味着它们仅编码Timed t ()
类型的单个值,并且不携带具有t
值的任何其他状态。在我们讨论电线之后,我们将看到它如何在评估电线时发挥作用。
电线:电线的主要类型&#39;在Netwire是:
()
此电汇描述Wire s e m a b
类型b
的值,其执行以下操作:
a
m
e
s
根据作为电抗值的性质,电线可以被认为是随时间变化的函数。因此,每条线都被编码为时间(或时间状态s
)的函数,该时间在该时刻产生类型b
的新值,以及用于评估的新线类型a
的下一个输入。通过返回值和新线,该函数可以通过函数定义传播它来包含状态。
此外,电线可能禁止或不产生值。这对于未定义计算时很有用(例如当鼠标位于应用程序窗口之外时)。这允许你实现switch
之类的东西,其中一条线路变为另一条线路以继续执行(例如一个玩家完成他的跳跃)。
有了这些想法,我们可以在netwire中看到电线的主要驱动因素:
stepWire :: Monad m => Wire s e m a b -> s -> Either e a -> m (Either e b, Wire s e m a b)
stepWire wire timestate input
完全按照我们之前的说法执行操作:它需要wire
并将前一个电汇中的当前timestate
和input
传递给它。然后,在基础Monad m
中,它生成值Right b
或禁止值为Left e
,然后给出下一个用于计算的连线。
使用Session
和Wire
类型的值,我们可以构建一个循环,一遍又一遍地做两件事:
这是一个程序的例子,它改变固定计数器永远计数两次:
import Control.Wire
-- My countLoop operates in the IO monad and takes two parameters:
-- 1. A session that returns time states of type (Timed Int ())
-- 2. A wire that ignores its input and returns an Int
countLoop :: Session IO (Timed Int ()) -> Wire (Timed Int ()) () IO a Int -> IO ()
countLoop session wire = do
(st, nextSession) <- stepSession session
(Right count, nextWire) <- stepWire wire st (Right undefined)
print count
countLoop nextSession nextWire
-- Main just initializes the procedure:
main :: IO ()
main = countLoop (countSession_ 1) $ time >>> (mkSF_ (*2))
关于如何做到这一点有一些争论。我认为在这种情况下最好利用底层的Monad m
,并简单地将当前状态的快照传递给stepWire
函数。这样做,我的大多数输入线看起来像这样:
mousePos :: Wire s e (State Input) a (Float, Float)
忽略导线输入,并从State
monad读取鼠标输入。我使用State
而不是Reader
来正确处理关键的去抖动(因此点击用户界面也不会点击UI下面的内容)。状态在我的main
函数中设置并传递给runState
,它也执行电线步进。像这样的电线的抑制行为可以产生一些优雅的代码。例如,假设您的箭头键有right
和left
,如果按下该键则会生成一个值,否则会禁止。您可以使用如下所示的导线创建角色移动:
(right >>> moveRight) <|> (left >>> moveLeft) <|> stayPut
由于导线是Alternative
的实例,如果right
禁止,它只会移动到下一条可能的导线。 a <|> b
仅在a
和b
都禁止时才会禁止。
您也可以编写代码以利用netwire的Event
系统,但是您必须使用{{1}自行制作返回Event
的电话。 }。话虽这么说,我还没有发现这种抽象比简单的抑制更有用。
答案 1 :(得分:0)
回答这个可能为时已晚,但我在这里有一个答案,其中包含Netwire 5程序的(简约)结构:Console interactivity in Netwire?