1

如何使用 Gloss 库在 Haskell 中创建粒子效果?(例如显示爆炸)

如果有人可以帮助我了解这是如何完成的,将不胜感激。

最好的问候, Skyfe。

4

1 回答 1

1

对问题的评论在提供高级解决方案方面做得很好,但我写这个答案是为了添加细节。

让我们首先对我们想要表示的真实世界对象进行建模。在我们的例子中,它是一个粒子。一个粒子应该有一个位置、一个速度和一个加速度,所有这些我们都可以用二维向量来表示。在 Haskell 中存储 2D 向量的合理方法是使用Linear.V2模块。接下来,让我们考虑一下我们希望粒子应该具有的额外属性,特别是涉及烟花或爆炸的属性。注意烟花中的粒子是如何燃烧一段时间然后“消失”的?我们将所说的时间称为粒子的生命周期,并使用Float表示它。我们现在可以为粒子创建适当的表示和一个粒子_

data Particle = Particle
  { _age          :: Float 
  , _lifespan     :: Float 
  , _position     :: V2 Float
  , _velocity     :: V2 Float
  , _acceleration :: V2 Float }
  deriving ( Show )

type Cluster = [Particle]

makeLenses ''Particle

在我们上面的数据类型中有一个名为age的额外字段。粒子的寿命代表了粒子从创建到死亡的时间,而它的年龄代表了自粒子创建以来所经过的时间。换句话说,一个粒子应该在它的年龄超过它的生命周期时消失。请记住这一点,以备后用。

接下来,让我们编写一个函数来帮助我们创建一个Particle。它所做的只是将初始年龄设置为 0 并将其余部分留给其他参数

makeParticle :: Float -> V2 Float -> V2 Float -> V2 Float -> Particle
makeParticle = Particle 0

完成后,我们可以编写一个函数来帮助我们创建一个包含n 个粒子的

makeCluster :: Int -> (Int -> Particle) -> Cluster
makeCluster n particleGen = map particleGen [0..(n - 1)]

之后,我们创建一个函数,允许我们将粒子推进dt秒。该函数推进粒子年龄,根据其速度改变其位置,最后根据其加速度改变其速度。最后,如果Particle的年龄超过了它的lifespan 我们通过评估为Nothing而不是Just改变的粒子来象征Particle的删除。

advanceParticle :: Float -> Particle -> Maybe Particle
advanceParticle dt = hasDecayed . updateVel . updatePos . updateAge
    where
  r2f = realToFrac
  hasDecayed p = if p^.age < p^.lifespan then Just p else Nothing
  updateAge  p = (age      %~ (dt                       +)) p
  updatePos  p = (position %~ (r2f dt * p^.velocity     +)) p
  updateVel  p = (velocity %~ (r2f dt * p^.acceleration +)) p

以下函数推进一个Cluster,并摆脱“死” Particle s

advanceCluster :: Float -> Cluster -> Cluster
advanceCluster dt = catMaybes . map (advanceParticle dt)

现在我们可以继续讨论与使用Graphics.Gloss实际绘制粒子有关的代码部分。我们将使用集群来表示模拟的状态,因此我们从一个函数开始,该函数返回表示程序初始状态的集群。对于一个简单的动画,我们将模拟一个烟花,其中所有粒子从相同的位置开始,具有相同的寿命,从它们的中心位置以规则的角度向外辐射,并受到相同的加速度

initState :: Cluster
initState = makeCluster numParticles particleGen
    where
  numParticles = 10

  particleGen :: Int -> Particle
  particleGen i = 
    makeParticle initLifespan
                 initPosition
                 (initVelMagnitude * V2 (cos angle) (sin angle))  
                 initAcceleration
      where
    fI               = fromIntegral
    angle            = (fI i) * 2 * pi / (fI numParticles)
    initLifespan     = 10
    initPosition     = V2 0 0
    initVelMagnitude = 5
    initAcceleration = V2 0 (-3)

然后我们编写一个函数来在屏幕上绘制一个集群

drawState :: Cluster -> Picture
drawState = pictures . map drawParticle
    where
  drawParticle :: Particle -> Picture
  drawParticle p = 
    translate (p^.position._x) (p^.position._y)    .
    color     (colorAdjust (p^.age / p^.lifespan)) .
    circleSolid $ circleRadius
      where
    circleRadius  = 3
    colorAdjust a = makeColor 1 0 0 (1 - a)

可能关于此的唯一非标准部分是colorAdjust函数。我在这里要做的是将粒子着色为红色,并且在创建它时让它根本不透明(即 alpha 值 1)并随着它的年龄接近其寿命而不断淡出(即 alpha 值不断接近 0)

我们快完成了!添加一个更新集群以反映时间流逝的函数

stepState :: ViewPort -> Float -> Cluster -> Cluster
stepState _ = advanceCluster

通过编写一个将所有内容联系在一起的函数来完成程序

main :: IO ()
main = 
  simulate (InWindow name (windowWidth, windowHeight) 
                          (windowLocX,  windowLocY))
           bgColor
           stepsPerSec
           initState
           drawState
           stepState
    where
  name             = "Fireworks!"
  windowWidth      = 300
  windowHeight     = 300
  windowLocX       = 30
  windowLocY       = 30 
  stepsPerSec      = 30
  bgColor          = white

我希望这有帮助!

于 2017-10-15T13:57:56.753 回答