Customizable KeyBinds. Better UIS or Advanced CAS

Customizable Keybinds

is a module script that simplifies binding Keys for you and gives you a lot of features.
For example:

  1. ComboKeys. Combination of keys will activate function but every key can also activate their own function.

  2. Priority system. Trigger only function with highest priority or few with the same priority

  3. Hold time. how much time player held a key or combination of keys

  4. 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

  1. AlterFunc Upgrade:
    will be for keys
    Will get how much time key was late
  2. LateFunc Upgrade:
    will be for Key
    Will get how much time Key was late

If you have any ideas or corrections, donā€™t hesitate to write them

36 Likes

I found mistake there. It resets previous func

self.Keys[input.KeyCode.Name] = self.DoubleKeys[input.KeyCode.Name]
task.wait(.4) --acceptable time between double press(changeble)
self.Keys[input.KeyCode.Name] = nil

Changed to this:

local prevKey = self.Keys[input.KeyCode.Name]
self.Keys[Key] = self.DoubleKeys[input.KeyCode.Name]
task.wait(.4)
self.Keys[Key] = input.KeyCode.Name

It now saves previous func.
Changed ā€œinput.KeyCode.Nameā€ to ā€œKeyā€ for tidiness

2 Likes

There isnā€™t a huge performance difference as far as Iā€™m aware of between CAS and UIS for you to need to worry about it, as long as you arenā€™t creating a ton of UIS connections you should be fine

2 Likes

What does ā€œDoubleKeysā€ do? charlimit

3 Likes

You need press them twice. For example: shift for sprint, but if you press shift 2 times it will activate another ability

3 Likes

Oh ok, thatā€™s neat, iā€™ll probably in up using this module, thanks

1 Like

Please wait another 20 minutes, Iā€™m little busy. I need to implement new feature, and fix double event call
Edit: Just donā€™t call ā€œKeyHandler:Startā€ 2 times. You can put ā€œKeyHandler:Startā€ before functions

1 Like

Oh, i didnā€™t mean right now, but probably later tonight i will try it.

1 Like

I understand. Thanks for feedback

1 Like

No problem! charlimit

1 Like

Hello everyone. New update is out.

New variable:

Holdtime = how much time player held this key (time between InputBegan and InputEnded)

Usability:
If player pressed ā€œRā€ quickly then it will cast fireball but if he held ā€œRā€ for 2 seconds it will cast Giant Fire Ball
or
The more you hold a ā€˜Qā€™ key, the wider the dash is

Iā€™m proud of my module but it is so quiet in replies
I want to discuss with other developers, find out their reactions and etc

Edit: Do I need to change the topicā€™s name?

2 Likes

I think a multiple keys system would be cool like if you press multiple keys at the same time it triggers the callback overall, good module from what I been reading

1 Like

Luckily, I have already used functions like that. I will implement this function(s) today.

2 Likes

Hello everyone. Iā€™m sad to announce that update is delayed due to school tomorrow.

I have question:
Should I add another InputBegan connection for ComboKeys? (Only input began, because if there is 3 or 4 buttons you donā€™t need fire function after stopped holding all of the buttons)

Because of my creativity I found 6 good ways to do this(more than 50 if we include variations)

Main issue now is that when checking ComboKeys with DoubleKeys and Keys it makes response little bit slower. I didnā€™t tested yet(tomorrow will) but
Should I add another InputBegan connection for ComboKeys?

The Keys and DoubleKeys will be as fast as usual, but for using ComboKeys you will need KeyHandler:ComboStart(). Soooo

What do we sacrifice?

Using only 2 connections or Keys and DoubleKeys speed

Also, I got an idea for SbSKeys(Step by Step keys) which you should press keys step by step(one by one) in less than 0.4 seconds(customizable) for something to happen(for example: open door or very powerful spell)

And mini question:
3 connections(Usual and InputBegan for ComboKeys)
or
4 connections(Usual and InputBegan, InputEnded for ComboKeys
If no one will answer I will make both( will use 4 connections and will add 3 connections in Unique functions). 3 connections are faster so I can not disapprove it. I think I will do both

Edit:

3 and 4 connections idea is ironically disapproved. The maximum optimized code for ComboKeys doesnā€™t affect speed. Also, current ComboKeys are better than both and can have functions of both

4 Likes

ComboKeys is out. @PR0XlM. Check tutorial for this. I also maximized speed for ComboKeys that even when tested with previous version, the version with ComboKeys was almost equal (0.01 seconds faster or slower)

Next things on my plan:

  • SbSKeys(Step by Step Keys)

  • Unique function SimpleComboKeys. Something like this:

KeyHandler.SimpleComboKeys["Q+R"] = function(state, holdtime)
	
end

Edit:
If you have any idea, like literally any, you can write it and I will do my best to do it. Have a nice day, I went to sleep

Edit2:

Work on module is stopped for 1.5 days(for me itā€™s long time) due to my chores. If I could get 2 free days I think moduleā€™s version would be around 3.2

Edit3:

Iā€™m happy to tell you that I almost finished SbSKeys. Announce date is tomorrow. UniqueFunc SimpleCombo is easy to do but it kinda makes functions untidy, so I make code samples that you will need to copy to be able to use SimpleComboKeys.

How about me cutting my module code into different sections(functions) and you will collect only that parts that you will use?
Kinda crazy idea but let me know your answer to that

3 Likes

Hello everyone. I got some news

  • SimpleComboKeys are disapproved
    Explanation: itā€™s just limited version of Combo Keys

  • SbSKeys are disapproved, but added.
    Explanation:
    Use ComboKey with ā€œOrdered = trueā€ and ā€œRepetitive = trueā€.
    You will get SbSKey ( Step by Step Key).

Version 2.0 is coming tomorrow

Sorry, update took more time than I expected. But here some spoilers:

  • ComboKey.Time = How much time he have to finish combo, if time is over all pressed keys will disactivate. (you can make puzzles with this) or ( you can use it to parkour as well ). (Only limits are your imagination(creativity, I donā€™t know the difference))

  • All Keys.MaxHoldTime = Maximum amount of time player can have between pressing and releasing button. For example: if MaxHoldTime = 5 and player still not releasing button it will no longer activate ā€œendedā€ state or it will call another function
    (All Keys = Keys, DoubleKeys, ComboKeys)

  • DoubleKeys will be removed. Instead you will chose how many times you need to press a button. Long story short: You have more control, you can use DoubleKeys, TripleKeys, QuadrupleKeys, QuintupleKeys, SextupleKeys, SeptupleKeysā€¦ as many as you want. Even you can make player press button 999 times to activate function (just for the joke and 999 is not actual limit but I think we need a limit (nobody to argue with me).

  • Fixing bugs. I didnā€™t even know that ā€œOrderedā€ wasnā€™t working. Changed ā€œCompleteā€

And the biggest one:

The module will use metatables

(I learned them yesterday. You donā€™t need to know metatables to use module, the metatables will just improve the code writing add some features)
BTW: Global script optimization

Also all possible parameters to ComboKey

KeyHandler.ComboKeys[1] = {
	Keys = {}, --needed
	Ordered = true,
	Complete = false,
	Priority = true,
	ResetFuncs = true,
	Repetitive = true,
	Time = 10,
	MaxHoldTime = 2,
	Funcs = {},
	Func = function() --needed
		
	end,
}

With Version 2.0 the upgrade to the topicā€™s interface will come

Edit: (itā€™s not 1.9 but itā€™s 2.0 because of full code rewriting, optimizing with the power of metatables)

2 Likes

Hello everyone. Version 2.0 is here

I used some metamethods to make code look tidy and added more arsenal.

KeyHandler.Keys[1] = {
	Keys = {"Q", "G"}, --required
	Func = function(state, ht)
		print("state", ht)
	end, --required
	
	Ordered = true, --optional 
	Priority = 2, --optional
	
	MaxHoldTime = 2, --optional
	AlterFunc = function() --optional
		
	end,
	
	--
	Funcs = {}, --optional
	ResetFuncs = true, --optional
	
	MaxTime = 10,--optional
	LateFunc = function() --optional

	end,
}

But
You canā€™t use same keys in 1 combo so DoubleKeys are not there yet. Itā€™s hard to make logic for them, Iā€™m trying. I think they will be ready today

1 Like

Hello everyone. The 2.2 version is out. DoubleKeys are added.

The description is changed and soonā€¦

1 Like

I think adding an option to create a mobile button and multiple options to change things like the name, image, position would be good.

Ok. Its on my list now.

  1. Working MaxTime
  2. GUIs
  3. More than Double Keys

If I finish MaxTime, then the rest is easy part, because MaxTime gives you ability to make combinations even if youā€™re not holding desired keys but pressed them. For mobile players will be hard to hold 2-3 keys at the same time, if they are not playing with more than 2 fingers, so I think you will use MaxTime to register their combinations.

I hate to be late so I will say a week for 3 of them but if I will be lucky I can finish them in 2 days. Also, thanks for idea

1 Like