Mogre New Basic Tutorial 4        
Tutorial Introduction
Ogre Tutorial Head In this tutorial we will be introducing one of the most useful Ogre constructs: the FrameListener. We also be introducing you to input handling in Mogre using MOIS. By the end of this tutorial you will understand FrameListeners, how to use FrameListeners to do things that require updates every frame, and how to use MOIS.


If you find any errors in this tutorial please send a private message to amirabiri.

Prerequisites

  • This tutorial assumes you have knowledge of C# programming and are able to setup and compile a Mogre application.
  • This tutorial assumes that you are familiar with C# events and delegates.
  • This tutorial also assumes that you have created a project using the Mogre Wiki Tutorial Framework.
  • This tutorial builds on the previous tutorials, and it assumes you have already worked through them.


Getting Started

As with the previous tutorials, we will be using the Mogre Wiki Tutorial Framework as our starting point. We will be adding code to the CreateScene, CreateFrameListeners and InitializeInput methods. Add the following code to your tutorial class:

Protected Overrides Sub CreateFrameListeners()
End Sub

Protected Overrides Sub InitializeInput()
End Sub

Frame Listeners

In Ogre, we can register a class to receive notification before and after a frame is rendered to the screen. Such a class is known as a Frame Listener. In Mogre this behaviour has been translated to .NET events. This means that using frame listeners is silghtly different in Mogre compared to Ogre. However the difference is minor and not really relevant if we are just using Mogre. Just remember that in Ogre it is necessary to register a whole class as a frame listener (observer pattern) and implement several methods for all the events described below. In Mogre on the other hand we just use events and we register single methods only to the events we are interested in.

The Frame Events

There are three frame events in Mogre:

Function FrameStarted(ByVal evt As FrameEvent) As Boolean
Function FrameRenderingQueued(ByVal evt As FrameEvent) As Boolean
Function FrameEnded(ByVal evt As FrameEvent) As Boolean

FrameStarted Called just before a frame is rendered.
FrameRenderingQueued Called after all render targets have had their rendering commands issued, but before
the render windows have been asked to flip their buffers over
FrameEnded Called just after a frame has been rendered.


This loops until any of the FrameListeners return false from FrameStarted, FrameRenderingQueued or FrameEnded. The return values for these functions basically mean "keep rendering". If you return false from either, the program will exit.

The FrameEvent object contains two variables, but only the timeSinceLastFrame is useful in a FrameListener. This variable keeps track of how long it's been since the FrameStarted or FrameEnded last fired. Note that in the FrameStarted method, FrameEvent.timeSinceLastFrame will contain how long it has been since the last FrameStarted event was last fired (not the last time a FrameEnded event was fired).

One important concept to realize about Mogre's FrameListeners is that the order in which they are called is entirely up to Mogre. You cannot determine which FrameListener is called first, second, third...and so on. If you need to ensure that FrameListeners are called in a certain order, then you should register only one FrameListener and have it perform all the calls in the proper order.

So, which one of the three frame listener events should you choose?

Since nothing happens in between the FrameEnded and FrameStarted methods being called, you can use them almost interchangably. Where you decide to put your code is entirely up to you. You can put it all in one big FrameStarted or FrameEnded handler, or you could divide it up between the two.

The FrameRenderingQueued Event

However, if you want to update your stuff once per frame, you should do that in the FrameRenderingQueued event, because that one is called just before the GPU is made busy by flipping the render buffer. So you want to keep your CPU busy while the GPU works.

Or, to quote the API docs:

The usefulness of this event comes from the fact that rendering
commands are queued for the GPU to process. These can take a little
while to finish, and so while that is happening the CPU can be doing
useful things. Once the request to 'flip buffers' happens, the thread
requesting it will block until the GPU is ready, which can waste CPU
cycles. Therefore, it is often a good idea to use this callback to
perform per-frame processing. Of course because the frame's rendering
commands have already been issued, any changes you make will only
take effect from the next frame, but in most cases that's not noticeable.


In other words as a rule of thumb you should use FrameRenderingQueued to perform your per-frame updates for performance.

Registering a Frame Listener

Given all of the above, registering a frame listener in Mogre is very easy. Here is an example:

AddHandler mRoot.FrameStarted, AddressOf OnFrameStarted


The tutorials framework provides an appropriate method for creating frame listeners called CreateFrameListener, which can be overridden. The tutorials framework uses this method to register a FrameRenderingQueued listener called OnFrameRenderingQueued which is also overrideable. The tutorials framework uses this frame listener to process mouse and keyboard input to move around the scene. In other words if we discard the framework's FrameRenderingQueued behaviour, we will loose the ability to move and look around that the tutorials framework provides.

To avoid this we will use the typical object oriented solution to this problem - we will call the parent class' implementation before adding our own code. Add the following code to your CreateFrameListeners method:

MyBase.CreateFrameListeners()


In this tutorial we will add more mouse and keyboard processing to modify the scene. We will be moving a ninja around and turning a light on and off. To do so we require a frame listener. Add the following code to your CreateFrameListeners method:

AddHandler mRoot.FrameRenderingQueued, AddressOf Me.ProcessUnbufferedInput


We are trying to register a method called ProcessUnbufferedInput as a frame listener but this method doesn't exist yet. The code will not compile right now. So let's add a basic ProcessUnbufferedInput method that doesn't do anything yet.

Protected Function ProcessUnbufferedInput(ByVal evt As FrameEvent) As Boolean
    Return True
End Function


Note that we are always returning true from this method - remember, if at any point any frame listener returns false, Ogre will break out of its main render loop and the program will end (the tutorial framework does just that when you press ESC).

Your CreateFrameListeners and ProcessUnbufferedInput methods should look something like this now:

Protected Overrides Sub CreateFrameListeners()
    MyBase.CreateFrameListeners()
    AddHandler mRoot.FrameRenderingQueued, AddressOf Me.ProcessUnbufferedInput
End Sub

Protected Function ProcessUnbufferedInput(ByVal evt As FrameEvent) As Boolean
    Return True
End Function


This is all that is required to add a frame listener. As you can see it's very simple. In the next section we will be adding code to this frame listener to process keyboard and mouse input.

Processing Input

MOIS is a .NET wrapper for OIS (Object oriented Input System). It is an object oriented input library and supports many different devices in three basic device categories (keyboards, mice, and joysticks).

OIS It is not strictly speaking part of Ogre, they are two separate systems. However OIS is bundled with Ogre and a good place to start learning processing input with Ogre. Consequently MOIS isn't strictly speaking part of Mogre, but it's bundled with the Mogre SDK. Later on when you have mastered Mogre and MOIS you can look into the other input systems on your own.

Another important note about MOIS/OIS is that unlike Mogre/Ogre, OIS is statically linked into MOIS. What that means is that there is no need to keep OIS.dll together with MOIS.dll (unlike Mogre.dll and OgreMain.dll). MOIS.dll is a standalone dll.

And finally, as of this writing, there is no available binary release of MOIS compiled against .NET 4.0. The 2010 versions of the tutorials framework use an app.config file to tell the .NET system to load the MOIS assembly anyway (mixed mode assembly). If you later create your own projects from scratch, you will have to do the same. Hopefully we will soon have .NET 4.0 versions of MOIS available.

Initializing MOIS

Before we can start using MOIS, we must initialize it. The code to intialize MOIS is a bit ugly but very short and simple. The reason it's a bit ugly is that Ogre and OIS are abstract systems but they need to pass a platform-specific piece of information between them - the render window's handle. The following code will create an InputManager object:

Dim windowHandle As Integer
mWindow.GetCustomAttribute("WINDOW", windowHandle)
Dim inputMgr = MOIS.InputManager.CreateInputSystem(windowHandle)


What we are doing here is using the render window's GetCustomAttribute method to acquire the window handle, which is needed by the InputManager object to know which window to poll input information from. We then use the InputManager class method CreateInputSystem to create an InputManager against the given window handle.

But where should you add the code above? The tutorial framework has another overrideable method called InitializeInput. We can override it and add our MOIS initialization code there. However, the tutorial framework uses this method to do its own input initlization to provide us with camera movement mentioned above. So we will do the same thing we've done with the frame listeners: override and call the framework's base method:

Protected mInputMgr      As MOIS.InputManager
Protected mNinjaKeyboard As MOIS.Keyboard
Protected mNinjaMouse    As MOIS.Mouse

Protected Overrides Sub InitializeInput()
    MyBase.InitializeInput()

    Dim windowHandle As Integer
    mWindow.GetCustomAttribute("WINDOW", windowHandle)
    mInputMgr = MOIS.InputManager.CreateInputSystem(windowHandle)
End Sub


By doing this we get an InputManager object without compromising the tutorial framework's own InputManager. Of course under normal circumstances you would only want one InputManager object to exist - what we've done here is wasteful. But for the purpose of this tutorial it's OK. The tutorial framework will continue to provide us with the camera movement functionality that we've grown accustomed to, and we will use our own InputManager to add more input processing. As long as we don't use the same keys as the framework does we'll be OK.

Unbuffered Input

MOIS/OIS have two main modes of operation - buffered and unbuffered. The differences between them will become clear as you progress through the next sections. We'll start with unbuffered input which is the simpler of the two.

To read user input from the mouse and keyboard, we need to create keyboard and mouse objects using the InputManager we just created. Add the following code to your InitializeInput method:

mNinjaKeyboard = mInputMgr.CreateInputObject(MOIS.Type.OISKeyboard, False)
mNinjaMouse    = mInputMgr.CreateInputObject(MOIS.Type.OISMouse,    False)


Note the second parameter passed to each call to CreateInputObject. This parameter tells MOIS whether we want a buffered or unbuffered input object. Also note that there is no use of Generics or anything similar here - we must apply the correct cast on the object returned by CreateInputObject (only in C# - in VB.NET the cast is implicit). However since this should only happen once and the cast always matches the first argument to CreateInputObject, it's not a big deal.

To see the results of our input processing, we will create a simple scene that we can manipulate based on the keyboard and mouse input. This simple scene will consist of the ninja we used in the previous tutorial and a point light. We'll move the ninja around using the I,J,K and L keys, rotate him with the mouse, and use the spacebar as a light switch for the point light.

Add the following code to your tutorial class:

Protected mNinjaEntity        As Entity
Protected mNinjaNode          As SceneNode
Protected mLight              As Light
Protected mLightToggleTimeout As Single  = 0

Protected Overrides Sub CreateScene()
    mSceneMgr.AmbientLight = New ColourValue(0.25F, 0.25F, 0.25F)

    mNinjaEntity = mSceneMgr.CreateEntity("Ninja", "ninja.mesh")
    mNinjaNode   = mSceneMgr.RootSceneNode.CreateChildSceneNode("NinjaNode")
    mNinjaNode.AttachObject(mNinjaEntity)

    mLight = mSceneMgr.CreateLight("pointLight")
    mLight.Type           = Light.LightTypes.LT_POINT
    mLight.Position       = New Vector3(250, 150, 250)
    mLight.DiffuseColour  = ColourValue.White
    mLight.SpecularColour = ColourValue.White
End Sub


The ambient light, ninja entity, scene node and the point light should all be familiar to you by now from the previous tutorials. We will use the mLightToggleTimeout variable later in our input processing.

At this point the code should compile and run. Do so now to make sure everything is OK. You should see the ninja and notice the presence of a point light near him. The tutorial framework's built-in input handling should still work, i.e you should still be able to move the camera around and exit the application by pressing the ESC key.

So now that we have a scene and we've acquired mouse and keyboard objects, what can we do with them? Let's return to our ProcessUnbufferedInput frame listener. What we want to do now is poll the information about the keyboard and mouse from these objects and manipulate the scene in our frame listener.

We start by capturing the user input. Add the following code to your ProcessUnbufferedInput method:

mNinjaKeyboard.Capture()
mNinjaMouse.Capture()


Calling Capture tells MOIS to read the user input. The keyboard and mouse objects will now be updated with the current state of the mouse and keyboard. We can now act based on this information.

Keyboard

We want to move the ninja around using the I,J,K,L keys. So let's do so. Add the following code to your ProcessUnbufferedInput method:

Dim ninjaMove As Vector3 = Vector3.ZERO

If mNinjaKeyboard.IsKeyDown(MOIS.KeyCode.KC_I) Then
    ninjaMove.z -= 1
End If

If (mNinjaKeyboard.IsKeyDown(MOIS.KeyCode.KC_K)) Then
    ninjaMove.z += 1
End If

If (mNinjaKeyboard.IsKeyDown(MOIS.KeyCode.KC_J)) Then
    ninjaMove.x -= 1
End If

If (mNinjaKeyboard.IsKeyDown(MOIS.KeyCode.KC_L)) Then
    ninjaMove.x += 1
End If

mNinjaNode.Translate(ninjaMove, Node.TransformSpace.TS_LOCAL)


This code is pretty simple: we create a Vector3 object that represents our desired movement direction. We set the x and z properties of this object if the appropriate keys are pressed. Finally we call Translate which moves the object. The Node.TransformSpace.TS_LOCAL parameter means we move the Ninja relative to his own rotation, which will help us later when we rotate him.

Compile the program and then run it twice: once with vsync on, and once off (vsync is one of the options in the configuration window that pops up when you run the application). You will notice that the ninja moves slowly with vsync on, and moves very fast with vsync off. Why is that happening?

The reason for this behaviour is that we are moving the object the same distance every frame, regardless of the amount of time that passed since the previous frame. This is something important to understand about frame listeners - any type of motion, animation, rotation or time measurement - all must be based on the frame listener's time property. You don't know exactly how many frames per second you will have - you might be running in vsync or might not, the screen sync might be 60 or 75, high load on the computer might reduce your frame rate. You cannot rely on any FPS and you cannot rely on constant sizes in frame listeners.

So what should we do instead? Suppose we want our ninja to move at a rate of 200 points per second. Here's how we do so:

Dim ninjaMove As Vector3 = Vector3.ZERO

If mNinjaKeyboard.IsKeyDown(MOIS.KeyCode.KC_I) Then
    ninjaMove.z -= 200
End If

If (mNinjaKeyboard.IsKeyDown(MOIS.KeyCode.KC_K)) Then
    ninjaMove.z += 200
End If

If (mNinjaKeyboard.IsKeyDown(MOIS.KeyCode.KC_J)) Then
    ninjaMove.x -= 200
End If

If (mNinjaKeyboard.IsKeyDown(MOIS.KeyCode.KC_L)) Then
    ninjaMove.x += 200
End If

mNinjaNode.Translate(ninjaMove * evt.timeSinceLastFrame, Node.TransformSpace.TS_LOCAL)


In this code we are moving the ninja every frame a distance relative to the length of the frame. This is measured by the time passed since the last frame, which is a float value. If we are running at 50 FPS, the value of timeSinceLastFrame will be 0.02f. We multuply that by our vector, which is always of the same constant length - 200, and we get a 200pt/sec movement vector, regardless of FPS.

You will use this technique a lot so make sure you understand it now - almost anything that happens repeatedly in a frame listener must use sizes relative to the frame's time.

Next we'll turn the spacebar into a light switch - every time the spacebar is pressed, the point light we created will turn on and off. Add the following code to your ProcessUnbufferedInput method:

If (mNinjaKeyboard.IsKeyDown(MOIS.KeyCode.KC_SPACE)) Then
    mLight.Visible = Not mLight.Visible
End If


Compile and run the code. What happens when we press the spacebar button? Instead of switching on and off, the light flickers. This problem demonstrates the nature of unbuffered input: in each frame when we ask the keyboard object if the spacebar is pressed, it gives us an answer irespective of the state of the button in the previous frame. In other words MOIS isn't telling us if the spacebar was just pressed, it tells us whether it's down right now or not.

Even the most seasoned gamer presses a button for one or two hundred miliseconds. That's enough time for a few frames. In each one of these frames the keyboard object would say that the spacebar is down. Our code then switches the light on or off. And the result is that the light flickers at the rate of the FPS. This is clearly not what we want. But how can we overcome this?

There are a few ways to overcome this problem, but we'll demonstrate a simple one here. Remember the mLightToggleTimeout variable we mentioned before? Well now we'll use it. Replace the above code with the following one:

If (mLightToggleTimeout > 0) Then
    mLightToggleTimeout -= evt.timeSinceLastFrame
Else
    If (mNinjaKeyboard.IsKeyDown(MOIS.KeyCode.KC_SPACE)) Then
        mLight.Visible = Not mLight.Visible
        mLightToggleTimeout = 0.5F
    End If
End If


Compile and run the code. The spacebar light switch works as expected now. Why?

What we've done is simple: each time we flick the light switch on or off, we set mLightToggleTimeout to 0.5. We then ignore the spacebar button for as long as mLightToggleTimeout is bigger than zero, and we reduce it by the length of the frame. In other words mLightToggleTimeout is a timer of half a second that we set each time we switch the light on or off.

This is also a common technique that you should get used to - creating simple timers by setting a variable to the length of time we want to wait first, then subtracting from it the length of the frame in each frame until it reaches zero or below.

This technique works but it isn't a perfect system in our case. First of if we leave the spacebar pressed the light will still flicker at a rate of twice a second. If we press the spacebar very fast on purpose, it won't respond as fast since it's limited by our timer. In other words we're still not detecting a button press properly, we are just using a simple delay mechanism delay to stop the light from flickering. Still this is good enough for some things and it's enough to demonstrate the basic usage of MOIS in unbuffered mode. A more advanced system could be to use a boolean variable to remember the previous state of the spacebar, and so detect a key press rather than rely on IsKeyDown. This is related to buffered input which we'll discuss soon. But before we talk about buffered input we'll learn how to use the mouse.

Mouse

So what about the mouse? The mouse object is slightly different to the keyboard object. You don't use the mouse object directly, but instead you use its property MouseState. This property in turn (which is an object of class MouseState) provides similar functionality to the keyboard, i.e it has a method called ButtonDown which is similar to the keyboard's IsKeyDown except it accepts an argument of a different enumeration (MOIS.MouseButtonID instead of MOIS.KeyCode). But the principle is the same so there is nothing new to add.

Mouse movement on the other hand is different. The MouseState object has X, Y and Z properties that tell us where MOIS/OIS think the mouse cursor is in terms of integer screen coordinates (The Z is actually redundant). These properties are not integers though - they are Axis objects. They have a property called abs (short for absolute) - the absolute screen position of the mouse. When the mouse moves and we call Capture it updates these values.

But we are not interested in MOIS/OIS idea of where the mouse cursor is for what we are doing here. This will become useful in later tutorials when we build an in-game GUI. Right now we want to know the general mouse movement. The Axis objects provide another property which MOIS/OIS updates for us called rel (short for relative). As you might have guessed, this is the relative change to the mouse cursor position since the last Capture, which is what we want.

So let's rotate the ninja based on mouse movement. The tutorial framework uses the mouse movement to rotate the camera and we want to disable this behaviour while the left mouse button is down and do something else with the mouse movement. This is easy to do. Add the following code to your ProcessUnbufferedInput method:

If mNinjaMouse.MouseState.ButtonDown(MOIS.MouseButtonID.MB_Left) Then
    mCameraMan.Freeze = True
    mNinjaNode.Yaw(New Degree(-mNinjaMouse.MouseState.X.rel * evt.timeSinceLastFrame))
Else
    mCameraMan.Freeze = False
End If


This code is simple: if the left mouse button is down, we freeze the tutorial framework's camera movement, and instead translate the relative mouse movement to rotation of the ninja left and right using Yaw which you've seen before. We are multiplying this value by the time length of the frame, as we should, right?

Compile and run this program twice again with vsync on and off. You should notice that the rotation is slow with vsync on, and very slow with vsync off (You might need to move the mouse a lot to notice any movement at all). What's happening?

The problem here is that MOIS/OIS already give us a relative value between captures. It doesn't matter how much time passed since the last frame, the relative mouse movement since the last Capture call is still the same. Remember that it's measured in integer values. We then took this relative integer value, and multiplied it by the frame length. The result is skewed because it's relative twice.

This flashes out another rule with frame listeners - when the values you are dealing with are already relative you should use them as they are and not multiply them by the frame length. This is something to look out for which might not always be obvious. Your best guide is playing with different frame rates and making sure that the program behaves the same way under different FPSs.

Fixing this problem is simple, simply change the Yaw call above and remove the multiplication by the frame length:

mNinjaNode.Yaw(New Degree(-mNinjaMouse.MouseState.X.rel))


Compile and run the program again. This time the ninja rotation should be the same with or without vsync.

This concludes the usage of unbuffered keyboard and mouse movement with MOIS/OIS. Next we will see how to use buffered input and how it differs from unbuffered input.

Buffered Input

So what is buffered input? Well buffered input as the name might imply means that instead of a simple mechanism that is ignorant of the previous state of the input device or the input events between calls to Capture, buffered input maintains a buffer of input events. This means two things, first that MOIS/OIS can detect and report an individual keypress or button click. An additional benefit is that input events cannot be lost between Capture calls, for example due to lower frame rate.

To use buffered input we have to change the way we create the mouse and keyboard objects. Replace the last two lines in InitializeInput with the following ones:

mNinjaKeyboard = mInputMgr.CreateInputObject(MOIS.Type.OISKeyboard, True)
mNinjaMouse    = mInputMgr.CreateInputObject(MOIS.Type.OISMouse,    True)


The only difference is that the second parameter in each call to CreateInputObject changed from false to true. This tells MOIS that we want to use buffered input.

We still need a frame listener that handles the input. To allow you to clearly see the difference between the two implementations, we'll use a new frame listener instead of deleting the old one. Modify the last line in CreateFrameListeners:

Protected Overrides Sub CreateFrameListeners()
    MyBase.CreateFrameListeners()
    AddHandler mRoot.FrameRenderingQueued, AddressOf Me.ProcessBufferedInput
End Sub


Buffered input works based on callbacks (events in .NET) so we have to register some. Add the following code to your InitializeInput method:

AddHandler mNinjaKeyboard.KeyPressed, AddressOf Me.OnNinjaKeyPressed
AddHandler mNinjaMouse.MousePressed,  AddressOf Me.OnNinjaMousePressed
AddHandler mNinjaMouse.MouseReleased, AddressOf Me.OnNinjaMouseReleased
AddHandler mNinjaMouse.MouseMoved,    AddressOf Me.OnNinjaMouseMoved


What this code does is register callback methods for input events. The methods we specified will be called when a keyboard button is pressed, a mouse button is clicked, or when the mouse moves. They won't be called immediately when the event occurs. Our call to the Capture methods will trigger a series of calls to these methods based on the buffer of input events.

Let's define these methods now. We'll start with OnNinjaKeyPressed that handles a keyboard key down event:

Protected Function OnNinjaKeyPressed(ByVal arg As MOIS.KeyEvent) As Boolean
    Select Case arg.key
        Case MOIS.KeyCode.KC_SPACE
            mLight.Visible = Not mLight.Visible
    End Select

    Return True
End Function


This code is very simple: when a key is pressed the method is called. Which key was pressed is part of the argument of the method. We check which one it is and if it's the spacebar, we toggle the state of the point light.

As you can see it's much simpler to handle a single key press with buffered input. We don't need any delay, and the result is more accurate (try pressing the spacebar fast many times and compare the result to the one using unbuffered input). We used a switch statement in case we want to add more cases in the future. For now we're only interested in one key.

The mouse button press / button released will be very similar:

Protected Function OnNinjaMousePressed(ByVal arg As MOIS.MouseEvent, ByVal id As MOIS.MouseButtonID) As Boolean
    Select Case id
        Case MOIS.MouseButtonID.MB_Left
            mCameraMan.Freeze = True
    End Select

    Return True
End Function

Protected Function OnNinjaMouseReleased(ByVal arg As MOIS.MouseEvent, ByVal id As MOIS.MouseButtonID) As Boolean
    Select Case id
        Case MOIS.MouseButtonID.MB_Left
            mCameraMan.Freeze = False
    End Select

    Return True
End Function


When the left mouse button is pressed we freeze the camera movement. When it's released we unfreeze it. It's much cleaner to deal with the keypress this way: it's more readable and more efficient (we don't repeatedly set the Freeze property unnecessarily).

Rotating the ninja is still a simple task:

Protected Function OnNinjaMouseMoved(ByVal arg As MOIS.MouseEvent) As Boolean
    If (arg.state.ButtonDown(MOIS.MouseButtonID.MB_Left)) Then
        mNinjaNode.Yaw(New Degree(-arg.state.X.rel))
    End If

    Return True
End Function


There is no major difference between this implementation and the unbuffered one, but this is cleaner and more readable at least. Note that in buffered mode the callbacks still have access to the same information that the unbuffered code has.

What about the Ninja movement? Well we can do the same thing for the left mouse button and use four additional boolean variables. But that would be cumbersome. Actually when it comes to simple continuous input the unbuffered approach is better. But we are not forced to discard the unbuffered way of doing things with buffered input - the current state of the input device is still available to us after a call to Capture. We still haven't defined our new frame listener ProcessBufferedInput. So let's do so now:

Protected Function ProcessBufferedInput(ByVal evt As FrameEvent) As Boolean
    mNinjaMouse.Capture()
    mNinjaKeyboard.Capture()

    Dim ninjaMove As Vector3 = Vector3.ZERO

    If mNinjaKeyboard.IsKeyDown(MOIS.KeyCode.KC_I) Then
        ninjaMove.z -= 200
    End If

    If (mNinjaKeyboard.IsKeyDown(MOIS.KeyCode.KC_K)) Then
        ninjaMove.z += 200
    End If

    If (mNinjaKeyboard.IsKeyDown(MOIS.KeyCode.KC_J)) Then
        ninjaMove.x -= 200
    End If

    If (mNinjaKeyboard.IsKeyDown(MOIS.KeyCode.KC_L)) Then
        ninjaMove.x += 200
    End If

    mNinjaNode.Translate(ninjaMove * evt.timeSinceLastFrame, Node.TransformSpace.TS_LOCAL)

    Return True
End Function


This looks exactly the same as the unbuffered implementation. Again note that the same functionality of unbuffered input is avaiable in buffered input mode. Buffered input doesn't cancel the unbuffered approach, it simply adds to it. The price is a small performance penalty for managing the input buffere behind the scenes.

Compile and run the program. Make sure all the same functionality is still present. Note that now if you leave the spacebar button pressed, the light does not flicker.

Conclusion

By now you should have a good understanding of both frame listeners in Mogre, and how to use MOIS to handle input in your Mogre application. Frame listeners are the core of a Mogre application and almost everything from here on will use them in one way or another. You should also now understand the differences between buffered and unbuffered input, so you can pick the one suitable for your needs.



Proceed to Mogre Basic Tutorial 5 The Mogre Startup Sequence