Best way to handle character movement

Hello,
The title is a bit vague, what i want to know what the best and optimal way is to handle character movement, but not only movement, just character related things in generall. What do i mean by that?

Lets say i have 3 localscripts,

  1. running
  2. roll
  3. dashing

Normally, with the current knowledge i would just create 3 local scripts and then put them in startercharacterscripts. But thats seems kinda unnecessary, because i would need to make 3 different scripts and all need to have the same lines of code over and over again

Example

image
We can see in this image i have 3 scripts as stated before the drop down.

In all of these scripts i have this typed:

local plrs = game:GetService('Players')
local plr = plrs.LocalPlayer
local char = plr.Character or plr.CharacterAdded:Wait()
local hum = char:FindFirstChildWhichIsA("Humanoid")
My attempt
local runner = {}
runner.__index = runner

local runSpeed = 30

local cas = game:GetService("ContextActionService")

local runKeybind = Enum.KeyCode.LeftShift
local ll = "Run"

function runner.new(char:Model)
	local hum = char:FindFirstChildWhichIsA("Humanoid")
	local self = setmetatable({}, runner)
	
	self.oldSpeed = hum.WalkSpeed
	self.running = false
	self.hum = hum
	
	local function handler(_, state)
		if state == Enum.UserInputState.Begin then
			runner:Run()
		elseif state == Enum.UserInputState.End then
			runner:UnRun()
		end
	end
	
	cas:BindAction(ll, handler, true, runKeybind)
	
	return self
end

function runner:Run()
	self.hum.WalkSpeed = runSpeed
end

function runner:UnRun()
	self.hum.WalkSpeed = self.oldSpeed
end

function runner:Disconnect()
	cas:UnbindAction(ll)
end


return runner

This module script didnt work as i wanted, and instead bonked me in the head with this error: 20:52:14.171 Workspace.3Xp01tME.charmovement.run:33: attempt to index nil with 'WalkSpeed' - Client - run:33

Second attempt (successfull)

I did it with module script.

local runner = {}
runner.__index = runner

local runSpeed = 30
local cas = game:GetService("ContextActionService")

function runner.new(char:Model, ll)
	local hum = char:FindFirstChildWhichIsA("Humanoid")
	local self = setmetatable({}, runner)
	
	self.oldSpeed = hum.WalkSpeed
	self.running = false
	self.hum = hum
	self.ll = ll
	
	return self
end

function runner:RunHandler()
	local function handler(actionName, inputState, inputObject)
		if inputState == Enum.UserInputState.Begin then
			self:Run()
		elseif inputState == Enum.UserInputState.End then
			self:UnRun()
		end
	end
	cas:BindAction(self.ll, handler, true, Enum.KeyCode.LeftShift)
end

function runner:Run()
	self.hum.WalkSpeed = runSpeed
end

function runner:UnRun()
	self.hum.WalkSpeed = self.oldSpeed
end

function runner:Disconnect()
	cas:UnbindAction(self.ll)
end


return runner

My initial thoughts was to use module scripts and then have 1 local script that handles the module scripts, but i dont know how i would make that happen. I would like to hear yall thoughts, or maybe how you do it.

Thanks.

1 Like

Not really sure if this is a ā€˜organizationā€™ problem or what but here is what I would do:

Simply what I would do is Iā€™d create a local script called ā€œMovementā€ or ā€œMovementControllerā€ whatever you want to call it. Then Iā€™d just write the movement code for everything there, but if you want to utilize module scripts then you could make a ā€œMovementā€ Class in a module script and then use the class with the local script.

This is just how I would do it and sorry for bad english.
Samuel

I like the architecture of the approach you use here, @3Xp01tME .
However, my grain of sand would be that I prefer something more simple, especially given how simple may a run/roll/dash behavior be (literally, your running behavior are two lines, haha);
You could implement the three in the same LocalScript, instead of outsourcing everything to one or three ModuleScripts, you can handle/apply them directly in a LocalScript for simplicityā€™s sake.

Example

For instance, for running, I would do something like this (directly in the LocalScript. Iā€™ll use UserInputService for this demonstration but you can use ContextActionService if you would like)

local UserInputService = game:GetService("UserInputService")

local player = game:GetService("Players").LocalPlayer
local character = player.Character or player.CharacterAdded:Wait()
local humanoid = character:WaitForChild("Humanoid")

local keyCodes = {
	Run = Enum.KeyCode.LeftShift
}

local regularSpeed = game:GetService("StarterPlayer").CharacterWalkSpeed
local runSpeed = 28

UserInputService.InputBegan:Connect(function(input, gameProcessed)
	if not gameProcessed and input.KeyCode == keyCodes.Run then -- start running
		humanoid.WalkSpeed = runSpeed
	end
end)

UserInputService.InputEnded:Connect(function(input, gameProcessed)
	if not gameProcessed and input.KeyCode == keyCodes.Run then -- stop running
		humanoid.WalkSpeed = regularSpeed
	end
end)

Also, you could use a running toggle function like this one if you would like, instead of applying behavior directly in-line:

local function toggleRunning()
	if humanoid.WalkSpeed == regularSpeed then humanoid.WalkSpeed = runSpeed
	else humanoid.WalkSpeed = regularSpeed end
end

Hope you find this insightful.

1 Like

This here is my problem. The more I add, the clunkier and less readable the script becomes. This certainly is better than using a script for each movement. The run and roll were an example.

Couldā€™ve formulated this much better, but I donā€™t feel like doing that on mobile :upside_down_face:

I see your point, itā€™s a valid feeling.
Although, you could make that LocalScript to be exclusively about ā€œAbilitiesā€, thereby, no disorganization could take place, by exclusively implementing (better said, stuffing) the miscellaneous of abilities in that LocalScript.

Agreed, though, if any particular ability is too complex or itā€™s closely related to another ability, and considering that ModuleScripts are out of the game to not create a soup out of each rice, you could do another LocalScript, parented to the main LocalScript, that handles exclusively those abilities behavior upon the main LocalScript directing it to do so.

Haha, donā€™t worry, unless Iā€™m missing anything, you were fairly clear.

You have some good points. I think im gonna still use modules tho. It feels better and more organized, reading it will also be much better, and to further reinforce my point: I can add things to the running module that other scripts can access too without the help of bindable events etc.

For example,
If i were damaged, and i want the player to be slower, i can add a variable in the module and make that the new running speed, or anything in that direction, but i will look into how i would prefer somethings.

1 Like