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


<HR>
Creative Commons Copyright -- Some rights reserved.


THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED.

BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE TO BE BOUND BY THE TERMS OF THIS LICENSE. THE LICENSOR GRANTS YOU THE RIGHTS CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND CONDITIONS.

1. Definitions

  • "Collective Work" means a work, such as a periodical issue, anthology or encyclopedia, in which the Work in its entirety in unmodified form, along with a number of other contributions, constituting separate and independent works in themselves, are assembled into a collective whole. A work that constitutes a Collective Work will not be considered a Derivative Work (as defined below) for the purposes of this License.
  • "Derivative Work" means a work based upon the Work or upon the Work and other pre-existing works, such as a translation, musical arrangement, dramatization, fictionalization, motion picture version, sound recording, art reproduction, abridgment, condensation, or any other form in which the Work may be recast, transformed, or adapted, except that a work that constitutes a Collective Work will not be considered a Derivative Work for the purpose of this License. For the avoidance of doubt, where the Work is a musical composition or sound recording, the synchronization of the Work in timed-relation with a moving image ("synching") will be considered a Derivative Work for the purpose of this License.
  • "Licensor" means the individual or entity that offers the Work under the terms of this License.
  • "Original Author" means the individual or entity who created the Work.
  • "Work" means the copyrightable work of authorship offered under the terms of this License.
  • "You" means an individual or entity exercising rights under this License who has not previously violated the terms of this License with respect to the Work, or who has received express permission from the Licensor to exercise rights under this License despite a previous violation.
  • "License Elements" means the following high-level license attributes as selected by Licensor and indicated in the title of this License: Attribution, ShareAlike.

2. Fair Use Rights

Nothing in this license is intended to reduce, limit, or restrict any rights arising from fair use, first sale or other limitations on the exclusive rights of the copyright owner under copyright law or other applicable laws.

3. License Grant

Subject to the terms and conditions of this License, Licensor hereby grants You a worldwide, royalty-free, non-exclusive, perpetual (for the duration of the applicable copyright) license to exercise the rights in the Work as stated below:

  • to reproduce the Work, to incorporate the Work into one or more Collective Works, and to reproduce the Work as incorporated in the Collective Works;
  • to create and reproduce Derivative Works;
  • to distribute copies or phonorecords of, display publicly, perform publicly, and perform publicly by means of a digital audio transmission the Work including as incorporated in Collective Works;
  • to distribute copies or phonorecords of, display publicly, perform publicly, and perform publicly by means of a digital audio transmission Derivative Works.
  • For the avoidance of doubt, where the work is a musical composition:
    • Performance Royalties Under Blanket Licenses. Licensor waives the exclusive right to collect, whether individually or via a performance rights society (e.g. ASCAP, BMI, SESAC), royalties for the public performance or public digital performance (e.g. webcast) of the Work.
    • Mechanical Rights and Statutory Royalties. Licensor waives the exclusive right to collect, whether individually or via a music rights society or designated agent (e.g. Harry Fox Agency), royalties for any phonorecord You create from the Work ("cover version") and distribute, subject to the compulsory license created by 17 USC Section 115 of the US Copyright Act (or the equivalent in other jurisdictions).
    • Webcasting Rights and Statutory Royalties. For the avoidance of doubt, where the Work is a sound recording, Licensor waives the exclusive right to collect, whether individually or via a performance-rights society (e.g. SoundExchange), royalties for the public digital performance (e.g. webcast) of the Work, subject to the compulsory license created by 17 USC Section 114 of the US Copyright Act (or the equivalent in other jurisdictions).


The above rights may be exercised in all media and formats whether now known or hereafter devised. The above rights include the right to make such modifications as are technically necessary to exercise the rights in other media and formats. All rights not expressly granted by Licensor are hereby reserved.

4. Restrictions

The license granted in Section 3 above is expressly made subject to and limited by the following restrictions:

  • You may distribute, publicly display, publicly perform, or publicly digitally perform the Work only under the terms of this License, and You must include a copy of, or the Uniform Resource Identifier for, this License with every copy or phonorecord of the Work You distribute, publicly display, publicly perform, or publicly digitally perform. You may not offer or impose any terms on the Work that alter or restrict the terms of this License or the recipients' exercise of the rights granted hereunder. You may not sublicense the Work. You must keep intact all notices that refer to this License and to the disclaimer of warranties. You may not distribute, publicly display, publicly perform, or publicly digitally perform the Work with any technological measures that control access or use of the Work in a manner inconsistent with the terms of this License Agreement. The above applies to the Work as incorporated in a Collective Work, but this does not require the Collective Work apart from the Work itself to be made subject to the terms of this License. If You create a Collective Work, upon notice from any Licensor You must, to the extent practicable, remove from the Collective Work any credit as required by clause 4(c), as requested. If You create a Derivative Work, upon notice from any Licensor You must, to the extent practicable, remove from the Derivative Work any credit as required by clause 4(c), as requested.
  • You may distribute, publicly display, publicly perform, or publicly digitally perform a Derivative Work only under the terms of this License, a later version of this License with the same License Elements as this License, or a Creative Commons iCommons license that contains the same License Elements as this License (e.g. Attribution-ShareAlike 2.5 Japan). You must include a copy of, or the Uniform Resource Identifier for, this License or other license specified in the previous sentence with every copy or phonorecord of each Derivative Work You distribute, publicly display, publicly perform, or publicly digitally perform. You may not offer or impose any terms on the Derivative Works that alter or restrict the terms of this License or the recipients' exercise of the rights granted hereunder, and You must keep intact all notices that refer to this License and to the disclaimer of warranties. You may not distribute, publicly display, publicly perform, or publicly digitally perform the Derivative Work with any technological measures that control access or use of the Work in a manner inconsistent with the terms of this License Agreement. The above applies to the Derivative Work as incorporated in a Collective Work, but this does not require the Collective Work apart from the Derivative Work itself to be made subject to the terms of this License.
  • If you distribute, publicly display, publicly perform, or publicly digitally perform the Work or any Derivative Works or Collective Works, You must keep intact all copyright notices for the Work and provide, reasonable to the medium or means You are utilizing: (i) the name of the Original Author (or pseudonym, if applicable) if supplied, and/or (ii) if the Original Author and/or Licensor designate another party or parties (e.g. a sponsor institute, publishing entity, journal) for attribution in Licensor's copyright notice, terms of service or by other reasonable means, the name of such party or parties; the title of the Work if supplied; to the extent reasonably practicable, the Uniform Resource Identifier, if any, that Licensor specifies to be associated with the Work, unless such URI does not refer to the copyright notice or licensing information for the Work; and in the case of a Derivative Work, a credit identifying the use of the Work in the Derivative Work (e.g., "French translation of the Work by Original Author," or "Screenplay based on original Work by Original Author"). Such credit may be implemented in any reasonable manner; provided, however, that in the case of a Derivative Work or Collective Work, at a minimum such credit will appear where any other comparable authorship credit appears and in a manner at least as prominent as such other comparable authorship credit.

5. Representations, Warranties and Disclaimer

UNLESS OTHERWISE AGREED TO BY THE PARTIES IN WRITING, LICENSOR OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE MATERIALS, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY, FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS, WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU.

6. Limitation on Liability.

EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.

7. Termination

  • This License and the rights granted hereunder will terminate automatically upon any breach by You of the terms of this License. Individuals or entities who have received Derivative Works or Collective Works from You under this License, however, will not have their licenses terminated provided such individuals or entities remain in full compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will survive any termination of this License.
  • Subject to the above terms and conditions, the license granted here is perpetual (for the duration of the applicable copyright in the Work). Notwithstanding the above, Licensor reserves the right to release the Work under different license terms or to stop distributing the Work at any time; provided, however that any such election will not serve to withdraw this License (or any other license that has been, or is required to be, granted under the terms of this License), and this License will continue in full force and effect unless terminated as stated above.

8. Miscellaneous

  • Each time You distribute or publicly digitally perform the Work or a Collective Work, the Licensor offers to the recipient a license to the Work on the same terms and conditions as the license granted to You under this License.
  • Each time You distribute or publicly digitally perform a Derivative Work, Licensor offers to the recipient a license to the original Work on the same terms and conditions as the license granted to You under this License.
  • If any provision of this License is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this License, and without further action by the parties to this agreement, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable.
  • No term or provision of this License shall be deemed waived and no breach consented to unless such waiver or consent shall be in writing and signed by the party to be charged with such waiver or consent.
  • This License constitutes the entire agreement between the parties with respect to the Work licensed here. There are no understandings, agreements or representations with respect to the Work not specified here. Licensor shall not be bound by any additional provisions that may appear in any communication from You. This License may not be modified without the mutual written agreement of the Licensor and You.