Mogre Basic Tutorial 4
From Ogre Wiki
Beginner Tutorial 4: Frame Listeners and Windows.Forms Input
Original version by Clay Culver
Any problems you encounter while working with this tutorial should be posted to the Mogre Forum.
Contents |
Prerequisites
This tutorial assumes you have knowledge of C# programming and are able to setup and compile an Ogre application (if you have trouble setting up your application, see this guide for help). This tutorial builds on the previous tutorials, so be sure that you have already worked through them.
Introduction
In this tutorial we will be introducing one of the most useful Ogre constructs: the FrameListener. 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 the Windows.Forms input system.
As you go through the tutorial you should be slowly adding code to your own project and watching the results as we build it.
Getting Started
As with the previous tutorials, we will be using a pre-constructed code base as our starting point. Create a Windows Application project and replace the code in program.cs with the following:
using System;
using System.Collections.Generic;
using System.Windows.Forms;
using MogreFramework;
using Mogre;
using System.Drawing;
namespace Tutorial04
{
static class Program
{
[STAThread]
static void Main()
{
try
{
MyOgreWindow win = new MyOgreWindow();
new SceneCreator(win);
win.Go();
}
catch (System.Runtime.InteropServices.SEHException)
{
if (OgreException.IsThrown)
MessageBox.Show(OgreException.LastException.FullDescription, "An Ogre exception has occurred!");
else
throw;
}
}
}
class MyOgreWindow : OgreWindow
{
const float TRANSLATE = 200;
const float ROTATE = 0.2f;
bool mRotating = false;
Vector3 mTranslation = Vector3.ZERO;
Point mLastPosition;
protected override void CreateInputHandler()
{
}
}
class SceneCreator
{
public SceneCreator(OgreWindow win)
{
win.SceneCreating += new OgreWindow.SceneEventHandler(SceneCreating);
}
void SceneCreating(OgreWindow win)
{
win.SceneManager.AmbientLight = new ColourValue(0.25f, 0.25f, 0.25f);
Entity ent = win.SceneManager.CreateEntity("Ninja", "ninja.mesh");
win.SceneManager.RootSceneNode.CreateChildSceneNode().AttachObject(ent);
Light light = win.SceneManager.CreateLight("Light");
light.Type = Light.LightTypes.LT_POINT;
light.Position = new Vector3(250, 150, 250);
light.DiffuseColour = ColourValue.White;
light.SpecularColour = ColourValue.White;
win.Camera.Position = new Vector3(0, 200, 400);
win.Camera.LookAt(ent.BoundingBox.Center);
}
}
}
Make sure you can compile and run the application before continuing. If you are having difficulty, refer to the project setup guide or post to the forums. Since we have overridden the CreateSceneHandler method and not added code to it yet, mouse and camera movement will be disabled for now.
In this tutorial we will start with a "default" scene (see the code snippet above). The only thing interesting to note is the Camera.LookAt call. In this line of code we have set the camera to look at the center of the entity. This works because the node we created resides at the origin (0, 0, 0). If you want to duplicate this functionality later on you have to add the node's position to the entity's bounding box's center.
FrameListeners
Introduction
In the previous tutorials we only looked at what we could do when we create the scene. In Ogre, we can register a class to receive notification before and after a frame is rendered to the screen. The root class provides the FrameStarted and FrameEnded events to handle these actions.
Ogre's main loop looks like this:
- The Root object fires the FrameStarted event.
- The Root object renders one frame.
- The Root object fires the FrameEnded event.
This loops until any of the event handlers return false. The return values for the event handlers 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 Ogre's FrameListeners is that the order in which they are called is entirely up to Ogre. 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 call all of the objects in the proper order.
You might also notice that the main loop really only does three things, and since nothing happens in between the frameEnded and frameStarted methods being called, you can use them almost interchangably. Where you decide to put all of 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.
Frame Listeners vs Timers
Frame listeners are one of the most useful Ogre constructs, as they allow us to incrementally update objects in the scene. You may have also noticed that a Timer (System.Windows.Forms.Timer) could do the same thing, and they allow you to control how often they are fired (as opposed to the frame listeners which fire as fast as you are rendering). In practice, these two things are virtually interchangable, but I have found that these usage guidelines work well for me:
- If you are updating objects which are being rendered you should use a frame listener to update it every frame. For example, if you are moving an object incrementally across the screen, you should use frame listeners.
- If you are performing an action which should happen often, but the result of which is not being directly rendered to the screen, you should use a Timer. For example, let's say your program is running at 600 FPS. You do not need to poll the keyboard, joystick(s), and network interfaces every frame (which would be 600 times per second) when polling it 10 times per second wouldn't be a noticable difference to the user.
You should mix and match frame listeners and timers based on your needs for the program.
Registering a FrameListener
Currently the code we have will run, but since we have overridden the CreateInputHandler method of OgreWindow, keyboard and mouse input is no longer working. In this tutorial we will be adding mouse and keyboard input back into the program.
Since the Root class is what renders frames, it also is in charge of keeping track of FrameListeners. The first thing we need to do is register a FrameStarted event handler in our "MyOgreWindow" class. Find the CreateInputHandler method in MyOgreWindow and add the following code to it:
this.Root.FrameStarted += new FrameListener.FrameStartedHandler(FrameStarted);
Then create a new function to handle this event called "FrameStarted":
bool FrameStarted(FrameEvent evt)
{
return true;
}
Be sure you can compile and run the application before continuing.
Handling User Input
Introduction
Now that we have a basic application set up, we will be writing code to move the camera based on mouse and keyboard input. Since moving the camera changes what we render every frame, we will use a frame listener for this instead of a timer.
Our strategy for keyboard input is to keep track of camera movement with a single Vector3 variable. When the user presses and releases specific keys we will add and subtract from this vector and move the camera by this amount every frame. Our strategy for handling mouse input is a bit more tricky. We want to rotate the camera only when the right mouse button is held down. To accomplish this we will need a boolean variable to keep track of the state of the right mouse button. If you look in the MyOgreWindow class you will see these two variables.
Key Input
The first thing we will do is translate the camera based on key input. Find the CreateInputHandler method in MyOgreWindow and add the following code:
this.KeyDown += new KeyEventHandler(KeyDownHandler);
this.KeyUp += new KeyEventHandler(KeyUpHandler);
Now we will create the KeyDownHandler method and add code to translate in the various directions based on the key:
void KeyDownHandler(object sender, KeyEventArgs e)
{
switch (e.KeyCode)
{
case Keys.Up:
case Keys.W:
mTranslation.z = -TRANSLATE;
break;
case Keys.Down:
case Keys.S:
mTranslation.z = TRANSLATE;
break;
case Keys.Left:
case Keys.A:
mTranslation.x = -TRANSLATE;
break;
case Keys.Right:
case Keys.D:
mTranslation.x = TRANSLATE;
break;
case Keys.PageUp:
case Keys.Q:
mTranslation.y = TRANSLATE;
break;
case Keys.PageDown:
case Keys.E:
mTranslation.y = -TRANSLATE;
break;
}
}
Next we will add code for KeyUpHandler. When the specified key is released, we will set it's corresponding direction in the vector to be zero:
void KeyUpHandler(object sender, KeyEventArgs e)
{
switch (e.KeyCode)
{
case Keys.Up:
case Keys.W:
case Keys.Down:
case Keys.S:
mTranslation.z = 0;
break;
case Keys.Left:
case Keys.A:
case Keys.Right:
case Keys.D:
mTranslation.x = 0;
break;
case Keys.PageUp:
case Keys.Q:
case Keys.PageDown:
case Keys.E:
mTranslation.y = 0;
break;
}
}
Finally, we need to act on this input. Find the FrameStarted method and add the following code:
Camera.Position += Camera.Orientation * mTranslation * evt.timeSinceLastFrame;
This moves the camera's position every frame. We multiply the translation by the camera's orientation so that when we translate, we are moving in the correct direction. That means that even when we hold down the W button we move "forward" no matter what direction the camera faces. If we did not do this, our camera would rotate independantly of its movement. (Which is currently not what we are going for.) We multiply the translation by the time since the last frame to keep the movement smooth, and independent of framerate.
Mouse Input
Now we need to add mouse-look to the program. To do this we will first register handlers for the MouseDown, MouseUp, and MouseMove events. Find the CreateInputHandler method and add the following code:
this.MouseDown += new MouseEventHandler(MouseDownHandler);
this.MouseUp += new MouseEventHandler(MouseUpHandler);
this.MouseMove += new MouseEventHandler(MouseMoveHandler);
When the right mouse button goes down, we want to hide the mouse and start rotating the camera. We also want to mark the current position of the cursor so that we know how much the cursor has moved:
void MouseDownHandler(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Right)
{
Cursor.Hide();
mRotating = true;
mLastPosition = Cursor.Position;
}
}
Similarly when the right mouse button goes up, we want to show the mouse and stop rotating the camera:
void MouseUpHandler(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Right)
{
Cursor.Show();
mRotating = false;
}
}
Finally, when the mouse is moved we need to handle it by rotating the camera, but only when mRotating is true. We will first figure out how much the mouse has moved since the last time we rotated the camera, rotatate the camera, then reset the last position:
void MouseMoveHandler(object sender, MouseEventArgs e)
{
if (mRotating)
{
float x = mLastPosition.X - Cursor.Position.X;
float y = mLastPosition.Y - Cursor.Position.Y;
Camera.Yaw(new Degree(x * ROTATE));
Camera.Pitch(new Degree(y * ROTATE));
mLastPosition = Cursor.Position;
}
}
That's it, run your program. You can now move and rotate the mouse based on user input.
Final Note
This tutorial shows you the basics of implementing an input system using Windows.Forms. For most uses of Mogre (including making games), the Windows.Forms input system is probably inappropriate. You should probably use something like DirectInput (using Managed DirectX), XNA's input system, SDL's input system (Tao.SDL for C#), or some other input system which has been wrapped/ported to .Net. In fact, as you will see in a later tutorial, you do not have to host Ogre in Windows.Forms...and probably shouldn't if you are making a game. On the other hand, if you are making a game editing tool or an application which uses 3D graphics (but doesn't require highly specific user input), Windows.Forms is a great choice for hosting your application. I personally use Tao.SDL for input along with an XBox 360 wired controller for my development. I recommend the non-wireless 360 controller because it works natively with windows (you can install the drivers from the internet), and is an extremely high-quality controller which can be used to test out your gamepad controls.
Even though this tutorial used Windows.Forms for input, the same concepts still apply to any event-driven input system. For a further example of a more fleshed out input handler, you should take a look at the source fore the default input handler in the MogreFramework: http://www.idleengineer.net/tutorials/MogreFramework/MogreFramework/DefaultInputHandler.cs.
Source Code
You can see the final state of this tutorial here: Mogre Basic Tutorial 4 Source
- Proceed to Basic Tutorial 5 The Ogre Startup Sequence

