Created
May 15, 2024 00:41
-
-
Save ImXirvin/142439248f677f86997c9e1f3286d49f to your computer and use it in GitHub Desktop.
Movement Controller for Roblox
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
local RunService = game:GetService("RunService") | |
local ContextActionService = game:GetService("ContextActionService") | |
local ReplicatedStorage = game:GetService("ReplicatedStorage") | |
local Controllers = ReplicatedStorage:WaitForChild("Controllers") | |
local HUDController = require(Controllers.Character.HUDController) | |
-- Constants | |
local HEAD_HORIZONTAL_FACTOR = 1 | |
local HEAD_VERTICAL_FACTOR = 0.6 | |
local BODY_HORIZONTAL_FACTOR = 0.5 | |
local BODY_VERTICAL_FACTOR = 0.4 | |
local UPDATE_SPEED = 0.25 | |
local playerConfig = require(ReplicatedStorage.Configs.Character.player).movement | |
local camera = workspace.CurrentCamera | |
local PlayerController = require(Controllers.PlayerController) | |
local character = PlayerController.Character | |
local humanoid = PlayerController.Humanoid | |
local humanoidRootPart = humanoid.RootPart | |
local isR6 = humanoid.RigType == Enum.HumanoidRigType.R6 | |
local head = character.Head | |
local torso = isR6 and character.Torso or character.UpperTorso | |
local neck = isR6 and torso.Neck or head.Neck | |
local waist = not isR6 and torso.Waist | |
local neckOriginalCFrame = neck.C0 | |
local waistOriginalCFrame = not isR6 and waist.C0 | |
local _ = require(script.Parent:WaitForChild("Movement").footsteps) | |
local MovementController = {} | |
neck.MaxVelocity = 1 / 3 | |
RunService.RenderStepped:Connect(function() | |
-- Check if every required body part exists and whether the CurrentCamera's CameraSubject is the Humanoid | |
if torso and head and ((isR6 and neck) or (neck and waist)) and camera.CameraSubject == humanoid then | |
local cameraCFrame = camera.CFrame | |
local headCFrame = head.CFrame | |
local torsoLookVector = torso.CFrame.lookVector | |
local distance = (headCFrame.p - cameraCFrame.p).magnitude | |
local heightDifference = headCFrame.Y - cameraCFrame.Y | |
local asinDifferenceDistance = math.asin(heightDifference / distance) | |
local perpendicular = ((headCFrame.p - cameraCFrame.p).Unit:Cross(torsoLookVector)).Y | |
local angleHeadVerticalFactor = asinDifferenceDistance * HEAD_VERTICAL_FACTOR | |
local angleHorizontalFactor = -perpendicular * HEAD_HORIZONTAL_FACTOR | |
if isR6 then | |
local neckRotation = CFrame.Angles(-angleHeadVerticalFactor, 0, angleHorizontalFactor) | |
neck.C0 = neck.C0:lerp(neckOriginalCFrame * neckRotation, UPDATE_SPEED) | |
else | |
local headRotation = CFrame.Angles(angleHeadVerticalFactor, angleHorizontalFactor, 0) | |
neck.C0 = neck.C0:lerp(neckOriginalCFrame * headRotation, UPDATE_SPEED) | |
local bodyRotation = CFrame.Angles( | |
angleHeadVerticalFactor * BODY_VERTICAL_FACTOR, | |
angleHorizontalFactor * BODY_HORIZONTAL_FACTOR, | |
0 | |
) | |
waist.C0 = waist.C0:lerp(waistOriginalCFrame * bodyRotation, UPDATE_SPEED) | |
end | |
end | |
end) | |
local function handleSprint(shouldSprint) | |
if shouldSprint == true then | |
if not MovementController:IsEventAllowed("sprint") then | |
return | |
end | |
local currentStamina = HUDController.Status:GetStatus("stamina") | |
if currentStamina <= 0 then | |
return | |
end | |
humanoid:SetAttribute("sprinting", true) | |
task.spawn(function() | |
local previousMultiplier = character:GetAttribute("stamina_multiplier") | |
character:SetAttribute("stamina_multiplier", 0) | |
local drain = playerConfig.staminaSprintDrainPerSecond | |
while humanoid:GetAttribute("sprinting") do | |
HUDController.Status:UpdateStatus("stamina", drain) | |
local currentStamina = HUDController.Status:GetStatus("stamina") | |
if currentStamina <= 0 then | |
humanoid:SetAttribute("sprinting", false) | |
break | |
end | |
task.wait(1) | |
end | |
--Maybe an exploit here | |
character:SetAttribute("stamina_multiplier", previousMultiplier) | |
end) | |
else | |
humanoid:SetAttribute("sprinting", false) | |
end | |
end | |
PlayerController:OnHumanoidAttributeChanged("sprinting", function() | |
local sprinting = humanoid:GetAttribute("sprinting") | |
if sprinting then | |
humanoid.WalkSpeed = playerConfig.sprintSpeed | |
else | |
humanoid.WalkSpeed = playerConfig.walkSpeed | |
end | |
end) | |
local function handleInput(actionName, inputState, inputObject) | |
if actionName == "Sprint" then | |
local shouldSprint = false | |
if inputState == Enum.UserInputState.Begin then | |
shouldSprint = true | |
end | |
handleSprint(shouldSprint) | |
end | |
end | |
function MovementController:Init() | |
character = PlayerController.Character | |
humanoid = PlayerController.Humanoid | |
humanoidRootPart = humanoid.RootPart | |
ContextActionService:BindAction("Sprint", handleInput, true, Enum.KeyCode.LeftShift, Enum.KeyCode.ButtonX) | |
ContextActionService:SetTitle("Sprint", "Sprint") | |
end | |
function MovementController:IsEventAllowed(event: string) | |
if not self.events or not self.events[event] then | |
return true | |
end | |
for _, callback in pairs(self.events[event]) do | |
if callback then | |
if not callback() then | |
return false | |
end | |
end | |
end | |
return true | |
end | |
function MovementController:RegisterHook(event: string, callback: () -> boolean) | |
self.events = self.events or {} | |
local length = #self.events + 1 | |
if not self.events[event] then | |
self.events[event] = {} | |
end | |
self.events[event][length] = callback | |
return length | |
end | |
function MovementController:UnregisterHook(event: string, id: number) | |
self.events = self.events or {} | |
self.events[event][id] = nil | |
end | |
HUDController.Status:OnChange("stamina", function(value) | |
local jumpDrain = playerConfig.staminaJumpDrain | |
local allowedJump = humanoid:GetStateEnabled(Enum.HumanoidStateType.Jumping) | |
if value < -jumpDrain and allowedJump then | |
humanoid:SetStateEnabled(Enum.HumanoidStateType.Jumping, false) | |
elseif value > -jumpDrain and not allowedJump then | |
humanoid:SetStateEnabled(Enum.HumanoidStateType.Jumping, true) | |
end | |
end) | |
PlayerController:OnHumanoid("Jumping", function() | |
local jumpDrain = playerConfig.staminaJumpDrain | |
HUDController.Status:UpdateStatus("stamina", jumpDrain) | |
end) | |
PlayerController:OnHumanoid("FreeFalling", function() | |
local PlrVelocity = humanoidRootPart.Velocity.Y | |
PlrVelocity *= -1 | |
if PlrVelocity > playerConfig.maxFallingVelocity then | |
humanoid.Health = 0 | |
elseif PlrVelocity > playerConfig.minFallingVelocity then | |
print("Falling damage") | |
humanoid.Health -= PlrVelocity / 4 | |
end | |
end) | |
return MovementController |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment