Вам нужно будет применить импульс к скорости Y для прыжка. Из вашего собственного ответа вы придумали способ сделать это, суммируя все импульсы от прыжков и добавляя их к интегралу ускорения.
Ваше ускорение также является постоянным. Если вы не хотите, чтобы игрок падает постоянно, вам нужно что-то вроде:
bYAccel = (ifB airborne) 4000 0
airborne = fmap (>0) bY
ifB :: Behavior t Bool -> a -> a -> Behavior t a
ifB boolBehavior yes no = fmap (\bool -> if bool then yes else no) boolBehavior
Одной из возможных причин высоты ваших прыжков меняется это вы не сбросить скорость, когда игрок приземляется. Если у вас есть правила, которые удерживают игрока выше некоторой позиции (например, пола) и каким-то образом останавливают ускорение, когда игрок попадает на пол, вам также необходимо установить скорость на 0, если она находится в направлении пола. (Если вы также установите его на 0, когда он не находится в направлении пола, игрок никогда не сможет получить скорость, чтобы покинуть землю.)
Причина, по которой это может привести к неустойчивым высотам прыжка, заключается в том, что конечная скорость, когда игровые земли будут близки к импульсу, который вы применяли для их взлета. Используя ваши цифры, если прыжок начался со скоростью -5000 и закончился со скоростью 4800, следующий прыжок добавит импульс -5000, взяв скачок до начальной скорости всего -200. Это может иметь конечную скорость 300, поэтому следующий прыжок будет почти полным -4700 прыжком.
Полный рабочий пример. Он использует библиотеку глянца для ввода и отображения. gameDefinition
соответствует компонентам, введенным в ваш вопрос. integrateDeltas
эквивалентен вашему integralB
, но создает события, которые являются импульсами, которые легко сгенерировать в тактируемой структуре, такой как глянцевая, и просты в использовании, смешанные с другими событиями, которые вызывают импульсы, такие как прыжки.
{-# LANGUAGE RankNTypes #-}
module Main where
import Reactive.Banana
import Reactive.Banana.Frameworks.AddHandler
import Reactive.Banana.Frameworks
import Data.IORef
import qualified Graphics.Gloss.Interface.IO.Game as Gloss
gameDefinition :: GlossGameEvents t -> Behavior t Gloss.Picture
gameDefinition events = renderBehavior
where
bY = accumB 0 (fmap sumIfPositive yShifts)
yShifts = integrateDeltas bYVel
bYVel = accumB 0 yVelChanges
yVelChanges = apply ((ifB airborne) (+) sumIfPositive) yVelShifts
yVelShifts = union (integrateDeltas bYAccel) (fmap (const 3) eJump)
bYAccel = (ifB airborne) (-10) 0
airborne = fmap (>0) bY
eJump = filterE isKeyEvent (event events)
integrateDeltas = integrateDeltaByTimeStep (timeStep events)
renderBehavior = (liftA3 render) bY bYVel bYAccel
render y yVel yAccel =
Gloss.Pictures [
Gloss.Translate 0 (20+y*100) (Gloss.Circle 20),
Gloss.Translate (-50) (-20) (readableText (show y)),
Gloss.Translate (-50) (-40) (readableText (show yVel)),
Gloss.Translate (-50) (-60) (readableText (show yAccel))
]
readableText = (Gloss.Scale 0.1 0.1) . Gloss.Text
-- Utilities
sumIfPositive :: (Ord n, Num n) => n -> n -> n
sumIfPositive x y = max 0 (x + y)
ifB :: Behavior t Bool -> a -> a -> Behavior t a
ifB boolBehavior yes no = fmap (\bool -> if bool then yes else no) boolBehavior
integrateDeltaByTimeStep :: (Num n) => Event t n -> Behavior t n -> Event t n
integrateDeltaByTimeStep timeStep derivative = apply (fmap (*) derivative) timeStep
isKeyEvent :: Gloss.Event -> Bool
isKeyEvent (Gloss.EventKey _ _ _ _) = True
isKeyEvent _ = False
-- Main loop to run it
main :: IO()
main = do
reactiveGame (Gloss.InWindow "Reactive Game Example" (400, 400) (10, 10))
Gloss.white
100
gameDefinition
-- Reactive gloss game
data GlossGameEvents t = GlossGameEvents {
event :: Event t Gloss.Event,
timeStep :: Event t Float
}
makeReactiveGameNetwork :: Frameworks t
=> IORef Gloss.Picture
-> AddHandler Gloss.Event
-> AddHandler Float
-> (forall t. GlossGameEvents t -> Behavior t Gloss.Picture)
-> Moment t()
makeReactiveGameNetwork latestFrame glossEvent glossTime game = do
eventEvent <- fromAddHandler glossEvent
timeStepEvent <- fromAddHandler glossTime
let
events = GlossGameEvents { event = eventEvent, timeStep = timeStepEvent }
pictureBehavior = game events
pictureChanges <- changes pictureBehavior
reactimate (fmap (writeIORef latestFrame) pictureChanges)
reactiveGame :: Gloss.Display
-> Gloss.Color
-> Int
-> (forall t. GlossGameEvents t -> Behavior t Gloss.Picture)
-> IO()
reactiveGame display color steps game = do
latestFrame <- newIORef Gloss.Blank
(glossEvent, fireGlossEvent) <- newAddHandler
(glossTime, addGlossTime) <- newAddHandler
network <- compile (makeReactiveGameNetwork latestFrame glossEvent glossTime game)
actuate network
Gloss.playIO
display
color
steps
()
(\world -> readIORef latestFrame)
(\event world -> fireGlossEvent event)
(\time world -> addGlossTime time)
В этом примере, bY
проверяет столкновения с пола при температуре от 0 по накоплению импульсов, но сдерживающие накопленное значение, чтобы быть выше 0.
скорости, bYVel
, накапливает все импульсы, в то время как в воздухе, но только те импульсы, которые направлены от пола, а не в воздухе. Если изменить
yVelChanges = apply ((ifB airborne) (+) sumIfPositive) yVelShifts
в
yVelChanges = fmap (+) yVelShifts
он воссоздает неустойчивый прыжки ошибка.
Ускорение, bYAccel
, присутствует только в воздухе.
Я использовал систему координат с осью + Y в направлении вверх (напротив ускорения).
Код в конце представляет собой небольшой каркас для перехвата реактивного банана до блеска.
Ваш диагноз был правильным, вам удалось угадать, что было не так с моим кодом, даже не увидев его. Невероятный ответ! – mcjohnalds45