Difference between revisions of "User talk:DavidE"

From HaskellWiki
Jump to navigation Jump to search
Line 92: Line 92:
   
 
Note that the initWorld function makes use of the Graphics.Ogre.HOgre module (the bindings to the Ogre 3D engine). The SceneNode for the ogre head is returned, but at the moment is not used.
 
Note that the initWorld function makes use of the Graphics.Ogre.HOgre module (the bindings to the Ogre 3D engine). The SceneNode for the ogre head is returned, but at the moment is not used.
  +
  +
So far the code hasn't taken advantage of FRP. Now lets see how we could use Events to print out the current time at each frame. To get the frame Event, we simply use <hask>frameE :: HookedBogreSystem -> Event t BogreFrame</hask>. This returns an Event of BogreFrames. We then have to transform the Event to an Event of the time (of type Float), and then to an an Event of IO actions. We use fmap (actually its infix, "<$>") to transform the events. We intend that whenever the IO Event occurs, it is executed. In Reactive-Banana, this is accomplished by passing the IO Event to the <hask>reactimate</hask> function. The code now looks as follows:
  +
  +
<haskell>
  +
myGame :: Frameworks t => GameBuilder t
  +
myGame bs smgr = do
  +
-- initialize the world
  +
ogreaHead <- liftIO $ initWorld bs smgr
  +
  +
-- get the BogerFrame Event :: Event t BogreFrame
  +
let fE = frameE bs
  +
  +
-- transform to the time :: Event t Float
  +
let frameTimeE = frameT <$> fE
  +
  +
-- transform to an IO action :: Event t (IO ())
  +
let printTimeIOE = (putStrLn . show) <$> frameTimeE
  +
  +
-- do the IO actions when they occur
  +
reactimate printTimeIOE
  +
  +
return ()
  +
</haskell>

Revision as of 17:24, 10 February 2013

Bogre-Banana

What is Bogre-Banana

Bogre-Banana is a 3D game engine for Haskell. It uses Haskell bindings to the OGRE 3D engine and OIS input system and a library called Reactive-Banana, to create a "Functional Reactive Programming" game engine. Bogre-Banana is designed to be concise and easy to use.


FRP Crash Course

Functional Reactive Programming (FRP) is a programming paradigm used widely with Functional languages to create interactive programs.

Programming in FRP consists of creating a network of "Behavior"s and "Event"s (although Events are more like event streams). A Behavior represents something that changes through time. An Event represents discrete time specific events. The key difference between a Behavior and an Event is that a Behavior has a value at all times, while an Event only occurs at specific instances of time.

In the context of a game engine, one might have a stream of events for keyboard input. When the user presses a key, a corresponding event is created. The number of times a key is pressed could be expressed as a Behavior based off of the keyboard event stream. This Behavior could be mapped to an output, e.g. displayed on the screen to the user. In a similar way, time, input, and world state can be expressed as Events and Behaviors then combined in various ways to create complex interactions that govern all aspects of the game.

Reactive-Banana is the FRP library used in Bogre-Banana. You can find more information about it at [[1]]


Tutorial 1: Hello 3D World

All Bogre-Banana games start with the runGame function. You must create a GameBuilder :: Frameworks t => HookedBogreSystem t -> SceneManager -> Moment t () function that describes the game. For now we will just create an empty game:

import Graphics.Ogre.Types
import Graphics.Ogre.HOgre

import Reactive.Banana.Frameworks
import Reactive.Banana.BOGRE


main :: IO ()
main = runGame myGame


myGame :: Frameworks t => GameBuilder t
myGame bs smgr = do
        return ()

Running this for the first time will ask you for some graphics settings. Select what you prefer and continue to run the game. You should see a blank window displaying your empty world (you will need to Alt+Tab out of the window to exit).

You may have noticed that the myGame function is a Moment t monad. In this monad we setup the Behaviors and Events we need for the game. We can also do some IO in this monad. For that we simply use the liftIO function. So if we wanted to print out "Hello World!" at the start of the game, we could just make myGame work as follows:

myGame :: Frameworks t => GameBuilder t
myGame bs smgr = do
        liftIO $ putStrLn "Hello World!"
        return ()

More usefully, one could create an initWorld IO function that will setup the world as needed. Say we want to create a light source and put an ogre head in the world. Make sure to have the mesh files in in the "./Media" directory. The code would now look like this:

import Graphics.Ogre.Types
import Graphics.Ogre.HOgre

import Reactive.Banana.Frameworks
import Reactive.Banana.BOGRE


main :: IO ()
main = runGame myGame


-- init the world and return the FRP network
initWorld ::Frameworks t => HookedBogreSystem t -> SceneManager -> IO (SceneNode)
initWorld bs smgr = do
        -- create a light
        l <- sceneManager_createLight_SceneManagerPcharP smgr "MainLight"
        light_setPosition_LightPfloatfloatfloat l 20 80 50
        
        -- load oger head
        ogreHead <- addEntity bs "ogrehead.mesh"
        return ogreHead


myGame :: Frameworks t => GameBuilder t
myGame bs smgr = do
        -- initialize the world
        ogreaHead <- liftIO $ initWorld bs smgr

        return ()

Note that the initWorld function makes use of the Graphics.Ogre.HOgre module (the bindings to the Ogre 3D engine). The SceneNode for the ogre head is returned, but at the moment is not used.

So far the code hasn't taken advantage of FRP. Now lets see how we could use Events to print out the current time at each frame. To get the frame Event, we simply use frameE :: HookedBogreSystem -> Event t BogreFrame. This returns an Event of BogreFrames. We then have to transform the Event to an Event of the time (of type Float), and then to an an Event of IO actions. We use fmap (actually its infix, "<$>") to transform the events. We intend that whenever the IO Event occurs, it is executed. In Reactive-Banana, this is accomplished by passing the IO Event to the reactimate function. The code now looks as follows:

myGame :: Frameworks t => GameBuilder t
myGame bs smgr = do
        -- initialize the world
        ogreaHead <- liftIO $ initWorld bs smgr

        -- get the BogerFrame Event :: Event t BogreFrame
        let fE = frameE bs

        -- transform to the time :: Event t Float
        let frameTimeE = frameT <$> fE

        -- transform to an IO action :: Event t (IO ())
        let printTimeIOE = (putStrLn . show) <$> frameTimeE
        
        -- do the IO actions when they occur
        reactimate printTimeIOE

        return ()