【问题标题】:How to implement a collision with Netwire (5.0.1)如何实现与 Netwire 的冲突 (5.0.1)
【发布时间】:2016-12-10 03:08:06
【问题描述】:

我正在尝试使用 Netwire 对移动对象进行建模,并且想知道推荐的方法来实现诸如球从墙上反弹之类的东西。我遇到了几种可能的方法来做到这一点,我需要一些帮助才能真正让它们工作。

动作代码如下所示:

type Pos = Float
type Vel = Float

data Collision = Collision | NoCollision
           deriving (Show)

motion :: (HasTime t s, MonadFix m) => Pos -> Vel -> Wire s Collision m a Pos
motion x0 v0 = proc _ -> do
             rec
                 v <- vel <<< delay 0 -< x
                 x <- pos x0 -< v
             returnA -< x

pos :: (HasTime t s, MonadFix m) => Pos -> Wire s Collision m Vel Pos
pos x0 = integral x0

main :: IO ()
main = testWire clockSession_ (motion 0 5)

制作在某个位置(例如 x=20)反弹的速度箭头的推荐方法是什么?

我已经看到了三种不同的方法可以做到这一点:

  • netwire --&gt; 函数似乎是最简单的。我有一个使用这个函数的原型,但是我不知道如何根据碰撞时的速度制作一个新的速度箭头,我只能使用一个固定的值,如果物体可以加速,这个值就没有用了。

    vel :: (HasTime t s, MonadFix m) => Wire s Collision m Pos Vel
    vel = pure 5 . unless (>20) --> pure (-5)
    
  • 在 Netwire 中使用 Eventswitch。我不明白这个怎么用。

  • 使用一般箭头可用的(|||)函数。

前两个似乎是最好的选择,但我不知道如何实现它们。

我见过其他类似的问题,但不同版本的 netwire 之间的不兼容使答案对我没有用。

【问题讨论】:

  • 查看此代码可能会有所帮助:github.com/crockeo/netwire-pong
  • @ErikR,感谢您的链接。我需要一些时间来了解它是如何工作的,但我的第一印象是它并不像我希望的那样简洁。

标签: haskell frp netwire


【解决方案1】:

免责声明:我无法评论“推荐”的内容,但我可以展示一种可以满足您的需求的方法。

我想描述两种方法:
第一个是使用有状态的连线,与this a bit outdated tutorial from 2013 非常相似,但基于 Netwire 5.0.2。
第二个是使用无状态的电线。因为它们是无状态的,所以需要反馈它们以前的值,这使得线的输入类型和线的最终组合更加复杂。否则它们非常相似。

两个示例中涉及的基本类型是

type Collision = Bool
type Velocity = Float
type Position = Float

有状态

您可以用两条(有状态的)线来模拟您的问题,然后将它们组合起来。

一根线对速度进行建模,该速度是恒定的,并且在发生碰撞时会改变方向。 this 的(简化)类型是Wire s e m Collision Velocity,即它的输入是碰撞是否发生,输出是当前速度。

另一个模拟位置并处理碰撞。 this 的(简化)类型是Wire s e m Velocity (Position, Collision),即它需要一个速度,计算新位置并返回该位置以及是否发生碰撞。

最后速度被反馈到位置线,碰撞结果被反馈到速度线。

我们来看看速度线的细节:

-- stateful fixed velocity wire that switches direction when collision occurs
velocity :: Velocity -> Wire s e m Collision Velocity
velocity vel = mkPureN $ \collision ->
  let nextVel = if collision then negate vel else vel
  in (Right nextVel, velocity nextVel)

mkPureN 创建一个有状态的连线,它只依赖于输入和它自己的状态(不依赖于 Monad 或时间)。状态是当前速度,如果 Collision=True 作为输入传递,则下一个速度被否定。返回值是一对速度值和具有新状态的新线。

对于这个位置,直接使用integral 线已经不够了。我们想要一个增强的“有界”版本的integral,它确保该值低于上限且大于 0,并在发生此类冲突时返回信息。

-- bounded integral [0, bound]
pos :: HasTime t s => Position -> Position -> Wire s e m Velocity (Position, Collision)
pos bound x = mkPure $ \ds dx ->
  let dt = realToFrac (dtime ds)
      nextx' = x + dt*dx -- candidate
      (nextx, coll)
        | nextx' <= 0 && dx < 0     = (-nextx', True)
        | nextx' >= bound && dx > 0 = (bound - (nextx' - bound), True)
        | otherwise                 = (nextx', False)
  in (Right (nextx, coll), pos bound nextx)

mkPure 类似于mkPureN,但允许线路依赖于时间。
dt 是时间差。
nextx' 是新位置,因为它将由integral.
以下几行检查边界并返回新位置,如果发生碰撞,新线具有新状态。

最后,您使用recdelay 将它们相互连接。完整示例:

{-# LANGUAGE Arrows #-}

module Main where

import Control.Monad.Fix
import Control.Wire
import FRP.Netwire

type Collision = Bool
type Velocity = Float
type Position = Float

-- bounded integral [0, bound]
pos :: HasTime t s => Position -> Position -> Wire s e m Velocity (Position, Collision)
pos bound x = mkPure $ \ds dx ->
  let dt = realToFrac (dtime ds)
      nextx' = x + dt*dx -- candidate
      (nextx, coll)
        | nextx' <= 0 && dx < 0     = (-nextx', True)
        | nextx' >= bound && dx > 0 = (bound - (nextx' - bound), True)
        | otherwise                 = (nextx', False)
  in (Right (nextx, coll), pos bound nextx)

-- stateful fixed velocity wire that switches direction when collision occurs
velocity :: Velocity -> Wire s e m Collision Velocity
velocity vel = mkPureN $ \collision ->
  let nextVel = if collision then negate vel else vel
  in (Right nextVel, velocity nextVel)

run :: (HasTime t s, MonadFix m) => Position -> Velocity -> Position -> Wire s () m a Position
run start vel bound = proc _ -> do
  rec
    v <- velocity vel <<< delay False -< collision
    (p, collision) <- pos bound start -< v
  returnA -< p

main :: IO ()
main = testWire clockSession_ (run 0 5 20)

无状态

无状态变体与有状态变体非常相似,只是状态会漂移到连线的输入类型,而不是作为创建连线的函数的参数。

因此,速度线将元组 (Velocity, Collision) 作为其输入,我们只需提升一个函数来创建它:

-- pure stateless independent from time
-- output velocity is input velocity potentially negated depending on collision
velocity :: Monad m => Wire s e m (Velocity, Collision) Velocity
velocity = arr $ \(vel, collision) -> if collision then -vel else vel

您也可以使用Control.Wire.Core 中的函数mkSF_(然后摆脱对Monad m 的限制)。

pos 变为

-- pure stateless but depending on time
-- output position is input position moved by input velocity (depending on timestep)
pos :: HasTime t s => Position -> Wire s e m (Position, Velocity) (Position, Collision)
pos bound = mkPure $ \ds (x,dx) ->
  let dt = realToFrac (dtime ds)
      nextx' = x + dt*dx -- candidate
      (nextx, coll)
        | nextx' <= 0 && dx < 0     = (-nextx', True)
        | nextx' >= bound && dx > 0 = (bound - (nextx' - bound), True)
        | otherwise                 = (nextx', False)
  in (Right (nextx, coll), pos bound)

这里我们仍然需要使用mkPure,因为没有专门用于依赖时间的无状态连线的函数。

要连接两条线,我们现在必须将速度的输出输入到自身和位置,然后从pos 线将位置输入到自身,并将碰撞信息输入到速度线。

但实际上,使用无状态线,您还可以将pos 线的“集成”和“边界检查”部分分开。然后pos 线是Wire s e m (Position, Velocity) Position,它直接返回上面的nextx'boundedPos 线是Wire s e m (Position, Velocity) (Position, Collision),它从pos 和速度获取新位置,并应用绑定检查.这样,不同的逻辑部分就可以很好地分开。

完整示例:

{-# LANGUAGE Arrows #-}

module Main where

import Control.Monad.Fix
import Control.Wire
import FRP.Netwire

type Collision = Bool
type Velocity = Float
type Position = Float

-- pure stateless but depending on time
-- output position is input position moved by input velocity (depending on timestep)
pos :: HasTime t s => Wire s e m (Position, Velocity) Position
pos = mkPure $ \ds (x,dx) ->
  let dt = realToFrac (dtime ds)
  in (Right (x + dt*dx), pos)

-- pure stateless independent from time
-- input position is bounced off the bounds
boundedPos :: Monad m => Position -> Wire s e m (Position, Velocity) (Position, Collision)
boundedPos bound = arr $ \(x, dx) ->
  let (nextx, collision)
        | x <= 0 && dx < 0 = (-x, True)
        | x >= bound && dx > 0 = (bound - (x - bound), True)
        | otherwise          = (x, False)
  in (nextx, collision)

-- pure stateless independent from time
-- output velocity is input velocity potentially negated depending on collision
velocity :: Monad m => Wire s e m (Velocity, Collision) Velocity
velocity = arr $ \(vel, collision) -> if collision then -vel else vel

-- plug the wires into each other
run :: (HasTime t s, MonadFix m) => Position -> Velocity -> Position -> Wire s () m a Position
run start vel bound = proc _ -> do
  rec
    v <- velocity <<< delay (vel, False) -< (v, collision)
    lastPos <- delay start -< p'
    p <- pos -< (lastPos, v)
    (p', collision) <- boundedPos bound -< (p, v)
  returnA -< p'

main :: IO ()
main = testWire clockSession_ (run 0 5 20)

【讨论】:

    猜你喜欢
    • 2020-11-04
    • 2023-02-07
    • 2020-06-20
    • 1970-01-01
    • 1970-01-01
    • 2015-12-29
    • 1970-01-01
    • 1970-01-01
    • 2013-10-01
    相关资源
    最近更新 更多