Difference between revisions of "User talk:DavidE"

From HaskellWiki
Jump to navigation Jump to search
(Replacing page with '= Bogre-Banana = Please go here for the latest version of this page: [http://www.haskell.org/haskellwiki/Bogre-Banana Bogre-Banana]')
 
(6 intermediate revisions by 2 users not shown)
Line 1: Line 1:
 
= Bogre-Banana =
 
= Bogre-Banana =
   
  +
Please go here for the latest version of this page: [http://www.haskell.org/haskellwiki/Bogre-Banana 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 has been 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 [[http://www.haskell.org/haskellwiki/Reactive-banana]]. It is highly recommended that you read the Reactive-Banana tutorial [[http://www.haskell.org/haskellwiki/FRP_explanation_using_reactive-banana]]
 
 
== Installation ==
 
 
Bogre-Banana can be installed through the cabal-install tool. There are, however, some required libraries needed before cabal-install can correctly build Bogre-Banana. For Ubuntu 12.10, simply run this to install the required packages:
 
 
<code>
 
$ sudo apt-get install ghc ghc-haddock cabal-install libogre-dev libxrandr-dev libois-dev g++
 
</code>
 
 
One all these packages have been installed, simply run cabal-install to build Bogre-Banana:
 
 
<code>
 
$ cabal update
 
 
$ export PATH=$PATH:~/.cabal/bin
 
 
$ cabal install bogre-banana
 
</code>
 
 
note that adding ~/.cabal/bin to PATH is required so that the build proces can properly use the cgen tool (a dependency of Bogre-Banana that should be pulled in by cabal-install).
 
 
== Environment ==
 
 
Make sure that in the run directory there is an ogre "plugins.cfg" file with this contents:
 
<haskell>
 
# Defines plugins to load
 
 
# Define plugin folder
 
PluginFolder=/usr/lib/i386-linux-gnu/OGRE-1.7.4
 
 
# Define D3D rendering implementation plugin
 
Plugin=RenderSystem_GL.so
 
Plugin=Plugin_ParticleFX.so
 
Plugin=Plugin_BSPSceneManager.so
 
Plugin=Plugin_OctreeSceneManager.so
 
#Plugin=Plugin_CgProgramManager.so
 
</haskell>
 
 
Note that you may need to change "/usr/lib/i386-linux-gnu/OGRE-1.7.4" if you have installed a different version of OGRE.
 
 
Aside of the run directory you can place a Media directory to contain all your mesh/texture/material/etc. data (The run and Media directory should be in the same directory: /somePath/run and /somePath/Media).
 
 
== Tutorial 1: Hello 3D World ==
 
 
All Bogre-Banana games start with the <hask>runGame</hask> function. You must create a <hask>GameBuilder :: Frameworks t => HookedBogreSystem t -> SceneManager -> Moment t ()</hask> function that describes the game. The HookedBogreSystem is needed by many functions in the framework. The SceneManager is mainly used to initialize the world. For now we will just create an empty game:
 
 
<haskell>
 
import Graphics.Ogre.Types
 
import Graphics.Ogre.HOgre
 
 
import Reactive.Banana
 
import Reactive.Banana.Frameworks
 
import Reactive.Banana.BOGRE
 
 
import BB.Util.Vec
 
 
 
main :: IO ()
 
main = runGame myGame
 
 
 
myGame :: Frameworks t => GameBuilder t
 
myGame bs smgr = do
 
return ()
 
</haskell>
 
 
Notice the use <hask>Frameworks t</hask>, it is used by the Reactive-Banana library, and will be in the type of each Behavior and Event. For the most part it can be ignored.
 
 
Running this code 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 <hask>myGame</hask> function is a <hask>Moment t</hask> 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 <hask>liftIO</hask> function. So if we wanted to print out "Hello World!" at the start of the game, we could just make myGame work as follows:
 
 
<haskell>
 
myGame :: Frameworks t => GameBuilder t
 
myGame bs smgr = do
 
liftIO $ putStrLn "Hello World!"
 
return ()
 
</haskell>
 
 
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:
 
 
<haskell>
 
import Graphics.Ogre.Types
 
import Graphics.Ogre.HOgre
 
 
import Reactive.Banana
 
import Reactive.Banana.Frameworks
 
import Reactive.Banana.BOGRE
 
 
import BB.Util.Vec
 
 
 
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 ()
 
</haskell>
 
 
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 = print <$> frameTimeE
 
 
-- do the IO actions when they occur
 
reactimate printTimeIOE
 
 
return ()
 
</haskell>
 
 
Now what if we want to move the ogre head in a circle as a function of time. We can use Bogre-bananas <hask>setPosB :: HookedBogreSystem t -> SceneNode -> Behavior t Vec3 -> Moment t ()</hask> function to set the position of the ogre head according to a Behavior. We fist convert our frameTimeE from an Event to a Behavior, then transform it into a Behavior of positions. Converting an event to a position can be done with the stepper function which creates a Behavior with an initial value, then updates its value whenever the event occurs. Here is our code now:
 
 
<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 a Behavior :: Behavior t Float
 
let timeB = stepper 0 frameTimeE
 
 
-- transform to a position Behavior :: Behavior t Vec3
 
let posB = (\time -> scale 50 (sin time, cos time, 0)) <$> timeB
 
 
-- set the position of the ogre head to the position Behavior
 
setPosB bs ogreaHead posB
 
 
return ()
 
</haskell>
 
 
Now that you've gotten around to using some of the basic FRP building blocks, I admit there is a much easier way of getting timeB using the <hask>getTimeB HookedBogreSystem t -> Behavior t Float</hask> function. the code now looks like:
 
 
<haskell>
 
myGame :: Frameworks t => GameBuilder t
 
myGame bs smgr = do
 
-- initialize the world
 
ogreaHead <- liftIO $ initWorld bs smgr
 
 
-- get frame time :: Behavior t Float
 
let timeB = getTimeB bs
 
 
-- transform to a position Behavior :: Behavior t Vec3
 
let posB = (\time -> scale 50 (sin time, cos time, 0)) <$> timeB
 
 
-- set the position of the ogre head to the position Behavior
 
setPosB bs ogreaHead posB
 
 
return ()
 
</haskell>
 
 
Now we are getting the hang of things, why not try and get some input working. Let's try and move the ogre head according to the mouse. here we can simply get the mouse position as a Behavior using the <hask>getMousePosB :: Frameworks t => HookedBogreSystem t -> Moment t (Behavior t Vec3)</hask> function:
 
 
<haskell>
 
myGame :: Frameworks t => GameBuilder t
 
myGame bs smgr = do
 
-- initialize the world
 
ogreaHead <- liftIO $ initWorld bs smgr
 
 
-- get the mouse position Behavior :: Behavior t Vec3
 
let posB = getMousePosB bs
 
 
-- set the position of the ogre head to the mouse position Behavior
 
setPosB bs ogreaHead posB
 
 
return ()
 
</haskell>
 
 
Wasn't that easy! As a final touch, let's have a look at keyboard input. Keyboard input comes in the form of an Event. We can use the <hask>getKeyDownE :: HookedBogreSystem t -> KeyCode -> Moment t (Event t KeyState)</hask> function to get an Event that occurs whenever the given key is pressed (make sure to import OIS.Types to get the key codes). Let's try and close the window using the escape key (we use the stopBogre function to stop the game):
 
 
<haskell>
 
...
 
import OIS.Types
 
...
 
 
myGame :: Frameworks t => GameBuilder t
 
myGame bs smgr = do
 
-- initialize the world
 
ogreaHead <- liftIO $ initWorld bs smgr
 
 
-- get the mouse position Behavior :: Behavior t Vec3
 
let posB = getMousePosB bs
 
 
-- set the position of the ogre head to the mouse position Behavior
 
setPosB bs ogreaHead posB
 
 
-- get the escape key event :: Event t KeyState
 
let escE = getKeyDownE bs KC_ESCAPE
 
 
-- replace each event with stopBogre
 
let stopGameIOE = (stopBogre bs) <$ escE
 
 
-- do the IO actions when they occur
 
reactimate stopGameIOE
 
 
return ()
 
</haskell>
 
 
Note the use of the <$ operator which is like <$> (infix fmap), but it doesn't transform with a function, it simply replaces each event with the left argument.
 
 
== Exercises ==
 
# Create a second ogre head that respond to the mouse in a different way (e.g make the second head mirror the movements of the first) HINT: You can reuse the mousePosB Behavior.
 
# Have the fist ogre head move with the mouse, and the second continuously circle around it. HINT: we already made an ogre head move in a circle around the origin, now make it relative to the first ogre head.
 
# Have the fist ogre head move with the mouse, and the second move directly toward the first with a speed proportional to their distance. HINT: If it is easier to think in terms of velocity, then you may make use of the <hask>velocityToPositionB :: HookedBogreSystem t -> Vec3 -> Behavior t Vec3 -> Behavior t Vec3</hask> function to convert a velocity Behavior to a position Behavior.
 

Latest revision as of 14:13, 14 April 2013

Bogre-Banana

Please go here for the latest version of this page: Bogre-Banana