In this tutorial we are going to look at how to handle Input using Lua scripting ( we will look at C++ later ). I assume you have read the prior tutorials, so you should have the ability to create a new project and populate it with a new entity using the crate model we made earlier. You do not need to export it again, you can simply copy the MODEL and texture files over to the root of your newly created project. So, do that now, create a new project, create a default scene and add the crate model to your scene. Next add a script component to your newly created crate entity. If you have any problems with the above instruction, please refer to prior tutorials in this series.
In this tutorial we cover:
- Polling for keyboard input
- Polling for mouse input
- Creating input maps to handle input in an abstract manner
By the way, this tutorial starts by demonstrating a way you shouldn’t use! Be sure to read the entire post before you do anything.
Handling Keyboard Input
The following script shows how to handle basic keyboard input using Lua. I attached the script to our entity and implement it it’s OnThink callback.
function OnThink(self) if Input:IsKeyPressed(Vision.KEY_UP) then self:IncPosition(0,1,0) elseif Input:IsKeyPressed(Vision.KEY_DOWN) then self:IncPosition(0,-1,0) elseif Input:IsKeyPressed(Vision.KEY_LEFT) then self:IncPosition(-1,0,0) elseif Input:IsKeyPressed(Vision.KEY_RIGHT) then self:IncPosition(1,0,0) end end
The key is the Input object, which is an instance of VScriptInput_wrapper, which as the name suggests, wraps input functionality and makes it available in Lua. Another thing of note is the Vision object ( well… table if I am going to use the proper terminology ) contains a number of constant defines, in this case we access the values for various key codes, such as Vision.KEY_LEFT. In the event of an arrow key being pressed, we move our entity calling the IncPosition() function, moving along either the X or Y axis, depending on which key is pressed.
The key thing to realize about the IsKeyPressed function is it will report true every time a key is hit. Therefore with the above code, if you hold down the key it will fire every time OnThink() is called. What do you do if you want to only respond when the key is pressed the first time? In that case you use SetKeyOnHit, like so:
function OnCreate(self) Input:SetKeyAsSingleHit(Vision.KEY_UP,true) end function OnThink(self) if Input:IsKeyPressed(Vision.KEY_UP) then self:IncPosition(0,100,0) end end
Using the above code, with the added call SetKeyAsSingleHit changes it so IsKeyPressed for KEY_UP will only return true when the UP arrow is initially pressed, and wont return true again until the key is released and pressed again. You can toggle this behaviour off by calling SetKeyAsSingleHit again but passing false instead.
One very important point!
When testing input code, it is very important that you run your game in “Play the Game” mode, otherwise vForge will still capture and handle all the input events. You run in Play The Game mode by clicking the down arrow next to the play icon and selecting Play The Game:
When you are done testing your game, hit the ESC key.
Handling Mouse Input
Now let’s quickly look at how you handle mouse events. I created a new scene in the same project, added the create model and once again attached a Lua script component to it. The Vision engine has the ability to poll the mouse for activity, which is exactly what we are about to demonstrate.
-- new script file function OnThink(self) local mouseX,mouseY = Input:GetMousePosition() local mouseDeltaX,mouseDeltaY = Input:GetMouseDelta() local mouseWheelDelta = Input:GetMouseWheelDelta() local isLeftButtonDown = Input:IsMouseButtonPressed(Vision.BUTTON_LEFT) local isRightButtonDown = Input:IsMouseButtonPressed(Vision.BUTTON_RIGHT) local outString = "Mouse Position:" .. mouseX .. "," .. mouseY .. " Mouse Delta:" .. mouseDeltaX .. "," .. mouseDeltaY .. " Mouse Wheel Delta:" .. mouseWheelDelta if isLeftButtonDown then outString = outString .. " Left Button Down" end if isRightButtonDown then outString = outString .. " Right Button Down" end Debug:PrintLine(outString) end
The code is quite straight forward. Each frame in OnThink() we poll the location of the mouse using GetMousePosition(), the amount the mouse has moved since the prior frame using GetMouseDelta(), the amount the mouse wheel has moved using GetMouseWheelDelta() and finally check to see if either mouse button is pressed using IsMouseButtonPressed(). Once again, the mouse button constants are defined in Vision.
If you run this code you will see:
Once again, like the keyboard demonstration, you need to run in Play The Game mode for Engine View to receive the input.
Creating Input Maps
Now that we’ve covered how to directly poll the mouse and keyboard for input, we will now look at handling input using a InputMap instead. Using an input map allows you to put a layer of abstraction between you and the input devices, allowing you to map multiple control schemes to a single action. Let’s jump right in and take a look. I am once again creating a new scene, adding the crate model and applying a script to it.
-- Inputmap demo function OnAfterSceneLoaded(self) self.map = Input:CreateMap("crateInputMap") self.map:MapTrigger("Up", "KEYBOARD", "CT_KB_SPACE") self.map:MapTrigger("Up", "MOUSE", "CT_MOUSE_RIGHT_BUTTON") self.map:MapTrigger("Up", {0,0,100,100}, "CT_TOUCH_ANY", {once=true}) -- arrow keys mapping for left/right self.map:MapTriggerAxis("MoveX", "KEYBOARD", "CT_KB_LEFT", "CT_KB_RIGHT") -- WASD mapping for left/right self.map:MapTriggerAxis("MoveX", "KEYBOARD", "CT_KB_A", "CT_KB_D") -- gamepad mapping for left/right self.map:MapTriggerAxis("MoveX", "PAD1", "CT_PAD_RIGHT_THUMB_STICK_LEFT", "CT_PAD_RIGHT_THUMB_STICK_RIGHT") end function OnThink(self) if self.map:GetTrigger("Up") == 1 then self:IncPosition(0,0,10) end if self.map:GetTrigger("MoveX") < 0 then self:IncPosition(10,0,0) elseif self.map:GetTrigger("MoveX") > 0 then self:IncPosition(-10,0,0) end end
This time we are creating a map to handle input using the function Input:CreateMap(). If you create another map using the same name, the previous map will be destroyed. Once we have a map, it’s a matter of defining triggers. Essentially this means we are giving a string key and mapping it to an input event. For example MapTrigger(“Up”,”KEYBOARD”,”CT_KB_SPACE”) is saying when the spacebar is pressed on the keyboard, trigger the trigger named “Up”. As you can see, you can map multiple devices/keys to the same trigger. In this example, we mapped the SpaceBar, Right Mouse Button and touching the screen in between the coordinates (0,0) and (100,100) all to the trigger “Up”. So now instead of handling all the different control schemes in code, we can simple deal with “Up”. If you look at the CT_TOUCH_ANY trigger ( which we will cover in more detail later ), you can pass in a table of optional parameters in as well, in this case we are saying we want the event to only fire once per touch.
Sometimes however you want to bind an axis to an event, such as pressing left or right arrow keys, or using the thumbstick on a gamepad. In that case you can use MapTriggerAxis(). In this case you pass in the name of the trigger, the device to use, then the values representing the negative and positive ranges of the axis. In the first example, we are mapping the LEFT and RIGHT arrow keys to the X axis, with CT_KB_LEFT representing the negative value and CT_KB_RIGHT representing the positive value. We also bound the A and D keys from a traditional WASD layout to the X axis, as well as the right thumbstick on a gamepad, if present. The code will warn you that a given device isn’t present, but will run anyway.
Now in on think you can see handling triggers is extremely simple, you simply call your map’s GetTrigger method, passing in the name of the trigger you created. Therefore if any of the triggers we defined are true ( RMB pressed, Spacebar hit or certain region of the screen is touched ), it will return 1 get you check the “Up” trigger. Dealing with axis is virtually identical, except it returns a value from –1 to 1, depending on direction pressed. Using a map allows you to easily handle multiple control schemes using a single code base.
In this tutorial, we looked mostly at traditional desktop controls… not really much use in mobile development. In the next part however, we are going to look at work with mobile input.