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 = {
	Enum.KeyCode.W,Enum.KeyCode.Up,Enum.KeyCode.ButtonR2,Enum.UserInputType.Touch
}
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
	end
	controls[ci] = new
end


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

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 = Vector3.new(
		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 Vector3.new() or pos -- fixed deadzone variant


	if gpe and not key == "Thumbstick1" then return end -- thumbstick is a GPE, who wouldn't known?...
	--print(state,typ,delta,pos,code,touch)

--	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
		else
			inputs.Throttle = state == "Begin" and 1 or 0
		end
--		print("Throttle",inputs.Throttle)
	end

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

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

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

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")

UserInputService.InputChanged:Connect(controlsModule.GetInput)
UserInputService.InputBegan:Connect(controlsModule.GetInput)
UserInputService.InputEnded:Connect(controlsModule.GetInput)

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 = {
	Enum.KeyCode.J
}

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. =)

57 Likes

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:

5 Likes

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?