Project OutFox Wiki
Project OutFox home View the OutFox Wiki source on GitHub Toggle Dark/Light/Auto modeToggle Dark/Light/Auto modeToggle Dark/Light/Auto mode Back to homepage

Custom Input

OutFox allows custom input to be managed by the theme itself. This allows you full sandboxed control of the game for the purposes of the theme.


Implementing Input

There are a few ways to implement input, but the general action is to call Screen:AddInputCallback( input function ) to the ActorFrame responsible for the input. This can be either a function or a module that will deal with listening and sending instructions back to the engine, that actors can then pick up to provide feedback.

Module method (OutFox Alpha 4 and onwards)

-- In the actorframe, we just call the module to handle the input.
return Def.ActorFrame{
	OnCommand = function(self)
		-- The module needs the actor handle itself to send commands back to it, so we give `self` as the
		-- argument.
		SCREENMAN:GetTopScreen():AddInputCallback( LoadModule("Lua.InputSystem.lua")(self) )
	end,
	-- With this, the actorframe is now responsible for handling input and sending it to others.
	StartCommand = function(self)
		SCREENMAN:SystemMessage("I have pressed START!")
	end
}

More direct method (Works on legacy versions of SM5)

-- This function will deal with a simple input set.
-- Let's make the input send a message when we press the player's assignated Start button.
-- event is sent over from InputCallback, and contains all the information needed to obtain input.
local function myCustomInput(event)
	-- First, let's verify that the input is being performed when pressing.
	-- There are three modes of input, which will be explain later in this chapter.
	if event.type == "InputEventType_FirstPress" then
		-- Ok, we have pressed the button once. Time to detect what button was pressed.
		if event.GameButton == "Start" then
			SCREENMAN:SystemMessage("I have pressed START!")
		end
	end
end

-- On this actorframe, we'll load the function.
return Def.ActorFrame{
	OnCommand = function(self)
		-- In this case, the function itself is now responsible of handling input and sending results to others.
		SCREENMAN:GetTopScreen():AddInputCallback( myCustomInput )
	end
}

Anatomy of the event variable

When recieving input, you will be given a table by the name of event. It gives you base elements, which contain all the information you need to determine what the user has performed to then report back to the theme.

NameReturnsDescription
controllerstringThe GameController the event was mapped to, which will depend on what controller was the input mapped to. Will return nil if the button is not mapped to any controller.
buttonstringThe semi-raw button that was pressed. This is what the button was mapped to by the keymap settings, but without the conversions that occur when OnlyDedicatedMenuButtons is true. Will be empty if the button was not mapped.
typestringThe type of event. For more information, go to Understanding Press Events.
GameButtonstringThe cooked button that was pressed. This is applied with mapping that occurs when OnlyDedicatedMenuButtons is true applied. This is nil for unmapped buttons.
PlayerNumberPlayerNumberThe player that the input is mapped to. Can be nil if its not on either player.
MultiPlayerMultiPlayerA MultiPlayer enumerator that is mapped to the input, can be used on MultiPlayer matches, can be nil if the input is not mapped to any MultiPlayer. Do not confuse this to PlayerNumber.

The Device Input Table

Inside this table, is another table called DeviceInput (can be accessed from event.DeviceInput), which are the raw details on the InputEvent the player has performed, which give the device the event has been performed on, the button that was pressed, if its still pressed, how long ago was the button pressed, a Z level counter for mouse input devices, as well as checks for joystick and mouse.

NameReturnsDescription
devicestringType of the device, which will start with "Device_", and then followed with a InputDeviceNames entry.
buttonstringThe button that was pressed, which will start with "DeviceButton_", and then followed with the key correspondant on the device.
levelfloatA floating point value for analog input.
zfloatA floating point value for determining what level is the mousewheel at.
downboolDetermines if the button is down. This is a combination of level with a threshold. and debouncing applied.
agofloatHow long ago this input occurred, in seconds.
is_joystickboolChecks if the device is a joystick.
is_mouseboolChecks if the device is a mouse.

Understanding Press Events

When calling an input, you have event.type, which is the moment where the button has been pressed, and contains three states:

graph TB
    A["InputEventType_FirstPress
(When pressing the button for the first time)"]:::Transition --> B["InputEventType_Repeat
(When holding the button)"]:::Transition --> C["InputEventType_Release
(When lifting the button from being pressed)"]:::Transition;
The repeat rate for InputEventType_Repeat is dependant on the current screen’s RepeatRate metric, which determines how fast the input will be repeated every second.

In order to filter what kind of input you want for specific actions, just include them in a if conditional check.

-- Let's say I want my start press to increase a variable and report it,
-- but also increase it while holding the button, but not when lifting it.

-- Initialize the variable to use as the counter.
local count = 0

local function myCustomInput(event)
	-- In this case, we'll check that the input is NOT Release, because that's the only InputEventType
	-- that we don't want verified on this check.
	if event.type ~= "InputEventType_Release" then
		-- Ok, we have pressed the button. time to count and report!
		if event.GameButton == "Start" then
			count = count + 1
			SCREENMAN:SystemMessage("The counter is now ".. count)
		end
	end
end

-- Now let's load the function to the actorframe.
return Def.ActorFrame{
	OnCommand = function(self)
		SCREENMAN:GetTopScreen():AddInputCallback( myCustomInput )
	end
}

Mouse Input

Mouse input is supported on OutFox and earlier releases of SM5 via a MessageCommand system that determines which button was pressed to perform actions.

Message NameSegment of Mouse
MouseLeftClickLeft Mouse Click
MouseRightClickRight Mouse Click
MouseMiddleClickMiddle Mouse Click
MouseThumb1Additional Mouse Click 1
MouseThumb2Additional Mouse Click 2
MouseWheelUpMouse Wheel Up
MouseWheelDownMouse Wheel Down
-- Example script to show a message when the left click of the mouse is pressed.
MouseLeftClickMessageCommand = function(self)
	SCREENMAN:SystemMessage("This is the left mouse click!")
end
Starting on OutFox Alpha 4.9.7GG, these MessageCommands now contain an argument to determine if the mouse click was lifted or not.
-- Example script to show a message when the left click of the mouse is pressed.
MouseLeftClickMessageCommand = function(self,param)
	if param.IsPressed then
		SCREENMAN:SystemMessage("This is the left mouse click being pressed!")
	else
		SCREENMAN:SystemMessage("This is the left mouse click being lifted!")
	end
end

General Recommendations

Remove the callback when leaving the screen

Upon adding the callback into the screen, it will be stored in memory for input to be processed, but won’t be cleared when leaving, which can cause input problems on the long run in the session.

-- Using the manual function method
OffCommand = function(self)
	-- To remove it, just call the same function that was used to control the screen.
	SCREENMAN:GetTopScreen():RemoveInputCallback( myCustomInput )
end
-- Using the InputSystem module
-- Before anything, set the function inside the actor itself.
OnCommand = function(self)
	self.callback = LoadModule("Lua.InputSystem.lua")(self)
	SCREENMAN:GetTopScreen():AddInputCallback( self.callback )
end,
-- And now, when leaving, it will have the right actor to remove.
OffCommmand = function(self)
	SCREENMAN:GetTopScreen():RemoveInputCallback( self.callback )
end