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
Надеюсь, это поможет!