Camera affecting Player's movement in a Lock System

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?

Videos:

Offset.X = 8

Offset.X = 0

Here is the code

local CameraOffset = Vector3.new(8, 3, -7) 
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.Scriptable
		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

3 Likes

Yeah this is a tough one.

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.

1 Like

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

Source: How would I go about detecting the direction a player is inputting for their character across platforms? - #5 by dogwarrior24

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)

That’s much more likely to work properly.

1 Like

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)

1 Like

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):

ScreenRecording2024-08-06at12.37.01PM-ezgif.com-optimize

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)
1 Like

For some reason the character now goes backwards when the locking is true, although I think this is about modifying small number so I’ll try that now

1 Like

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)
1 Like

Nice! Glad I could help.

You should be able to flip input directions in the getInputDirection function by multiplying either direction.X or direction.Z by -1 here:

... then Vector3.new(direction.X, 0, direction.Z).Unit else ...
1 Like

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.