Hello, I am in search of feedback on my script. So I am making a movement system and right now I have got 4 scripts: main controller, state controller, input controller and force controller. I would greatly appreciate if someone would look over my code and point out any inefficiencies or any other unlogical stuff. Thank you very much!
BASIC RUNDOWN OF THE SYSTEM:
- main script gets called in an external local script
- it calls the three other scripts
- input controller begins listening to keys and updates values accordingly
- main script starts updating states based on input
- main script moves character utilizing force controller
MAIN: (ACTS AS A MIDDLEMAN BETWEEN OTHER SCRIPTS)
local movement_controller_module = {}
movement_controller_module.__index = movement_controller_module
--services--
local runservice = game:GetService("RunService")
--modules--
local input_manager = require(script:WaitForChild("input_manager")).new()
input_manager:begin_listening()
local state_controller = require(script:WaitForChild("state_controller")).new()
local force_controller = require(script:WaitForChild("force_controller")).new()
--constructor
function movement_controller_module.new()
local self = setmetatable({}, movement_controller_module)
self:__init()
return self
end
--methods
function movement_controller_module:__init()
self.walking_speed = 4000
self.running_speed = 10000
self.crouching_speed = 2000
self.jump_force = 5000
end
function movement_controller_module:begin_updating_state()
--renderstepped loop to update state every frame
self.update_state_conn = runservice.PreRender:Connect(function()
print(state_controller:get_current_state())
--for standing state (none of movement keys are pressed = standing)
if not input_manager:is_key_pressed(Enum.KeyCode.W) and not input_manager:is_key_pressed(Enum.KeyCode.A) and not input_manager:is_key_pressed(Enum.KeyCode.S) and not input_manager:is_key_pressed(Enum.KeyCode.D) then
state_controller:set_state("standing")
force_controller:nullify_force("forward_force")
end
--for walking state (movement keys are pressed = walking)
if input_manager:is_key_pressed(Enum.KeyCode.W) or input_manager:is_key_pressed(Enum.KeyCode.A) or input_manager:is_key_pressed(Enum.KeyCode.S) or input_manager:is_key_pressed(Enum.KeyCode.D) then
state_controller:set_state("walking")
force_controller:apply_constant_force("forward_force", self:calculate_direction(), self.walking_speed)
end
--for running state (left shift pressed + character is not standing still = running)
if input_manager:is_key_pressed(Enum.KeyCode.LeftShift) and state_controller:get_current_state() ~= "standing" then
state_controller:set_state("running")
force_controller:apply_constant_force("forward_force", self:calculate_direction(), self.running_speed)
end
if input_manager:is_key_pressed(Enum.KeyCode.C) then
state_controller:set_state("crouching")
force_controller:apply_constant_force("forward_force", self:calculate_direction(), self.crouching_speed)
end
if input_manager:is_key_pressed(Enum.KeyCode.Space) and state_controller:get_current_state() ~= "crouching" then
state_controller:set_state("jumping")
force_controller:apply_momental_force("up_force", Vector3.yAxis, self.jump_force)
end
end)
end
function movement_controller_module:stop_updating_states()
--disconnects render stepped
if self.update_state_conn ~= nil then
self.update_state_conn:Disconnect()
self.update_state_conn = nil
end
end
function movement_controller_module:calculate_direction()
local direction = Vector3.new()
local cam = workspace.CurrentCamera
--checks every movement key
if input_manager:is_key_pressed(Enum.KeyCode.W) then direction += cam.CFrame.LookVector end
if input_manager:is_key_pressed(Enum.KeyCode.A) then direction += -cam.CFrame.RightVector end
if input_manager:is_key_pressed(Enum.KeyCode.S) then direction += -cam.CFrame.LookVector end
if input_manager:is_key_pressed(Enum.KeyCode.D) then direction += cam.CFrame.RightVector end
--if direction is 0 (no keys are being pressed OR keys that are pressed cancel eachother out)
if direction.Magnitude == 0 then
return Vector3.new()
end
--returns force without the Y axis as a UNIT VECTOR
return (direction * Vector3.new(1, 0, 1)).Unit
end
return movement_controller_module
STATE MANAGER
local state_controller_module = {}
state_controller_module.__index = state_controller_module
local default_state = "standing"
--constructor
function state_controller_module.new()
local self = setmetatable({}, state_controller_module)
self:__init()
return self
end
--methods
function state_controller_module:__init()
self.current_state = default_state
self.possible_states = {
"standing",
"walking",
"running",
"jumping",
"falling",
"crouching"
}
end
function state_controller_module:get_current_state(): string
return self.current_state
end
function state_controller_module:set_state(state: string)
--checks if state we are setting is a possible state
if table.find(self.possible_states, state) then
self.current_state = state
end
end
return state_controller_module
INPUT MANAGER
local input_manager_module = {}
input_manager_module.__index = input_manager_module
--services
local uis = game:GetService("UserInputService")
--constructor
function input_manager_module.new()
local self = setmetatable({}, input_manager_module)
self:__init()
return self
end
--methods
function input_manager_module:__init()
self.W = false
self.A = false
self.S = false
self.D = false
self.C = false
self.Space = false
self.LeftShift = false
end
function input_manager_module:begin_listening()
--prevents creating multiple connections
if not self.begin_conn then
--inputbegan connection
self.begin_conn = uis.InputBegan:Connect(function(input, processed)
--if its one of the keys we are tracking
if self[input.KeyCode.Name] ~= nil then
self[input.KeyCode.Name] = true
end
end)
end
--prevents creating multiple connections
if not self.end_conn then
--inputended connection
self.end_conn = uis.InputEnded:Connect(function(input, processed)
--if its one of the keys we are tracking
if self[input.KeyCode.Name] ~= nil then
self[input.KeyCode.Name] = false
end
end)
end
end
function input_manager_module:cut_connections()
--disconnects inputbegan
if self.begin_conn then
self.begin_conn:Disconnect()
self.begin_conn = nil
end
--disconnects inputended
if self.end_conn then
self.end_conn:Disconnect()
self.end_conn = nil
end
end
function input_manager_module:is_key_pressed(key: Enum.KeyCode): boolean
--checks if the asked is one of which we are checking
if self[key.Name] ~= nil then
return self[key.Name]
end
warn(key.Name.." is not being tracked by the input manager.")
end
return input_manager_module
FORCE CONTROLLER
local force_controller = {}
force_controller.__index = force_controller
force_controller.forces = {}
--private variables--
local player = game.Players.LocalPlayer
local char = player.Character or player.CharacterAdded:Wait()
local humrootpart = char:WaitForChild("HumanoidRootPart")
--constructor
function force_controller.new()
local self = setmetatable({}, force_controller)
--create forces
self.force_attachment = self:create_attachment()
self:add_force("forward_force")
self:add_force("up_force")
return self
end
--methods
function force_controller:create_attachment()
--prevents creating more if 1 already exists
if self.force_attachment then return self.force_attachment end
--cfg attachment
local attachment = Instance.new("Attachment")
attachment.Name = "force_attachment"
attachment.Parent = humrootpart
--saves to controller object
return attachment
end
function force_controller:get_attachment()
--lazy loads attachment if it doesn't exist
if self.force_attachment then return self.force_attachment end
self:create_attachment()
end
function force_controller:add_force(name: string)
--cfg force instance
local force = Instance.new("VectorForce")
force.Name = name
force.ApplyAtCenterOfMass = true
force.Force = Vector3.new()
force.Attachment0 = self:get_attachment()
force.Parent = self:get_attachment()
--saves to controller object
force_controller.forces[name] = force
end
function force_controller:get_force(name: string)
--lazy loads attachment if it doesn't exist
if force_controller.forces[name] then return force_controller.forces[name] end
self:add_force(name)
end
function force_controller:apply_constant_force(force_name: string, direction: Vector3, amount: number)
local force = self:get_force(force_name)
force.Force = direction.Unit * amount
end
function force_controller:apply_momental_force(force_name: string, direction: Vector3, amount: number)
local force = self:get_force(force_name)
force.Force = direction.Unit * amount
wait(0.2)
--removes force after slight delay
force.Force = Vector3.new(0, 0, 0)
end
function force_controller:update_force(name: string, new_amount: number)
--updates force amount
local force = self:get_force(name)
force.Force = force.Force.Unit * new_amount
end
function force_controller:nullify_force(force_name: string)
local force = self:get_force(force_name)
force.Force = Vector3.zero
end
return force_controller