Haskell光泽粒子效果

时间:2014-11-02 14:21:21

标签: haskell particle-system particles gloss

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

如果有人能帮我解决这个问题,我们将非常感激。

最诚挚的问候, Skyfe。

1 个答案:

答案 0 :(得分:1)

关于这个问题的评论很好地提供了一个高级别的解决方案,但我写这个答案来添加细节。

让我们首先模拟我们想要表达的真实世界对象。在我们的例子中,它是一个粒子。粒子应该具有位置速度加速度,我们可以使用2D矢量表示所有这些。在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 的额外字段。粒子的寿命表示粒子从创建到死亡的时间,而 age 表示自粒子以来经过的时间>的创作。换句话说,当年龄超过其生命周期时,粒子应该会消失。请记住这一点。

接下来,让我们编写一个帮助我们创建粒子的函数。它所做的就是将初始年龄设置为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 秒推进粒子。该功能推进了粒子 age ,根据 velocity 更改其位置,最后根据加速<更改 velocity / em>的。最后,如果粒子年龄超过其生命周期,我们会将粒子删除>通过评估 Nothing 而不是只是更改的粒子。

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

以下功能推进了群集,并摆脱了“死亡”。的粒子取值

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)并且随着它的而逐渐淡出年龄接近生命周期(即持续接近0的alpha值)

我们差不多完成了!添加更新群集的功能以反映时间的推移

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

我希望这有帮助!