Haskell Gloss Particle Effects

Как создать эффекты частиц в Haskell с помощью библиотеки Gloss? (например, чтобы показать взрыв)

Если бы кто-нибудь мог немного помочь мне в том, как это делается, это было бы очень ценно.

С наилучшими пожеланиями, Skyfe.

1 ответ

Комментарий к вопросу делает хорошую работу по обеспечению решения высокого уровня, но я пишу этот ответ, чтобы добавить детали.

Давайте начнем с моделирования реального объекта, который мы хотим представить. В нашем случае это частица. Частица должна иметь положение, скорость и ускорение, и все это мы можем представить с помощью 2D векторов. Разумный способ сохранить 2D-векторы в Haskell - использовать модуль 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 секунд. Функция увеличивает возраст частицы, изменяет ее положение в зависимости от скорости и, наконец, изменяет скорость в зависимости от ускорения. В конце концов, если возраст Частицы превышает ее продолжительность жизни, мы символизируем удаление Частицы, оценивая Ничто вместо Только измененной частицы.

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. Я собирался покрасить Частицу в красный цвет, и когда она будет создана, она вообще не будет прозрачной (то есть альфа-значение 1) и будет постепенно исчезать по мере того, как ее возраст приближается к продолжительности жизни (то есть альфа-значение, которое продолжает приближаться к 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

Надеюсь, это поможет!

Другие вопросы по тегам