1

鉴于以下情况:

integralB :: Num a => Behavior t a -> Behavior t a -- definite integral of a behaviour
eJump :: Event t a -- tells the player to jump
bYAccel = pure 4000 -- y acceleration
bYVel = integralB bYAccel -- y velocity
bY = integralB bYVel -- y position

当跳跃事件到达时,如何让玩家跳跃(可能通过设置它的 y 速度)?

4

2 回答 2

2

您需要能够对 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 轴的坐标系。

最后的代码是一个小框架,用于将响应式香蕉连接到光泽度。

于 2013-12-19T09:15:25.590 回答
0

解决了!之前没有想到这一点我觉得有点傻,但我只是在每次 eJump 时增加一个计数器并将该计数器添加到 bYVel。

bJumpVel = sumB $ (-5000) <$ eJump
bYVel = (+) <$> bJumpVel <*> integralB bYAccel

-- gives the sum of the events
sumB :: Num a => Event t a -> Behavior t a
sumB e = accumB 0 $ (+) <$> e

出于某种原因,跳跃的高度总是变化很大,但这可能与我的时间安排无关。

我不会将此问题标记为已回答,以防有人想分享一个更好的问题。

于 2013-12-19T06:10:15.667 回答