Skip to content

Instantly share code, notes, and snippets.

@BrandonForbrad
Last active December 16, 2023 18:17
Show Gist options
  • Save BrandonForbrad/def145706fb63b3a4b5d86cddad95a60 to your computer and use it in GitHub Desktop.
Save BrandonForbrad/def145706fb63b3a4b5d86cddad95a60 to your computer and use it in GitHub Desktop.
Roblox Lua Input/Device module. This script makes creating inputs for all devices easier and communicating them. (Works with ECS or Event Based/OOP)
--Inputs module
--Created by forbrad 12/02/2023
--v1.0.0.1
local Inputs = {}
---INPUT BINDS----------------------------------------
--KBM | Keyboard and mouse (PC)
--TouchScreen | Any touch screen device (Mobile)
--Controller | Anygame controller (Console)
--For UiButton the second arguement will be the name of the tag within CollectionService (Set the tag to the relative buttons)
Inputs.Binds = {
["PlaceBlock"] = { KBM = {"UserInputType", "MouseButton1"}, TouchScreen = {"UserInputType", "Touch"}, Controller = {"KeyCode", "ButtonR1"}},
["PrimaryAttack"] = { KBM = {"UserInputType", "MouseButton1"}, TouchScreen = {"UserInputType", "Touch"}, Controller = {"KeyCode", "ButtonR1"}},
["ToggleInventory"] = { KBM = {"KeyCode", "E"}, TouchScreen = {"UiButton", "ToggleInventoryButton"}, Controller = {"KeyCode", "ButtonX"}, AllDevices = {"UiButton", "ToggleInventoryButton" }},
["HotbarSlot1"] = { KBM = {"KeyCode", "One"}, TouchScreen = {"UiButton", "HotbarSlot1Button"}, Controller = {"UiButton", "HotbarSlot1Button"}, AllDevices = {"UiButton", "HotbarSlot1Button" } },
["HotbarSlot2"] = { KBM = {"KeyCode", "Two"}, TouchScreen = {"UiButton", "HotbarSlot2Button"}, Controller = {"UiButton", "HotbarSlot2Button"}, AllDevices = {"UiButton", "HotbarSlot2Button" }},
["HotbarSlot3"] = { KBM = {"KeyCode", "Three"}, TouchScreen = {"UiButton", "HotbarSlot3Button"}, Controller = {"UiButton", "HotbarSlot3Button"}, AllDevices = {"UiButton", "HotbarSlot3Button" } },
["HotbarSlot4"] = { KBM = {"KeyCode", "Four"}, TouchScreen = {"UiButton", "HotbarSlot4Button"}, Controller = {"UiButton", "HotbarSlot4Button"}, AllDevices = {"UiButton", "HotbarSlot4Button" } },
["HotbarSlot5"] = { KBM = {"KeyCode", "Five"}, TouchScreen = {"UiButton", "HotbarSlot5Button"}, Controller = {"UiButton", "HotbarSlot5Button"}, AllDevices = {"UiButton", "HotbarSlot5Button" } },
["NextHotbarSlot"] = { KBM = {"KeyCode", "Q"}, TouchScreen = {"UiButton", "NextHotbarSlotButton"}, Controller = {"KeyCode", "ButtonR2"}, AllDevices = {"UiButton", "NextHotbarSlotButton" } },
}
local AllowDeviceSwitching = true -- ALLOW PLAYERS TO SWITCH DEVICES
local DeviceSwitchBinds = { -- BINDS TO SWITCH DEVICE
KBM = {"UserInputType", "MouseButton1"},
TouchScreen = {"UserInputType", "Touch"},
Controller = {"KeyCode", "ButtonR1"},
}
local Devices = {
KBM = "PC",
TouchScreen = "Mobile",
Controller = "Console"
}
--Inputs.Began
--Inputs.Ended
--Inputs.GetDevice
--Inputs.GetAllDevices
--Inputs.OnNewDevice
----------------------------------------------------------
---CORE FUNCTIONALITY
Inputs.PlayerDevices = {}
Inputs.LocalDevice = "KBM"
local CollectionService = game:GetService("CollectionService")
local RunService = game:GetService("RunService")
local UIS = game:GetService("UserInputService")
local GuiService = game:GetService("GuiService")
local Signal : {
InputBegan: RemoteEvent,
InputEnded: RemoteEvent,
SetDevice: RemoteEvent
} = {}
local SetDeviceBind: BindableEvent
local function SetLocalDevice(DeviceName)
Inputs.LocalDevice = DeviceName
Inputs.PlayerDevices[game.Players.LocalPlayer] = DeviceName
Signal.SetDevice:FireServer(DeviceName)
SetDeviceBind:Fire(DeviceName)
end
setmetatable(Signal, {__index = function(_self, index)
return game.ReplicatedStorage:FindFirstChild(index)
end})
function Signal.new(Name: "SignalName") : RemoteEvent
local Remote = Instance.new("RemoteEvent")
Remote.Name = Name
Remote.Parent = game.ReplicatedStorage
Signal[Name] = Remote
return Remote
end
local IsServer = RunService:IsServer()
function Inputs:Init() : "Starts and Initilizes the input system"
SetDeviceBind = Instance.new("BindableEvent")
SetDeviceBind.Parent = script
SetDeviceBind.Name = "SetDeviceBind"
if IsServer then
--Server
Signal.new("InputBegan")
Signal.new("InputEnded")
Signal.new("SetDevice")
Signal.SetDevice.OnServerEvent:Connect(function(player, Device)
if Devices[Device] == nil then return end
Inputs.PlayerDevices[player] = Device
SetDeviceBind:Fire(player, Device)
end)
else
--Client
if UIS.TouchEnabled then
SetLocalDevice("TouchScreen")
elseif GuiService:IsTenFootInterface() then
SetLocalDevice("Controller")
else
SetLocalDevice("KBM")
end
if AllowDeviceSwitching then
UIS.InputBegan:Connect(function(input, gameProcessedEvent)
if gameProcessedEvent then return end
for DeviceName, Binds in next, DeviceSwitchBinds do
if DeviceName == Inputs.LocalDevice then continue end
if input[Binds[1]] == Enum[Binds[1]][Binds[2]] then
SetLocalDevice(DeviceName)
break
end
end
end)
end
end
end
Inputs.Inited= false
if not Inputs.Inited then
Inputs.Inited = true
Inputs:Init()
end
function Inputs.GetDevice(player: Player): string
return Inputs.PlayerDevices[player]
end
function Inputs.GetAllDevices(): {[Player]: "device string"}
return Inputs.PlayerDevices
end
function Inputs.OnNewDevice(callback): RBXScriptConnection
return SetDeviceBind.Event:Connect(callback)
end
function Inputs.SendInputBegan(InputName, ...)
Signal.InputBegan:FireServer(InputName, ...)
end
function Inputs.SendInputEnded(InputName, ...)
Signal.InputEnded:FireServer(InputName, ...)
end
Inputs.HoldingInputs = {}
if IsServer then
for bindname, _ in next, Inputs.Binds do
Inputs.HoldingInputs[bindname] = {}
end
else
for bindname, _ in next, Inputs.Binds do
Inputs.HoldingInputs[bindname] = false
end
end
function Inputs.isHolding(InputName, player):boolean
if IsServer then
if Inputs.HoldingInputs[InputName] == nil then
Inputs.HoldingInputs[InputName] = {}
Inputs.HoldingInputs[InputName][player] = false
return false
end
if Inputs.HoldingInputs[InputName][player] == nil then
return false
end
return Inputs.HoldingInputs[InputName][player]
else
if Inputs.HoldingInputs[InputName] == nil then
return false
end
return Inputs.HoldingInputs[InputName]
end
end
local LastPressButtonName: string = {}
function Inputs.Began(InputName: string, callback): RBXScriptConnection
if IsServer then
--Server
return Signal.InputBegan.OnServerEvent:Connect(function(player, input, ...)
if input == InputName then
Inputs.HoldingInputs[InputName][player] = true
callback(player, ...)
end
end)
else
--Client
local function ThrowCallback()
Inputs.HoldingInputs[InputName]= true
local ArgsToSend = {callback()}
if ArgsToSend[1] ~= true then return end
table.remove(ArgsToSend, 1)
Signal.InputBegan:FireServer(InputName, unpack(ArgsToSend))
end
return UIS.InputBegan:Connect(function(input, gameProcessedEvent)
local CurrBinds = Inputs.Binds[InputName][Inputs.LocalDevice]
if CurrBinds[1] == "UiButton" then
if input.UserInputType == Enum.UserInputType.MouseButton1 or input.UserInputType == Enum.UserInputType.Touch then
for _, Button: GuiButton in CollectionService:GetTagged(CurrBinds[2]) do
if Button.GuiState ~= Enum.GuiState.Press then continue end
table.insert(LastPressButtonName, InputName)
ThrowCallback()
break
end
end
return
end
if input[CurrBinds[1]] == Enum[CurrBinds[1]][CurrBinds[2]] and not gameProcessedEvent then
ThrowCallback()
end
if Inputs.Binds[InputName].AllDevices == nil then return end
CurrBinds = Inputs.Binds[InputName].AllDevices
if CurrBinds[1] == "UiButton" then
if input.UserInputType == Enum.UserInputType.MouseButton1 or input.UserInputType == Enum.UserInputType.Touch then
for _, Button: GuiButton in CollectionService:GetTagged(CurrBinds[2]) do
if Button.GuiState ~= Enum.GuiState.Press then continue end
table.insert(LastPressButtonName, InputName)
ThrowCallback()
break
end
end
return
end
if input[CurrBinds[1]] == Enum[CurrBinds[1]][CurrBinds[2]] and not gameProcessedEvent then
ThrowCallback()
end
end)
end
end
function Inputs.Ended(InputName , callback) : RBXScriptConnection
if IsServer then
--Server
return Signal.InputEnded.OnServerEvent:Connect(function(player, input, ...)
if input == InputName then
Inputs.HoldingInputs[InputName][player] = false
callback(player, ...)
end
end)
else
--Client
local function ThrowCallback()
Inputs.HoldingInputs[InputName] = false
local ArgsToSend = {callback()}
if ArgsToSend[1] ~= true then return end
table.remove(ArgsToSend, 1)
Signal.InputEnded:FireServer(InputName, unpack(ArgsToSend))
end
return UIS.InputEnded:Connect(function(input)
local CurrBinds = Inputs.Binds[InputName][Inputs.LocalDevice]
local function Check()
if CurrBinds[1] == "UiButton" then
if input.UserInputType == Enum.UserInputType.MouseButton1 or input.UserInputType == Enum.UserInputType.Touch then
local fbn = table.find(LastPressButtonName, InputName)
if fbn ~= nil then
table.remove(LastPressButtonName, fbn)
ThrowCallback()
end
end
return
end
if input[CurrBinds[1]] == Enum[CurrBinds[1]][CurrBinds[2]] then
ThrowCallback()
end
end
task.spawn(Check)
CurrBinds = Inputs.Binds[InputName].AllDevices
if CurrBinds == nil then return end
task.spawn(Check)
end)
end
end
--for ECS systems
Inputs.NewInputList = {}
function Inputs.onInputBegan(InputName)
if Inputs.NewInputList[InputName.."Began"] == nil then
return {}
end
return Inputs.NewInputList[InputName.."Began"]
end
function Inputs.onInputEnded(InputName)
if Inputs.NewInputList[InputName.."Ended"] == nil then
return {}
end
return Inputs.NewInputList[InputName.."Ended"]
end
Inputs.IndexedECS = false
function Inputs.nextstep()
if not Inputs.IndexedECS then
Inputs.IndexedECS = true
for bindname, _ in next, Inputs.Binds do
Inputs.NewInputList[bindname.."Began"] = {}
Inputs.Began(bindname, function(...)
table.insert(Inputs.NewInputList[bindname.."Began"], {
bindname = bindname,
SendToServer = function(...)
Inputs.SendInputBegan(bindname, ...)
end,
args = {...}
})
end)
Inputs.NewInputList[bindname.."Ended"] = {}
Inputs.Ended(bindname, function(...)
table.insert(Inputs.NewInputList[bindname.."Ended"], {
bindname = bindname,
SendToServer = function(...)
Inputs.SendInputEnded(bindname, ...)
end,
args = {...}
})
end)
end
end
for bindname, _ in next, Inputs.NewInputList do
table.clear(Inputs.NewInputList[bindname])
end
end
return Inputs
@BrandonForbrad
Copy link
Author

BrandonForbrad commented Dec 15, 2023

v1.0.01: added compatibility for ECS and functionality for inputs under the same binds and better support for ROJO/Rewire based systems.

@mikoooo12
Copy link

real

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment