Hello, i’m currenty trying to do a Lock System similar to almost any fighting game that has that, however there is a small yet annoying bug that I don’t know how to fix.
What I wanted to do is position the camera near the player and have that look at the target when the lock is true, everything was working fine until I noticed that the X of Offset that I gave to the camera is giving problems with the Player’s movement. When I go right to sort of go around the locked target, it automatically gets near it whitout me pressing W or S, and for the left it does the same except that it gets further, I tried to change some values and it seems that if the X of the offset is set to 0, it solves this issue, however, the camera is not where I want it to be placed at.
How can I place the camera where I want without affecting the player’s movement?
The simple thing to do would be to keep the camera’s alignment consistent, though that wouldn’t look nearly as good.
What you’d need to do is override the humanoid’s move vector to something reoriented based on the matrix multiple between the character CFrame and the camera CFrame (aka weird math I never do but Roblox has nice functions in the CFrame class).
Here is the code to override the MoveDirection:
-- Note the priority needs to be after the `.Character` priority.
RunService:BindToRenderStep("MoveOverride", Enum.RenderPriority.Character.Value + 1, function()
if player.Character then
local humanoid = player.Character:FindFirstChild("Humanoid")
if humanoid then
local currentDirection = humanoid.MoveDirection
local fixedDirection = -- TODO
humanoid:Move(fixedDirection, false) -- It's probably easier to calculate it relative to the world, so we can use false here
end
end
end)
Here’s a (quick and totally untested) try at the required conversion (I haven’t entirely thought through how the input is converted relative to the camera by the default scripts, so it definitely might not work correctly):
-- Note the priority needs to be after the `.Character` priority.
RunService:BindToRenderStep("MoveOverride", Enum.RenderPriority.Character.Value + 1, function()
if player.Character then
local humanoid = player.Character:FindFirstChild("Humanoid")
local hrp = player.Character:FindFirstChild("HumanoidRootPart")
if humanoid and hrp then
local currentDirection = humanoid.MoveDirection
local cameraCFrame = workspace.CurrentCamera.CFrame
-- Potentially need to remove the x and z part of the cameraCFrame's orientation
local characterCFrame = hrp.CFrame
local currWorldSpace = currentDirection
local currCamSpace = cameraCFrame:VectorToObjectSpace(currWorldSpace)
local fixedCharSpace = characterCFrame:VectorToObjectSpace(currCamSpace)
local fixedWorldSpace = characterCFrame:VectorToWorldSpace()
local fixedFlattenedUnit = Vector3.new(fixedWorldSpace.X, 0 fixedWorldSpace.Z).Unit
local fixedDirection = fixedFlattenedUnit
humanoid:Move(fixedDirection, false) -- It's probably easier to calculate it relative to the world, so we can use false here
end
end
end)
If that only sort of doesn’t work (i.e. the direction is slightly off) it’s probably because of the downwards tilt of the camera, which the default camera code might remove before converting the input to a direction.
Actually, instead of all that complicated code, you could just use the raw input direction directly from the player module:
local Controller = require(game.Players.LocalPlayer.PlayerScripts:WaitForChild("PlayerModule")):GetControls()
function getInputDirection()
local direction = workspace.CurrentCamera.CFrame:VectorToWorldSpace(Controller:GetMoveVector())
return if direction.X > 0 or direction.Z > 0 then Vector3.new(direction.X, 0, direction.Z).Unit else Vector3.zero
end
local Controller = require(game.Players.LocalPlayer.PlayerScripts:WaitForChild("PlayerModule")):GetControls()
function getInputDirection()
local direction = workspace.CurrentCamera.CFrame:VectorToWorldSpace(Controller:GetMoveVector())
return if direction.X > 0 or direction.Z > 0 then Vector3.new(direction.X, 0, direction.Z).Unit else Vector3.zero
end
-- Note the priority needs to be after the `.Character` priority.
RunService:BindToRenderStep("MoveOverride", Enum.RenderPriority.Character.Value + 1, function()
if player.Character then
local humanoid = player.Character:FindFirstChild("Humanoid")
local hrp = player.Character:FindFirstChild("HumanoidRootPart")
if humanoid and hrp then
local characterCFrame = hrp.CFrame
local fixedDirection = Vector3.zero
-- Get the raw input direction from the player module
local inputDirection = getInputDirection()
if inputDirection ~= Vector3.zero then
-- Convert the input direction into world space
fixedDirection = characterCFrame:VectorToWorldSpace(inputDirection)
end
humanoid:Move(fixedDirection, false)
end
end
end)
Thank you for replying and sorry for the late answer, I tried the code and it didn’t work for me, but probably it’s because I don’t know how to apply it correctly, do I have to ovveride the CFrame of the Hrp I did? And how do I also include the Hrp facing the target? if it can be of any use, this is the entire script btw
local RunService = game:GetService("RunService")
local Controller = require(game.Players.LocalPlayer.PlayerScripts:WaitForChild("PlayerModule")):GetControls()
local Players = game:GetService("Players")
local UserInputService = game:GetService("UserInputService")
local Player = Players.LocalPlayer
local Mouse = Player:GetMouse()
local Camera = workspace.CurrentCamera
local Character = Player.Character or Player.CharacterAdded:Wait()
local Humanoid = Character:WaitForChild("Humanoid")
local NormalCameraOffset = Humanoid.CameraOffset
local Locking = false
local LockedTarget = nil
local Highlight = Instance.new("Highlight")
Highlight.FillTransparency = 1
local CameraOffset = Vector3.new(8, 3, -7)
RunService.RenderStepped:Connect(function() -- Mouse Hovering
local target = Mouse.Target
if target and not Locking then
local Parent = target.Parent
if Parent:FindFirstChild("Humanoid") then
Highlight.Parent = Parent
elseif Parent:IsA("Accessory") then
local accessoryParent = Parent.Parent
Highlight.Parent = accessoryParent
else
Highlight.Parent = game.ReplicatedStorage
end
else
Highlight.Parent = game.ReplicatedStorage
end
end)
-- Camera and Hrp
local function updateCameraAndCharacter()
if Humanoid.Health == 0 then
Locking = false
LockedTarget = nil
Camera.CameraType = "Custom"
Highlight.Parent = game.ReplicatedStorage
end
if LockedTarget then
Camera.CameraType = Enum.CameraType.Custom
local RootPart = Character:WaitForChild("HumanoidRootPart")
local targetPosition = LockedTarget.Position
local RootPos = RootPart.Position
local direction = (Vector3.new(targetPosition.X, RootPos.Y, targetPosition.Z) - RootPos).unit
RootPart.CFrame = CFrame.new(RootPos, RootPos + direction)
local cameraPosition = RootPart.Position + (RootPart.CFrame.RightVector * CameraOffset.X) + (RootPart.CFrame.UpVector * CameraOffset.Y) + (RootPart.CFrame.LookVector * CameraOffset.Z)
Camera.CFrame = CFrame.new(cameraPosition, targetPosition)
end
end
-- Lock Inputs
UserInputService.InputBegan:Connect(function(input, gameProcessedEvent)
if input.UserInputType == Enum.UserInputType.MouseButton3 or (input.UserInputType == Enum.UserInputType.Keyboard and input.KeyCode == Enum.KeyCode.L) and not gameProcessedEvent then
local Target = Mouse.Target
if (input.UserInputType == Enum.UserInputType.MouseButton3 or (input.UserInputType == Enum.UserInputType.Keyboard and input.KeyCode == Enum.KeyCode.L)) and Locking == true and Target then
Locking = false
LockedTarget = nil
Camera.CameraType = "Custom"
elseif Target then
local parent = Target.Parent
if parent:FindFirstChild("Humanoid") then
LockedTarget = parent:FindFirstChild("HumanoidRootPart")
Locking = true
print(Target.Name)
elseif parent:IsA("Accessory") then
local accessoryParent = parent.Parent
if accessoryParent and accessoryParent:FindFirstChild("Humanoid") then
LockedTarget = accessoryParent:FindFirstChild("HumanoidRootPart")
Locking = true
print(Target.Name)
end
else
LockedTarget = nil
Locking = false
Camera.CameraType = "Custom"
end
else
LockedTarget = nil
Locking = false
Camera.CameraType = "Custom"
end
end
end)
RunService.RenderStepped:Connect(function()
if Locking then
updateCameraAndCharacter()
end
end)
Whoops, that’s totally my bad. I didn’t read over the code I got for the input direction and it totally just converts it to camera relative (exactly what I didn’t want!). It also has an error where it needs an absolute value. Here’s the fixed code:
local RunService = game:GetService("RunService")
local player = game:GetService("Players").LocalPlayer
local abs = math.abs
local Controller = require(game.Players.LocalPlayer.PlayerScripts:WaitForChild("PlayerModule")):GetControls()
function getInputDirection()
local direction = Controller:GetMoveVector()
return if abs(direction.X) > 0.001 or abs(direction.Z) > 0.001 then Vector3.new(direction.X, 0, direction.Z).Unit else Vector3.zero
end
-- Note the priority needs to be after the `.Character` priority.
RunService:BindToRenderStep("MoveOverride", Enum.RenderPriority.Character.Value + 1, function()
if player.Character then
local humanoid = player.Character:FindFirstChild("Humanoid")
local hrp = player.Character:FindFirstChild("HumanoidRootPart")
if humanoid and hrp then
local characterCFrame = hrp.CFrame
local fixedDirection = Vector3.zero
-- Get the raw input direction from the player module
local inputDirection = getInputDirection()
if inputDirection ~= Vector3.zero then
-- Convert the input direction into world space
fixedDirection = characterCFrame:VectorToWorldSpace(inputDirection)
end
humanoid:Move(fixedDirection, false)
end
end
end)
To implement it in your code, you just need to paste it at the bottom and add an if statement to make sure it only does the correction when Locking is true:
local RunService = game:GetService("RunService")
local Controller = require(game.Players.LocalPlayer.PlayerScripts:WaitForChild("PlayerModule")):GetControls()
local Players = game:GetService("Players")
local UserInputService = game:GetService("UserInputService")
local Player = Players.LocalPlayer
local Mouse = Player:GetMouse()
local Camera = workspace.CurrentCamera
local Character = Player.Character or Player.CharacterAdded:Wait()
local Humanoid = Character:WaitForChild("Humanoid")
local NormalCameraOffset = Humanoid.CameraOffset
local Locking = false
local LockedTarget = nil
local Highlight = Instance.new("Highlight")
Highlight.FillTransparency = 1
local CameraOffset = Vector3.new(8, 3, -7)
RunService.RenderStepped:Connect(function() -- Mouse Hovering
local target = Mouse.Target
if target and not Locking then
local Parent = target.Parent
if Parent:FindFirstChild("Humanoid") then
Highlight.Parent = Parent
elseif Parent:IsA("Accessory") then
local accessoryParent = Parent.Parent
Highlight.Parent = accessoryParent
else
Highlight.Parent = game.ReplicatedStorage
end
else
Highlight.Parent = game.ReplicatedStorage
end
end)
-- Camera and Hrp
local function updateCameraAndCharacter()
if Humanoid.Health == 0 then
Locking = false
LockedTarget = nil
Camera.CameraType = "Custom"
Highlight.Parent = game.ReplicatedStorage
end
if LockedTarget then
Camera.CameraType = Enum.CameraType.Custom
local RootPart = Character:WaitForChild("HumanoidRootPart")
local targetPosition = LockedTarget.Position
local RootPos = RootPart.Position
local direction = (Vector3.new(targetPosition.X, RootPos.Y, targetPosition.Z) - RootPos).unit
RootPart.CFrame = CFrame.new(RootPos, RootPos + direction)
local cameraPosition = RootPart.Position + (RootPart.CFrame.RightVector * CameraOffset.X) + (RootPart.CFrame.UpVector * CameraOffset.Y) + (RootPart.CFrame.LookVector * CameraOffset.Z)
Camera.CFrame = CFrame.new(cameraPosition, targetPosition)
end
end
-- Lock Inputs
UserInputService.InputBegan:Connect(function(input, gameProcessedEvent)
if input.UserInputType == Enum.UserInputType.MouseButton3 or (input.UserInputType == Enum.UserInputType.Keyboard and input.KeyCode == Enum.KeyCode.L) and not gameProcessedEvent then
local Target = Mouse.Target
if (input.UserInputType == Enum.UserInputType.MouseButton3 or (input.UserInputType == Enum.UserInputType.Keyboard and input.KeyCode == Enum.KeyCode.L)) and Locking == true and Target then
Locking = false
LockedTarget = nil
Camera.CameraType = "Custom"
elseif Target then
local parent = Target.Parent
if parent:FindFirstChild("Humanoid") then
LockedTarget = parent:FindFirstChild("HumanoidRootPart")
Locking = true
print(Target.Name)
elseif parent:IsA("Accessory") then
local accessoryParent = parent.Parent
if accessoryParent and accessoryParent:FindFirstChild("Humanoid") then
LockedTarget = accessoryParent:FindFirstChild("HumanoidRootPart")
Locking = true
print(Target.Name)
end
else
LockedTarget = nil
Locking = false
Camera.CameraType = "Custom"
end
else
LockedTarget = nil
Locking = false
Camera.CameraType = "Custom"
end
end
end)
RunService.RenderStepped:Connect(function()
if Locking then
updateCameraAndCharacter()
end
end)
-- Move Direction Correction Code --
local RunService = game:GetService("RunService")
local player = game:GetService("Players").LocalPlayer
local abs = math.abs
local Controller = require(game.Players.LocalPlayer.PlayerScripts:WaitForChild("PlayerModule")):GetControls()
function getInputDirection()
local direction = Controller:GetMoveVector()
return if abs(direction.X) > 0.001 or abs(direction.Z) > 0.001 then Vector3.new(direction.X, 0, direction.Z).Unit else Vector3.zero
end
-- Note the priority needs to be after the `.Character` priority.
RunService:BindToRenderStep("MoveOverride", Enum.RenderPriority.Character.Value + 1, function()
-- The code should only run when the player is locked on a target
if not Locking then return end
if player.Character then
local humanoid = player.Character:FindFirstChild("Humanoid")
local hrp = player.Character:FindFirstChild("HumanoidRootPart")
if humanoid and hrp then
local characterCFrame = hrp.CFrame
local fixedDirection = Vector3.zero
-- Get the raw input direction from the player module
local inputDirection = getInputDirection()
if inputDirection ~= Vector3.zero then
-- Convert the input direction into world space
fixedDirection = characterCFrame:VectorToWorldSpace(inputDirection)
end
humanoid:Move(fixedDirection, false)
end
end
end)
Here’s how some test code looks in studio (the movement is relative to the character):
Exact Test Code in a LocalScript in StarterPlayerScripts
local RunService = game:GetService("RunService")
local player = game:GetService("Players").LocalPlayer
local abs = math.abs
local Controller = require(game.Players.LocalPlayer.PlayerScripts:WaitForChild("PlayerModule")):GetControls()
function getInputDirection()
--local direction = workspace.CurrentCamera.CFrame:VectorToWorldSpace()
local direction = Controller:GetMoveVector()
return if abs(direction.X) > 0.001 or abs(direction.Z) > 0.001 then Vector3.new(direction.X, 0, direction.Z).Unit else Vector3.zero
end
RunService:BindToRenderStep("MoveOverride", Enum.RenderPriority.Character.Value + 1, function()
if player.Character then
local humanoid = player.Character:FindFirstChild("Humanoid")
local hrp = player.Character:FindFirstChild("HumanoidRootPart")
if humanoid and hrp then
-- Since the movement is relative to the HRP we don't want movement to affect the HRP
humanoid.AutoRotate = false -- Only for testing, simulates locking
local characterCFrame = hrp.CFrame
local fixedDirection = Vector3.zero
-- Get the raw input direction from the player module
local inputDirection = getInputDirection()
print(inputDirection)
if inputDirection ~= Vector3.zero then
-- Convert the input direction into world space
fixedDirection = characterCFrame:VectorToWorldSpace(inputDirection)
end
humanoid:Move(fixedDirection, false)
end
end
end)
Thank you very much! Your code really helped me out, I find out that Humanoid.AutoRotate did the trick, so I just had to add some simple code , this is how it turned out
function getInputDirection()
--local direction = workspace.CurrentCamera.CFrame:VectorToWorldSpace()
local direction = Controller:GetMoveVector()
return if abs(direction.X) > 0.001 or abs(direction.Z) > 0.001 then Vector3.new(direction.X, 0, direction.Z).Unit else Vector3.zero
end
RunService:BindToRenderStep("MoveOverride", Enum.RenderPriority.Character.Value + 1, function()
if Locking then
if Player.Character then
local humanoid = Player.Character:FindFirstChild("Humanoid")
local hrp = Player.Character:FindFirstChild("HumanoidRootPart")
if humanoid and hrp then
-- Since the movement is relative to the HRP we don't want movement to affect the HRP
humanoid.AutoRotate = false
local characterCFrame = hrp.CFrame
local fixedDirection = Vector3.zero
-- Get the raw input direction from the player module
local inputDirection = getInputDirection()
if inputDirection ~= Vector3.zero then
-- Convert the input direction into world space
fixedDirection = characterCFrame:VectorToWorldSpace(inputDirection)
end
humanoid:Move(fixedDirection, false)
end
end
else
Locking = false
Humanoid.AutoRotate = true
end
end)