Processing Mouse and Keyboard events

In this tutorial we will be expanding on the program in the Render loop tutorial to show the basics of input event processing. The following topics are covered:

  1. Creating a project
  2. Adding an event handler
  3. Processing events
  4. Moving the camera

The tutorial's code files and media files are installed along with Ginjo-Builder and can be accessed from the 'Project' window that is shown when the editor starts. This tutorial is called '02_MouseKeyboard' in the 'Tutorials' tab.

1. Creating a project

First we create a project and compiler options XML file. (See Render loop for a more detailed description of creating a project.)

  1. Create a new project through the Project menu.
  2. Right click on project's top level tree node named "New GBP(Unsaved)" and select Rename folder. Rename the folder to "Events".
  3. Save the project as "EventsProject.gbp" from the Project menu.
  4. Add a new file to the project tree and save it as "procEvents.gbc"
  5. Add a new file to the project tree and save it as "Events.xml". (The XML file must have the same name as the top level tree node.)

Open "Events.xml", and just like in Render loop add the <exe> and <startup> elements. We will also import the GB namespace so we don't have to type it out every time. (For the full list of XML tags see Compiler options.)

<?xml version="1.0" encoding="utf-8"?>
<root>
   <exe path=".\game.exe" />
   <startup name="procEvents.main" />
   
   <ImportSymbols>
      <namespace name="GB"  />
   </ImportSymbols>
</root>

2. Adding an event handler

In our XML file we specified the startup function as "procEvents.main". Double-click on "procEvents.gbc" in the project tree to open it, and add the following function, called "main". (When you run your program this is the function where execution starts.) Initialize the Irrlicht 3D engine and add the Warrior model as in the Render loop tutorial. In this tutorial we do not have any lights, thus the Lighting material flag of the model must be turned to false. (Otherwise the model will be completely black.) In the next section ('Processing events') we will be making use of four global variables to pass information between our event processing function and our render loop. Add the four variables (ZoomNeeded, MouseChange, PrevPos and LeftBtnDown) before the 'main' function.

var:Int32 ZoomNeeded=0
var:int32 MouseChange=0	//the change in X position of the mouse
var:int32 PrevPos=0	//the previous X position of the mouse
var:Boolean LeftBtnDown=false

function main(var:string cmdArgs[]) returns Int32
{
   var:gb.IrrlichtCreationParameters options=new gb.IrrlichtCreationParameters()
   //Without anti-aliasing our model edges would be jagged 
   options.AntiAliasing=255
   //Tell Irrlicht to use OpenGL for graphics
   options.DriverType=gb.video.DriverType.OpenGL
   //Turn off logging, we won't be using it for now
   options.LoggingLevel=gb.LogLevel.None
   
   var:gb.IrrlichtDevice engine = gb.IrrlichtDevice.CreateDevice(options)
   //Also set our window's title
   engine.SetWindowCaption('Event handling')
   
   var:video.VideoDriver driver=engine.VideoDriver
   var:Scene.SceneManager smgr=engine.SceneManager

   //add camera 
   var:scene.CameraSceneNode Camera= smgr.AddCameraSceneNode()
   camera.Position=new core.Vector3Df(10,30,-100)

   //load model from relative path ..\media\warrior.x
   var:scene.AnimatedMesh WarriorMesh = smgr.GetMesh('..\media\warrior.x')
   //add model to scene
   var:Scene.AnimatedMeshSceneNode Warrior
   Warrior = smgr.AddAnimatedMeshSceneNode(WarriorMesh)

   if(!isNull(Warrior)){
      Warrior.Scale = new core.Vector3Df(20)
      Warrior.SetMaterialFlag(video.MaterialFlag.Lighting, false)
      camera.Target = Warrior.Position + new core.Vector3Df(0,20,0)
   }
}

In the next section we will write a function called inputHandler to process events, but first we will specify in our 'main' function that the Irrlicht engine should pass events to our inputHandler function. The following two lines of code create a new EventHandler and register it to handle our Irrlicht engine's OnEvent event. (See Events and Delegates for details.) Add these two lines to your 'main' function after "engine.SetWindowCaption('Event handling')"

//Create an event handler that delegates to the 'inputHandler' function
var:gb.IrrlichtDevice.EventHandler Handler=new gb.IrrlichtDevice.EventHandler(toDelegate(inputHandler))
addhandler(engine.OnEvent, Handler)

3. Processing events

When an event occurs it is passed by Irrlicht to any function that is registered to handle the OnEvent event. (Note that at the end of the function we return false, otherwise other event handlers such as the Maya or FPS camera in the Cameras tutorial won't receive the input.) The Event object can contain 6 different types of events: GUI, Joystick, Key, Log or User. First we will need to test which event type is contained in the Event object.

//This is the function that is passed into IrrlichtDevice.EventHandler in our 'main' function
function inputHandler(var:gb.Event inputEvent) returns Boolean{
   //inputEvent can represent many different types: GUI,Joystick,Key,Mouse,Log,User
   //Do we have a mouse event?
   if(inputEvent.Type = gb.EventType.Mouse){
      switch(inputEvent.Mouse.Type){
         case(MouseEventType.LeftDown){
            LeftBtnDown=true
            PrevPos=inputEvent.Mouse.X
         }
         case(MouseEventType.LeftUp){
            LeftBtnDown=false
         }
         case(MouseEventType.Move){
            if(LeftBtnDown){
               MouseChange+=PrevPos-inputEvent.Mouse.X
               PrevPos=inputEvent.Mouse.X
            }
         }
      }
   }

   //Do we have a keyboard event?
   if(inputEvent.Type=gb.EventType.Key){
      //keyup event for the minus sign on the number pad
      if(inputEvent.Key.Key=gb.KeyCode.subtract && !inputEvent.Key.PressedDown)
      {
         ZoomNeeded-=1
      }
      //keyup event for the plus sign on the number pad
      elseif(inputEvent.Key.Key=gb.KeyCode.Add && !inputEvent.Key.PressedDown)
      {
         ZoomNeeded+=1
      }
   }
   //return true to prevent further processing of the event
   return false;
}

4. Moving the camera

In the previous section we create the inputHandler function that changes the MouseChange and ZoomNeeded global variables based on user input. We will now continue our 'main' function by adding a render loop exactly as in the first tutorial:

var:video.Color background=new video.Color(160, 160, 160)
while (engine.Run()){
   driver.BeginScene(video.ClearBufferFlag.All, background)
   smgr.DrawAll()
   driver.EndScene()
}

However, before we redraw the scene we are going to change the camera's Z position based on the ZoomNeeded variable and the X position based on the MouseChange variable. In the code below we also restrict the position of the camera to between -200 and -30 for the Z position and to between -100 and 100 for the X position. Our render loop will look as follows:

var:video.Color background=new video.Color(160, 160, 160)
while (engine.Run()){
   //check if we need to zoom in or out, move the camera accordingly
   if(ZoomNeeded<0 && camera.Position.Z>-200 ){
      camera.Position=new core.Vector3Df(camera.Position.X,camera.Position.Y, camera.Position.Z+ZoomNeeded*10)
   }
   elseif(ZoomNeeded>0 && camera.Position.Z<-30 ){
      camera.Position=new core.Vector3Df(camera.Position.X,camera.Position.Y, camera.Position.Z+ZoomNeeded*10)
   }
   ZoomNeeded=0

   //check if the mouse has moved while the left button is held down
   //move the camera accordingly
   if(MouseChange<0 && camera.Position.x>-100 ){
      camera.Position=new core.Vector3Df(camera.Position.X+MouseChange,camera.Position.Y, camera.Position.Z)
   }
   elseif(MouseChange>0 && camera.Position.x<100 ){
      camera.Position=new core.Vector3Df(camera.Position.X+MouseChange,camera.Position.Y, camera.Position.Z)
   }
   MouseChange=0

   //now redraw the scene
   driver.BeginScene(video.ClearBufferFlag.All, background)
   smgr.DrawAll()
   driver.EndScene()
}