您需要能够对 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 轴的坐标系。
最后的代码是一个小框架,用于将响应式香蕉连接到光泽度。