A tutorial series about Moai basics. In this tutorial, we’ll talk about a few ways to capture user input, specifically pointer location and mouse clicking. We’ll do a few simple scripts and investigate two separate methods to get that input. Talk about the tutorial here! First, a note; input in Moai is determined by the host. If you look at the GlutHost.cpp file in the Moai source, down around line 240, it lists the input config.

AKUSetInputConfigurationName ( "AKUGlut" );

	AKUReserveInputDevices			( GlutInputDeviceID::TOTAL );
	AKUSetInputDevice				( GlutInputDeviceID::DEVICE, "device" );
	
	AKUReserveInputDeviceSensors	( GlutInputDeviceID::DEVICE, GlutInputDeviceSensorID::TOTAL );
	AKUSetInputDeviceKeyboard		( GlutInputDeviceID::DEVICE, GlutInputDeviceSensorID::KEYBOARD,		"keyboard" );
	AKUSetInputDevicePointer		( GlutInputDeviceID::DEVICE, GlutInputDeviceSensorID::POINTER,		"pointer" );
	AKUSetInputDeviceButton			( GlutInputDeviceID::DEVICE, GlutInputDeviceSensorID::MOUSE_LEFT,	"mouseLeft" );
	AKUSetInputDeviceButton			( GlutInputDeviceID::DEVICE, GlutInputDeviceSensorID::MOUSE_MIDDLE,	"mouseMiddle" );
	AKUSetInputDeviceButton			( GlutInputDeviceID::DEVICE, GlutInputDeviceSensorID::MOUSE_RIGHT,	"mouseRight" );

This set of code creates a Lua table called “device”, and inserts the various inputs into that table so it can be called when we are ready to call them. If a user were so inclined, they could create their own input devices/buttons. Now we can get into the meat of this input tutorial.

Input with Callbacks

For the first portion of the tutorial we will be using callbacks. All of the examples in this tutorial will require an open window that we will move our pointer over and click on. After we create our window, it’s time to start our input grabbing.

MOAISim.openWindow ( "Inputs", 320, 480 ) 

function onPointerEvent ( x, y )

	print ( "pointer:", x, y )
end
MOAIInputMgr.device.pointer:setCallback ( onPointerEvent ) 

We first create a function that will take in two variables ( our x and y ), and then print them. Then we set up our input to callback to that function. Run the program and roll your mouse pointer over the open window. You may notice that the coordinates that are being printed are taken from a "window style" coordinate system, where the top left corner of the window is 0,0. Later, we will change this to a "world style" coordinate system, where the center is 0,0. Now we’re going to do the same thing for our left mouse button, but instead of just the one action from the pointer (location), we will have two separate actions; the pressing of the button, and the release of the button (there is a third action, “isDown”, which is activated for as long as the button is pressed. We will use this later in the tutorial.) Delete the previous code (except for opening the window) and without further ado:

MOAISim.openWindow ( "Inputs", 320, 480 ) 

function onMouseLeftEvent ( down )

	if down == true then
		print ( "mouse left pressed" )
	else
		print ( "mouse left released" )
	end
end
MOAIInputMgr.device.mouseLeft:setCallback ( onMouseLeftEvent ) 

Just like the pointer, except now we have two actions. If the mouse left button has been pressed down, then we will print “mouse left pressed”, and if it is not down, we print “mouse left released.” Run this program and click on the window a few times. Now that we have gone over a simple way to capture and print some input, we’ll change it up a bit so that our input can be seen in a textbox on the actual window. We will need to create a viewport, layer, font, and textbox, set that textbox’s location as the pointer’s location (converted to world space), and then set the textbox string as our pointer’s location.

MOAISim.openWindow ( "Inputs", 320, 480 )

viewport = MOAIViewport.new ()
viewport:setScale ( 320, 480 )
viewport:setSize ( 320, 480 )

layer = MOAILayer2D.new ()
layer:setViewport ( viewport )
MOAISim.pushRenderPass ( layer )

charcodes = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 .,:;!?()&/-'
font = MOAIFont.new ()
font:loadFromTTF ( 'arialbd.ttf', charcodes, 16, 163 )

textbox = MOAITextBox.new ()
textbox:setFont ( font )
textbox:setTextSize ( font:getScale ())
textbox:setRect ( -50, -50, 50, 50 )
textbox:setYFlip ( true )

function onPointerEvent ( x, y )

	mouseX, mouseY = layer:wndToWorld ( x, y )
	textbox:setLoc ( mouseX, mouseY )
	textbox:setString ( tostring ( "X" .. mouseX  ..  " " .. "Y" .. mouseY ) )
	layer:insertProp ( textbox )
end
MOAIInputMgr.device.pointer:setCallback ( onPointerEvent )

If you’ve read the previous tutorials, this will all look familiar. Now, running this program, we'll see the textbox follow our pointer across the window.

And finally, we’ll combine this with our Mouse Left event from earlier.

function onPointerEvent ( x, y )

	mouseX, mouseY = layer:wndToWorld ( x, y )
	textbox:setLoc ( mouseX, mouseY )
    textbox:setString ( tostring ( "X" .. mouseX  ..  " " .. "Y" .. mouseY ) )
end

function onMouseLeftEvent ( down )
	
	if down == true then
		print "Press!"
		layer:insertProp ( textbox )
	else
		layer:removeProp ( textbox )
	end
end
MOAIInputMgr.device.pointer:setCallback ( onPointerEvent )
MOAIInputMgr.device.mouseLeft:setCallback ( onMouseLeftEvent ) 

This screenshot looks an awful lot like the previous one, but now the textbox only appears when the button is pressed, and after the button is let go, it disappears.

Input Polling with Threads

Now that we’ve got our inputs figured out with the use of callbacks, we’ll investigate another way to use inputs. This time we’re going to constantly check if it’s been pressed, and if it is then we will do our actions. To do this, we will need a thread running to cycle through the checks.

MOAISim.openWindow ( "Inputs", 320, 480 )
mainThread = MOAIThread.new ()
mainThread:run (
	function ()

		local run = true
		while run do
			coroutine.yield ()		

			if MOAIInputMgr.device.mouseLeft:down () then
				print "pressed!"
			end

		end
	end
) 

This creates a thread, and while run is true, it will check to see if the mouseLeft has been pressed. If it has, then it will then print out the message.

To bring this up to speed with the other example, it’s a fairly similar process with one exception:

MOAISim.openWindow ( "Inputs", 320, 480 )

viewport = MOAIViewport.new ()
viewport:setScale ( 320, 480 )
viewport:setSize ( 320, 480 )

layer = MOAILayer2D.new ()
layer:setViewport ( viewport )
MOAISim.pushRenderPass ( layer )

charcodes = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 .,:;!?()&/-'
font = MOAIFont.new ()
font:loadFromTTF ( 'arialbd.ttf', charcodes, 16, 163 )

textbox = MOAITextBox.new ()
textbox:setFont ( font )
textbox:setTextSize ( font:getScale ())
textbox:setRect ( -50, -50, 50, 50 )
textbox:setYFlip ( true )

mainThread = MOAIThread.new ()
mainThread:run (

	function ()
	
		local run = true
		local mouseX, mouseY = 0, 0
		while run do
		
			coroutine.yield ()
		
			if MOAIInputMgr.device.pointer then
				mouseX, mouseY = layer:wndToWorld ( MOAIInputMgr.device.pointer:getLoc () )
				textbox:setLoc ( mouseX, mouseY )
			end
			if MOAIInputMgr.device.mouseLeft:isDown () then
				print "pressed!"
				textbox:setString ( tostring ( "X" .. mouseX  ..  " " .. "Y" .. mouseY ) )
				layer:insertProp ( textbox )
			 else
				layer:removeProp ( textbox )
			end
		end
	end
) 

You will notice that on our mouseLeft check, we are checking for isDown and not down. The reason for this is that if we checked for “down”, it would only activate the moment the button was pressed, which only lasts for one frame. For us to see our textbox more than for a fraction of a second, we have to check for when the button is currently being pressed down.

Check back later for a tutorial on multi-touch for mobile devices that will expand upon what we have done in this tutorial.