Hi, I am trying to make abilities for certain classes and to use the abilities you can press a key to activate them. I am trying to do all this inside a module script and each class will have a different module script that handles the abilities.
Problem is I don’t know how to detect if a key is pressed in a module script.
MainModule
local UserInputService = game:GetService("UserInputService")
local Archer = {}
Archer.__index = Archer
function Archer.new(Player)
local self = setmetatable({},Archer)
self.Player = Player
return self
end
function Archer:Initilize()
print("INITILIZED: Archer Module")
local InfoFolder = self.Player:FindFirstChild("Information")
local PlayerMouse = self.Player:GetMouse()
Archer:Inputs(self.Player)
end
function Archer:Inputs(Player)
local InfoFolder = Player.Information
UserInputService.InputBegan:Connect(function(Input,GameProcessed)
if GameProcessed then return end
if Input.KeyCode == Enum.KeyCode.Q then
print("Move 1")
end
if Input.KeyCode == Enum.KeyCode.E then
print("Move 2")
end
if Input.KeyCode == Enum.KeyCode.Z then
print("Move 3")
end
if Input.KeyCode == Enum.KeyCode.F then
print("Ultimate")
end
end)
end
function Archer:Marksman()
end
function Archer:HomingArrow()
end
function Archer:ArrowBarrage()
end
return Archer
You’ve mentioned you don’t know how to detect keys being pressed, so I am going to give you a portion of my code, and hopefully it will give you an idea of how to do it.
Hopefully it helps you out and you’ll know what to do next.
local UserInput = game:GetService("UserInputService") ---This is the user input service
local ActionController = {} ---This is where I store all my functions, I am using rojo + knit to manage my code.
---I have this function that handles actions binding.
function ActionController:BindAction(keyCode, stateName)
UserInput.InputBegan:Connect(function(input, gameProcessed)
if gameProcessed then
return
end
if input.KeyCode ~= keyCode then
return
end
if not (self.IsAlive and self.Humanoid) then ---Checks whether the player is alive, otherwise there's no point in playing actions.
return
end
local canPlay = true
if self.State ~= "None" then ---This is where I check if the player is already playing an action, if so, I undo it.
canPlay = self.State ~= stateName
self:UndoState()
end
if canPlay then ---If the player is not playing an action, I set the state and play the action.
self.State = stateName
self:PlayState(stateName) ---This is where you define your handler function
---In my case, it looks up for the "stateName" inside a table and executes the function
end
end)
end
---This function essentially binds all actions at once, this is where I organize my "actions".
function ActionController:BindActions()
local states = { ---This is how I map all keys and state names, these are essentially strings that are referenced for my handler function.
Enum.KeyCode.Z, "At Ease",
Enum.KeyCode.X, "Prone",
Enum.KeyCode.C, "Crouch",
Enum.KeyCode.V, "Salute",
Enum.KeyCode.B, "Cross Arms"
}
for index = 1, #states, 2 do ---This is how I iterate through the table, I am using a for loop because I am lazy lol.
self:BindAction(states[index], states[index + 1])
end
end
I think I’m doing this completely wrong nothing is printing for me and I’m not sure why
local UserInputService = game:GetService("UserInputService")
local ActionController = {}
local Archer = {}
Archer.__index = Archer
function Archer.new(Player)
local self = setmetatable({},Archer)
self.Player = Player
return self
end
function Archer:Initilize()
print("INITILIZED: Archer Module")
local InfoFolder = self.Player:FindFirstChild("Information")
local PlayerMouse = self.Player:GetMouse()
ActionController:BindActions()
end
function ActionController:BindAction(keyCode, stateName)
UserInputService.InputBegan:Connect(function(input, gameProcessed)
if gameProcessed then
return
end
print("Test")
if input.KeyCode ~= keyCode then
return
end
if not (self.IsAlive and self.Humanoid) then ---Checks whether the player is alive, otherwise there's no point in playing actions.
return
end
local canPlay = true
if self.State ~= "None" then ---This is where I check if the player is already playing an action, if so, I undo it.
canPlay = self.State ~= stateName
self:UndoState()
end
if canPlay then ---If the player is not playing an action, I set the state and play the action.
self.State = stateName
self:PlayState(stateName) ---This is where you define your handler function
---In my case, it looks up for the "stateName" inside a table and executes the function
end
end)
end
function ActionController:BindActions()
local states = { ---This is how I map all keys and state names, these are essentially strings that are referenced for my handler function.
Enum.KeyCode.Q, "Marksman",
Enum.KeyCode.E, "Homing Arrow",
Enum.KeyCode.Z, "Arrow Barrage",
Enum.KeyCode.F, "Ultimate",
}
for index = 1, #states, 2 do ---This is how I iterate through the table, I am using a for loop because I am lazy lol.
self:BindAction(states[index], states[index + 1])
end
end
function Archer:PlayState(stateName)
if stateName == "Marksman" then
print("Marksman")
end
if stateName == "Homing Arrow" then
print("Homing Arrow")
end
if stateName == "Arrow Barrage" then
print("Arrow Barrage")
end
if stateName == "Ultimate" then
print("Ultimate")
end
end
function Archer:UndoState(stateName)
print("Undoing state")
end
return Archer
I’ve simplified your code and commented off some parts for testing, but hopefully you’ll know what to do next:
local UserInputService = game:GetService("UserInputService")
---Creates the action controller object
local Archer = {}
Archer.__index = Archer
function Archer.new(Player)
local self = setmetatable({}, Archer)
self.Player = Player
self.State = "None"
return self
end
function Archer:Initilize()
print("INITILIZED: Archer Module")
---local InfoFolder = self.Player:FindFirstChild("Information")
---local PlayerMouse = self.Player:GetMouse()
local ArcherObj = Archer.new() ---Creates a new object
ArcherObj:BindActions() ---Binds the actions
end
function Archer:BindAction(keyCode, stateName)
UserInputService.InputBegan:Connect(function(input, gameProcessed)
if gameProcessed then
return
end
if input.KeyCode ~= keyCode then
return
end
print("Input is NOT a game processed event and IS the bound key, continuing...")
--[[
if not (self.IsAlive and self.Humanoid) then ---Checks whether the player is alive, otherwise there's no point in playing actions.
return
end
print("Player IS alive, continuing...")
--]]
---local canPlay = true
if self.State ~= "None" then ---This is where I check if the player is already playing an action, if so, I undo it.
self:UndoState()
else
self.State = stateName
self:PlayState(stateName) ---This is where you define your handler function
end
end)
end
function Archer:BindActions()
local states = { ---This is how I map all keys and state names, these are essentially strings that are referenced for my handler function.
Enum.KeyCode.Q, "Marksman",
Enum.KeyCode.E, "Homing Arrow",
Enum.KeyCode.Z, "Arrow Barrage",
Enum.KeyCode.F, "Ultimate",
}
for index = 1, #states, 2 do ---This is how I iterate through the table, I am using a for loop because I am lazy lol.
self:BindAction(states[index], states[index + 1])
end
end
function Archer:PlayState(stateName)
if stateName == "Marksman" then
print("Marksman")
end
if stateName == "Homing Arrow" then
print("Homing Arrow")
end
if stateName == "Arrow Barrage" then
print("Arrow Barrage")
end
if stateName == "Ultimate" then
print("Ultimate")
end
end
function Archer:UndoState(stateName)
print("Undoing state")
self.State = "None"
end
Archer:Initilize()
I’m not really sure if it’s just studio but I tested it and it didn’t work for some reason. I checked if it was being initilized and everything but UserInputService.InputBegan:Connect(function(input, gameProcessed) didn’t run.
Here’s the code I runned
local UserInputService = game:GetService("UserInputService")
---Creates the action controller object
local Archer = {}
Archer.__index = Archer
function Archer.new(Player)
local self = setmetatable({}, Archer)
self.Player = Player
self.State = "None"
return self
end
function Archer:Initilize()
print("INITILIZED: Archer Module")
---local InfoFolder = self.Player:FindFirstChild("Information")
---local PlayerMouse = self.Player:GetMouse()
local ArcherObj = Archer.new() ---Creates a new object
ArcherObj:BindActions() ---Binds the actions
end
function Archer:BindAction(keyCode, stateName)
print("Binding action: ".. stateName)
UserInputService.InputBegan:Connect(function(input, gameProcessed)
if gameProcessed then
return
end
if input.KeyCode ~= keyCode then
return
end
print("Input is NOT a game processed event and IS the bound key, continuing...")
--[[
if not (self.IsAlive and self.Humanoid) then ---Checks whether the player is alive, otherwise there's no point in playing actions.
return
end
print("Player IS alive, continuing...")
--]]
---local canPlay = true
if self.State ~= "None" then ---This is where I check if the player is already playing an action, if so, I undo it.
self:UndoState()
else
self.State = stateName
self:PlayState(stateName) ---This is where you define your handler function
end
end)
end
function Archer:BindActions()
local states = { ---This is how I map all keys and state names, these are essentially strings that are referenced for my handler function.
Enum.KeyCode.Q, "Marksman",
Enum.KeyCode.E, "Homing Arrow",
Enum.KeyCode.Z, "Arrow Barrage",
Enum.KeyCode.F, "Ultimate",
}
for index = 1, #states, 2 do ---This is how I iterate through the table, I am using a for loop because I am lazy lol.
self:BindAction(states[index], states[index + 1])
end
end
function Archer:PlayState(stateName)
if stateName == "Marksman" then
print("Marksman")
end
if stateName == "Homing Arrow" then
print("Homing Arrow")
end
if stateName == "Arrow Barrage" then
print("Arrow Barrage")
end
if stateName == "Ultimate" then
print("Ultimate")
end
end
function Archer:UndoState(stateName)
print("Undoing state")
self.State = "None"
end
-- I removed the Archer:Initilize since it was already being initilized
return Archer
Output:
Studio
19:06:02.145 INITILIZED: Main Menu - Server - MenuModule:13
19:06:08.691 INITILIZED: Archer Module - Server - MainModule:16
19:06:08.691 Binding action: Marksman - Server - MainModule:24
19:06:08.691 Binding action: Homing Arrow - Server - MainModule:24
19:06:08.691 Binding action: Arrow Barrage - Server - MainModule:24
19:06:08.691 Binding action: Ultimate - Server - MainModule:24
My personal preferred method of handling keys is to have a generic input controller that can watch for certain input triggers that the client gives. It can then call the relevant action in an activated class/character controller.
Instead of an inputs function, I would independently have skill functions in the class module. The controller class would listen on different inputs and accordingly call certain functions to my liking. If I wanted to build on additional inputs (e.g. one class double taps to perform ultimate, another only needs a single tap), the class controller receiving the inputs could track them so it can act accordingly. It’s also maintainable since it divides input handling from class/character handling.
You may want to consider structuring your code that way instead.
It doesn’t have to be a LocalScript, it can be another ModuleScript. The main thing I’m talking about is the code itself, handling inputs should be divorced from any specific character module. You can create a generic handler that listens to inputs and gives it to the character modules to work with.
In short though yes, you want separate code that will listen for triggered input and then call actions from the active character module. If you have a permanent kit for all characters (all are expected to have 3 skills and an ultimate), for example, any press of the ultimate key will always call the ultimate function in the active character module.
That’s alright, it can work even without a set kit for each character.
Regarding your inputs: I’m thinking more along the line of a generic controller, meaning you only have one that handles all input from the client and delivers it to class modules. I have an example that I personally use with my own class-based RPG experience.
This is how I have mine set up:
CharacterMaps would be equivalent to your Classes folder. init.lua is the input controller. It might be a bit hard to see but init.lua and CharacterMaps are both children of one folder but init.lua is not a child of CharacterMaps, they’re on the same level.
The input controller is where we set up our UserInputService connections. As we have controller and mobile support, it allows us to make actions equivalent (for example: pressing left mouse, left trigger and tapping a specific button would all do the same thing). It also allows us to set an active “action map” which determines which character’s controls we want to call actions from.
In the characters maps, that’s where we set up the actions that should happen when actions are called. That’d be equivalent to you making an ultimate function in your class module.
Key is pressed → Input controller tells a character map what input was sent and what the state of it is (began or ended) → ActionMap receives this directive, finds what action is linked to that input if there is one, then calls that function