如何使用 Gloss 库在 Haskell 中创建粒子效果?(例如显示爆炸)
如果有人可以帮助我了解这是如何完成的,将不胜感激。
最好的问候, Skyfe。
如何使用 Gloss 库在 Haskell 中创建粒子效果?(例如显示爆炸)
如果有人可以帮助我了解这是如何完成的,将不胜感激。
最好的问候, Skyfe。
对问题的评论在提供高级解决方案方面做得很好,但我写这个答案是为了添加细节。
让我们首先对我们想要表示的真实世界对象进行建模。在我们的例子中,它是一个粒子。一个粒子应该有一个位置、一个速度和一个加速度,所有这些我们都可以用二维向量来表示。在 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
我希望这有帮助!