如何使用Gloss库在Haskell中创建粒子效果? (例如表示爆炸)
如果有人能帮我解决这个问题,我们将非常感激。
最诚挚的问候, Skyfe。
答案 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
我希望这有帮助!