Vehicle Input Controller Module

Heya fellow developers!
I was just updating my Input module for my car game and thought someone else might find it useful, so here you go!

local m = {}		-- functions
local inputs = {}	-- input states
local controls = {}	-- keybinds

inputs.Throttle = 0; 				-- User input determining the power sent to the wheels
inputs.Steer = 0;					-- User input determining the angle at which the wheels should be turned.
inputs.Clutch = 0;					-- A mediator between the engine/throttle and the wheels
inputs.Handbrake = 0;				 -- State of the handbrake which stops the rear wheels from turnning - 0 = released, 1 = engaged.
inputs.Brake = 0;
inputs.GearUp = 0;
inputs.GearDown = 0;

controls.Throttle = {
controls.Brake = {
	Enum.KeyCode.S, Enum.KeyCode.Down, Enum.KeyCode.ButtonL2,Enum.UserInputType.Touch
controls.SteerLeft = {
	Enum.KeyCode.A, Enum.KeyCode.Left, Enum.KeyCode.Thumbstick1,Enum.UserInputType.Touch
controls.SteerRight = {
	Enum.KeyCode.D, Enum.KeyCode.Right, Enum.KeyCode.Thumbstick1,Enum.UserInputType.Touch
controls.Handbrake = {
	Enum.KeyCode.Space, Enum.KeyCode.ButtonA
controls.Clutch = {
	Enum.KeyCode.LeftControl,Enum.KeyCode.LeftShift,Enum.KeyCode.RightControl, Enum.KeyCode.ButtonL1
controls.GearUp = {
	Enum.KeyCode.R, Enum.KeyCode.E, Enum.KeyCode.ButtonB
controls.GearDown = {
	Enum.KeyCode.F,Enum.KeyCode.Q, Enum.KeyCode.ButtonX
for ci,control in pairs(controls) do -- converting enums to have readable indexes.
	local new = {}
	for i,keycode in pairs(control) do
		local index = keycode.Value
		new[index] = index
	controls[ci] = new

function m.GetControls() -- return the list of user input controls
	return controls
function m.GetInputs() -- return the state of user inputs
	return inputs

function m.GetInput(io,gpe)	-- inputObject, gameProcessedEvent

	local state = io.UserInputState.Name -- setting enums to strings with '.Name' is much easier
	local typ = io.UserInputType.Name
	local pos = io.Position    --pos shows the current position of an input (e,g, mouse.X/Y)
	local delta = io.Delta      -- delta shows how much has CHANGED from last input
	local key = io.KeyCode.Name    
	local code = io.KeyCode.Value   -- gets the KeyCode number value
	local gamepad = typ:match("Gamepad")  -- is it a gamepad?

	pos =
		pos.X * math.abs(pos.X),   -- account for deadzone from thumbstick input.
		pos.Y * math.abs(pos.Y),
		pos.Z * math.abs(pos.Z)
		--pos.Magnitude <= 0.125 and or pos -- fixed deadzone variant

	if gpe and not key == "Thumbstick1" then return end -- thumbstick is a GPE, who wouldn't known?...

--	print("State",state)
--	print("IOType",typ)
--	print("POS:",pos)
--	print("DELTA:",delta)
--	print("KEY:",key)
--	print(controls.Throttle[key])
--	print("Gamepad:",gamepad)

	if controls.Throttle[code] then -- Throttle
		if key:match("Button") then
			inputs.Throttle = pos.Z
			inputs.Throttle = state == "Begin" and 1 or 0
--		print("Throttle",inputs.Throttle)

	if controls.Brake[code] then -- Brake
		if key:match("Button") then
			inputs.Brake = pos.Z
			inputs.Brake = state == "Begin" and 1 or 0
--		print("Brake:",inputs.Brake)

	if controls.SteerLeft[code] or controls.SteerRight[code] then
		if key:match("Thumbstick1") then
			inputs.Steer = pos.X * (state == "End" and 0 or 1)
			inputs.Steer = 1 * (controls.SteerLeft[code] and -1 or 1) * (state == "End" and 0 or 1)
--		print("Steer",inputs.Steer,pos.X)

	if state == "Begin" then
		if controls.GearUp[code] then
			inputs.GearUp = 1
--			print("Gear Up",inputs.GearUp)
		if controls.GearDown[code] then
			inputs.GearDown = 1
--			print("Gear Down",inputs.GearDown)
	if controls.Clutch[code] then
		inputs.Clutch = state == "Begin" and 1 or 0
--		print("Clutch",inputs.Clutch)
	if controls.Handbrake[code] then
		inputs.Handbrake = state == "Begin" and 1 or 0
--		print("Clutch",inputs.Clutch)

return m

Setting up the module

To make use of the module, require it via a LocalScript, then use the UserInputService to fire the module whenever a user input is triggered:

local controlsModule = require(script:WaitForChild("ControlsModule"))
local UserInputService = game:GetService("UserInputService")


Using the module

You can easily add, remove or change the input requirements in the ‘controls’ table.
For example, if you wanted to engage Left Steering when you press the ‘J’ button;

controls.SteerLeft = {

You could even manipulate this from an external script, if you wanted to include custom controls.
(however you would probably need to do some work-around for gamepad input).

All inputs return a value between 0 (disengaged) and 1 (engaged) except for Steering, which is a value between -1 and 1.

On line 65 for the module I have included a damping (is that what you call it?) so as to negate dead zone in Gamepad thumbsticks for steering. However I left the damping for all gamepad input as I felt it made a nicer experience for gamepad input.

To get information from it, call the ‘GetInputs’ function to return a list of the inputs.

local input = controlsModule.GetInputs() -- returns the list of 'inputs' and their states

local throttle = input.Throttle -- returns the input for the 'Throttle'
local steer = input.Steer -- returns the input for the 'Steering'

Leave me feedback!

My previous attempt at making a controller system was… horrible, to say the least, however I’m really curious how you find using this, whether it is useful, and if I can make any improvements!
So let me know down below!

Hope its useful to you. =)


Hello fellow devs! Have you had the chance to use this module?

If so, please leave me some feedback on any issues you had, and what can be improved!

Thanks! :upside_down_face:


I will definitely be trying this tomorrow I will give all the feedback I can

1 Like

do you know how i can make it for a train? i need it to change a value and when you hold W the value is going up but when you release it it stops, do you know how?

Can’t expect any better feedback like, an amazing job! Your skills are incredible! Keep it up!

1 Like

Could you explain what exactly I would fire to the module?

As mentioned in the section - “Using the module”, you just need to call GetInputs, which returns the inputs table that has the current state of Throttle, Steer, Clutch etc.

local input = controlsModule.GetInputs() -- returns the list of 'inputs' and their states

As long as you have connected the UserInputService’s Events, Begin, Ended and Changed to the GetInput (different than GetInputs - gosh this is a bad naming convention :scream:) you should get different values when getting the input.

To be quite honest, looking back at this system it is very convoluted, but I still think its an interesting way of using modules. :slight_smile:

Have fun.

I just saw this pop up since I’d read it before and there was a recent comment.
Since you originally posted I’ve been working on a construction site game with vehicles that use VehicleSeat inputs as well as UserInputService.InputChanged for other control inputs but it had issues with holding down 2 or more keys at the same time, then changing one of the controls and it cancelling out one of the inputs.
Going to give this a try, looks like it’ll fix my issue!

1 Like

So a few questions:
Where does the first script go and is it a local or server script?
And where does the 2nd script (the LocalScript) go?
The second script refers to ControlsModule in line 1, is that what the name of the first script should be?