Customizable Keybinds
is a module script that simplifies binding Keys for you and gives you a lot of features.
For example:
-
ComboKeys. Combination of keys will activate function but every key can also activate their own function.
-
Priority system. Trigger only function with highest priority or few with the same priority
-
Hold time. how much time player held a key or combination of keys
-
Repetitive Keys. Functions that will be activated if key is pressed as many times as you want
Personally, I can say that this is one of the best modules to deal with keys and the most important part is it is still becoming better and better. I promise I will not throw away this project.
Module:
--Version 3.2
--Source:
--https://devforum.roblox.com/t/customizable-keybinds-better-uis-or-advanced-cas/2910676
export type KeyValues = {
Keys: {{
key : string,
func : (state: Enum.UserInputType, ht : number) -> nil,
GUI : ImageButton | TextButton
}},
Func: (state: Enum.UserInputType, ht: number) -> nil,
Active : boolean,
Ordered: boolean,
Priority: number,
MaxHoldTime: number,
AlterFunc: (state: Enum.UserInputType, ht: number) -> nil,
ResetFuncs: boolean,
MaxTime: number,
LateFunc: (state: Enum.UserInputType, ht: number) -> nil
}
local UIS = game:GetService("UserInputService")
local RunS = game:GetService("RunService")
local ActiveKeys = {} --Enabled Keys with Highest Priority
local AllKeys = {} --All asigned Keys
local KeyHandler = {}
KeyHandler.Default = {
Ordered = false,
ResetFuncs = false,
Priority = 1,
MaxHoldTime = 2,
MaxTime = 3
}
KeyHandler.WasActivated = {}
local Key = {}
Key.__index = Key
function KeyHandler.Create(values : KeyValues)
local KeytableProxy = values
KeytableProxy.Active = true
KeytableProxy.LastHT = 0
KeytableProxy.KeysActive = {}
KeytableProxy.TimeRunningUp = nil
for i,v in pairs(KeyHandler.Default) do
if KeytableProxy[i] == nil then
KeytableProxy[i] = v
end
end
for i, tablekey in ipairs(KeytableProxy.Keys) do
local keys = string.split(tablekey[1], ",")
KeytableProxy.KeysActive[i] = false
for _, key in ipairs(keys) do
Key.Create(i, key, tablekey[2], tablekey[3], KeytableProxy)
end
end
local KeytableMeta = {}
KeytableMeta.__index = KeytableProxy
KeytableMeta.__newindex = function(t,i,v)
local value = KeytableProxy[i]
if value ~= v then
if i == "Keys" then
if value ~= nil then
--//DeleteKey
for i, keyindex in ipairs(KeytableProxy[i]) do
local keys = string.split(keyindex, ",")
for _, Key in ipairs(keys) do
Key:Destroy()
end
end
end
--//Add Key
for i, tablekey in ipairs(v) do
local keys = string.split(tablekey[1], ",")
KeytableProxy.KeysActive[i] = false
for _, key in ipairs(keys) do
Key.Create(i, key, tablekey[2], tablekey[3], KeytableProxy)
end
end
end
KeytableProxy[i] = v
end
end
local Keytable = {}
return setmetatable(Keytable, KeytableMeta)
end
function Key.Create(i, key : string, func, GUI : ImageButton|TextButton, Parent)
local LocalKey = {
key = key,
Priority = Parent.Priority,
Func = func,
AlterFunc = Parent.AlterFuncs ~= nil and Parent.AlterFuncs[key] or Parent.AlterFuncs ~= nil and Parent.AlterFuncs[i] or nil,
MaxHoldTime = Parent.MaxHoldTime or KeyHandler.MaxHoldTime,
LastHT = 0,
Keytable = Parent,
Index = i
}
if game.UserInputService.TouchEnabled == true then
if GUI ~= nil then
GUI.Visible = true
GUI.MouseButton1Down:Connect(function()
LocalKey:Fire(Enum.UserInputState.Begin)
end)
GUI.MouseButton1Up:Connect(function()
LocalKey:Fire(Enum.UserInputState.End)
end)
end
end
if AllKeys[key] then
local highest = 0
for _, Key in ipairs(AllKeys[key]) do
if Key.Priority > highest then
ActiveKeys[key] = {Key}
highest = Key.Priority
Key.ActiveKeysIndex = 1
elseif Key.Priority == highest then
local function findKeyWithSameParent(parent)
for i,v in ipairs(ActiveKeys[key]) do
if v.Parent == parent then
return i
end
end
end
local index = findKeyWithSameParent(Key.Parent)
if index then
if Key.Index < ActiveKeys[key][index].Index then
ActiveKeys[key][index] = Key
Key.ActiveKeysIndex = index
end
else
table.insert(ActiveKeys[key], Key)
Key.ActiveKeysIndex = #ActiveKeys[key]
end
end
end
table.insert(AllKeys[key], Key)
Key.AllKeysIndex = #AllKeys[key]
else
AllKeys[key] = {LocalKey}
ActiveKeys[key] = {LocalKey}
Key.ActiveKeysIndex = #ActiveKeys[key]
Key.AllKeysIndex = #AllKeys[key]
end
setmetatable(LocalKey, Key)
end
function Key:Fire(state : Enum.UserInputState)
if self.Keytable.Active == false then return false end
if state == Enum.UserInputState.Begin then
if state ~= nil and self.Keytable.Ordered == true and self.Keytable.KeysActive[self.Index-1] ~= nil and self.Keytable.KeysActive[self.Index-1] == false then
return false
end
if self.Keytable.TimeRunningUp == nil then
task.spawn(function()
local MaxTime = 0
self.Keytable.TimeRunningUp = RunS.Heartbeat:Connect(function(dt)
MaxTime += dt
if MaxTime > self.Keytable.MaxTime then
table.clear(KeyHandler.WasActivated)
if self.Keytable.LateFunc ~= nil then
self.Keytable.LateFunc()
end
self.Keytable.TimeRunningUp:Disconnect()
end
end)
end)
end
self.Keytable.KeysActive[self.Index] = true
self.LastHT = tick()
if self.Func then
self.Func(Enum.UserInputState.Begin)
end
if not table.find(self.Keytable.KeysActive, false) then
self.Keytable.LastHT = tick()
self.Keytable.Func(Enum.UserInputState.Begin)
end
else
if tick() - self.LastHT < self.MaxHoldTime then
if not table.find(self.Keytable.KeysActive, false) then
self.Keytable.LastHT = tick() - self.Keytable.LastHT
self.Keytable.Func(Enum.UserInputState.End, self.Keytable.LastHT)
end
if self.Func then
if self.Keytable.ResetFuncs ~= true or self.Keytable.LastHT == 0 then
self.LastHT = tick() - self.LastHT
self.Func(Enum.UserInputState.End, self.LastHT)
end
end
elseif self.AlterFunc then
if self.Keytable.ResetFuncs ~= true or self.Keytable.LastHT == 0 then
self.LastHT = tick() - self.LastHT
self.AlterFunc(Enum.UserInputState.End, self.LastHT)
end
end
self.Keytable.KeysActive[self.Index] = false
end
if state ~= nil then return true end
end
function Key:Destroy()
table.remove(ActiveKeys[self.key], self.ActiveKeysIndex)
table.remove(AllKeys[self.key], self.AllKeysIndex)
end
local KeyConnections = nil
function KeyHandler:Start()
KeyConnections = {
Began = UIS.InputBegan:Connect(function(input, gPE)
local key = input.KeyCode.Name
--if gPE then return end
if ActiveKeys[key] then
for _, Key in ipairs(ActiveKeys[key]) do
local Active = Key:Fire(Enum.UserInputState.Begin)
if Active then table.insert(KeyHandler.WasActivated, Key) end
end
end
end),
Ended = UIS.InputEnded:Connect(function(input, gPE)
local key = input.KeyCode.Name
--if gPE then return end
for _, localKey in ipairs(KeyHandler.WasActivated) do
localKey:Fire(Enum.UserInputState.End)
end
table.clear(KeyHandler.WasActivated)
end)
}
end
function KeyHandler:GetKeys()
print("All Keys: ", AllKeys)
print("Active Keys: ", ActiveKeys)
end
function KeyHandler:Stop()
if KeyConnections ~= nil then
KeyConnections.Begin:Disconnect()
KeyConnections.Ended:Disconnect()
KeyConnections = nil
else
warn("KeyHandler is not active")
end
end
return KeyHandler
Usage:
local FireBall = KeyHandler.Create({
Keys = {"Z"},
Func = function(state, ht)
--code
end
} :: KeyHandler.KeytableValues)
local Dash = KeyHandler.Create({
Keys = {"X"},
Func = function(state, ht)
--code
end
} :: KeyHandler.KeytableValues)
From code above you can use this:
--For example when player equips tool
Dash.Active = true
--and when unequips
Dash.Active = false
--You can also use
Fireball.LastHT
--For example:
if Fireball.LastHT > 5 then
Now letās look at Parameters we can use:
local HollowPurple = KeyHandler.Create({
Keys = {
{"Z,E", function(state, ht)
if state == Enum.UserInputState.Begin then
--Create Red Particles
else
--Remove Red Particles
--Launch Red
end
end, Player.PlayerGUIs.Screen.ImageButton},
{"X,R", function(state, ht)
if state == Enum.UserInputState.Begin then
--Create Blue Particles
else
--Remove Blue Particles
--Launch Blue
end
end, Player.PlayerGUIs.Screen.ImageButton2}
},
Func = function(state, ht) --Func that will be activated when all Keys will be pressed
if state == Enum.UserInputState.Begin then
--Remove Blue and Red particles
--Create Purple Particles
else
--Launch Purple
end
end,
Ordered = false, --does player need to press keys in order (default is false)
Priority = 2, --only the keys with high priority or same priority will be activated (default is 1)
MaxHoldTime = 3, --after that time Keys will not activate "ended" state and will call AlterFunc (default is 3)
AlterFunc = function()--Will soon be upgraded
--code
end,
ResetFuncs = true, --If Purple begin started, Red and Blue ended will not be activated
MaxTime = 4, --How much time player has to finish combo
LateFunc = function() --if not nill then will be called after time is up
--code
end,
} :: KeyHandler.KeytableValues)
Latest Version 3.2
Logs:
-
3.2 Reset bug fixed
-
3.1 Structure changes. More readable code
-
3.0 Whole code rewriting, optimization. Alternate keys are added. Requiring KeyHandler from different scripts will no more create more connections
-
2.4 States name change
-
2.3 Big update: Working MaxTime, More than Double Keys, GUIs
-
2.2 Fixed bugs. Double Keys added
-
2.1 Fixed bugs. Code optimized. (Next update will be late cuz Iām working)
-
2.0 Used metatables. More advanced and optimized code.(you can get previous version below if you want)
-
1.8 Changed ComboKeys. Now you can get state and holdtime even for combo keys
-
1.7 Added new variable ResetFuncs for ComboKeys and 3 mini functions.
(Fun fact: ComboKeys were made without using IsKeyDown() -
1.6 Added ComboKeys. Learn more in tutorial.
Also, the warnings in code was added so you will make less mistakes -
1.5 Added HoldTime. +3 functions, +1 unique functions.
StateChanger script giveaway(in Example) -
1.4 Fixed main issue with DoubleKeys. Now you can get states(began, ended) for DoubleKeys
-
1.3 Added Reset() and SpecifiedReset(). Added Demonstration
-
1.2 Function state(began, ended) for UIS.InputBegan and UIS.InputEnded
More states can be added -
1.1 Fixed func reset and tidier code
-
1.0 First code
Future plans
- AlterFunc Upgrade:
will be for keys
Will get how much time key was late - LateFunc Upgrade:
will be for Key
Will get how much time Key was late