As you said you will need an offset to the movement vector opposite.
I have done some research into the default player module, the camera relative to the movement is done here. I’m guessing if you can somehow insert the camera CFrame using CFrame.lookAt() it will always be perp
function ControlModule:calculateRawMoveVector(humanoid: Humanoid, cameraRelativeMoveVector: Vector3): Vector3
--Blabla a lot of other code for VR controls or joystick
--The important below which prints with default key board controls
local c, s
--Modify cameraCFrame here such that it is always perpendicular to the target enemy
--Add this line to replace the camera CFrame for recalculation purposes
local cameraCFrame = CFrame.lookAt(humRP, targetCharacterPosition) --Find a way to get targetCharacterPosition in this function
--Below is the default code, it is complex and doesn't matter as we know the default movement behavior (move right = move right in camera.CFrame.RightVector)
local _, _, _, R00, R01, R02, _, _, R12, _, _, R22 = cameraCFrame:GetComponents()
if R12 < 1 and R12 > -1 then
-- X and Z components from back vector.
c = R22
s = R02
else
-- In this case the camera is looking straight up or straight down.
-- Use X components from right and up vectors.
c = R00
s = -R01*math.sign(R12)
end
local norm = math.sqrt(c*c + s*s)
print("Returning")
return Vector3.new(
(c*cameraRelativeMoveVector.X + s*cameraRelativeMoveVector.Z)/norm,
0,
(c*cameraRelativeMoveVector.Z - s*cameraRelativeMoveVector.X)/norm
)
end
Nvm got really interested in the problem. Here is a proof of concept local script which forks over the player module and changes the function
local ControlModule = require(game.StarterPlayer.StarterPlayerScripts.PlayerModule):GetControls()
local VRService = game:GetService("VRService")
local UserInputService = game:GetService("UserInputService")
local target = workspace.Steve --replace target to your liking
local function getGamepadRightThumbstickPosition(): Vector3
local state = UserInputService:GetGamepadState(Enum.UserInputType.Gamepad1)
for _, input in pairs(state) do
if input.KeyCode == Enum.KeyCode.Thumbstick2 then
return input.Position
end
end
return Vector3.new(0,0,0)
end
function ControlModule:calculateRawMoveVector(humanoid: Humanoid, cameraRelativeMoveVector: Vector3): Vector3
local camera = workspace.CurrentCamera
if not camera then
return cameraRelativeMoveVector
end
local cameraCFrame = camera.CFrame
cameraCFrame = CFrame.lookAt(humanoid.RootPart.Position, target.Position)
if VRService.VREnabled and humanoid.RootPart then
local vrFrame = VRService:GetUserCFrame(Enum.UserCFrame.Head)
vrFrame = self:GetEstimatedVRTorsoFrame()
-- movement relative to VR frustum
local cameraDelta = camera.Focus.Position - cameraCFrame.Position
if cameraDelta.Magnitude < 3 then -- "nearly" first person
cameraCFrame = cameraCFrame * vrFrame
else
cameraCFrame = camera.CFrame * (vrFrame.Rotation + vrFrame.Position * camera.HeadScale)
end
end
if humanoid:GetState() == Enum.HumanoidStateType.Swimming then
if VRService.VREnabled then
cameraRelativeMoveVector = Vector3.new(cameraRelativeMoveVector.X, 0, cameraRelativeMoveVector.Z)
if cameraRelativeMoveVector.Magnitude < 0.01 then
return Vector3.zero
end
local pitch = -getGamepadRightThumbstickPosition().Y * math.rad(80)
local yawAngle = math.atan2(-cameraRelativeMoveVector.X, -cameraRelativeMoveVector.Z)
local _, cameraYaw, _ = cameraCFrame:ToEulerAnglesYXZ()
yawAngle += cameraYaw
local movementCFrame = CFrame.fromEulerAnglesYXZ(pitch, yawAngle, 0)
return movementCFrame.LookVector
else
return cameraCFrame:VectorToWorldSpace(cameraRelativeMoveVector)
end
end
local c, s
local _, _, _, R00, R01, R02, _, _, R12, _, _, R22 = cameraCFrame:GetComponents()
if R12 < 1 and R12 > -1 then
-- X and Z components from back vector.
c = R22
s = R02
else
-- In this case the camera is looking straight up or straight down.
-- Use X components from right and up vectors.
c = R00
s = -R01*math.sign(R12)
end
local norm = math.sqrt(c*c + s*s)
return Vector3.new(
(c*cameraRelativeMoveVector.X + s*cameraRelativeMoveVector.Z)/norm,
0,
(c*cameraRelativeMoveVector.Z - s*cameraRelativeMoveVector.X)/norm
)
end
just maybe not so rly quickly but could you implement it to something like this please, because i want to be able to turn it off and on
--pretty bad code, i'll fix it later
local plr = game:GetService("Players").LocalPlayer
local char = plr.Character or plr.CharacterAdded:Wait()
local hum = char:FindFirstChildOfClass("Humanoid")
local humRP = hum.RootPart
--local otherHP = plr.PlayerGui.OtherHPBarGui
--otherHPFrame = otherHP.HealthbarFrame.HealthBar
local uis = game:GetService("UserInputService")
hum:SetAttribute("Locked", false)
local function getTarget(input: InputObject, gameProcessedEvent: boolean)
if gameProcessedEvent then return end
if input.UserInputType == Enum.UserInputType.MouseButton3 then
local camera = workspace.CurrentCamera
local length = 66.6667
local raycastParams = RaycastParams.new()
raycastParams.FilterDescendantsInstances = {char}
raycastParams.FilterType = Enum.RaycastFilterType.Exclude
raycastParams.IgnoreWater = true
local raycastResult = workspace:Raycast(camera.CFrame.Position, camera.CFrame.LookVector * length, raycastParams)
if raycastResult.Instance.Parent:IsA("Model") then
if raycastResult.Instance.Parent:FindFirstChildOfClass("Humanoid") then
local Target = {
["Head"] = raycastResult.Instance.Parent.Head,
["Model"] = raycastResult.Instance.Parent,
["Hum"] = raycastResult.Instance.Parent:FindFirstChildOfClass("Humanoid"),
["RootPart"] = raycastResult.Instance.Parent:FindFirstChildOfClass("Humanoid").RootPart,
}
if hum:GetAttribute("Locked") == false then
local mouse = game:GetService("Players").LocalPlayer:GetMouse()
--otherHP.Enabled = true
local function LockOn()
humRP.CFrame = CFrame.new(humRP.Position,Vector3.new(Target.RootPart.Position.X, humRP.Position.Y, Target.RootPart.Position.Z))
game:GetService("TweenService"):Create(camera, TweenInfo.new(0.0625, Enum.EasingStyle.Quad, Enum.EasingDirection.InOut), {CFrame = CFrame.new((humRP.CFrame * Vector3.new(5, 2, 7.5)),humRP.Position:Lerp(Target.Head.Position, 0.5))}):Play()
local health = Target.Hum.Health
local f = health/100
--otherHP.Adornee = Target
--otherHPFrame:TweenSize(UDim2.new(f,0,1,0),"Out","Quad",0.25)
end
game:GetService("RunService"):BindToRenderStep("LockOn", Enum.RenderPriority.Camera.Value + 1, LockOn)
repeat
task.wait()
camera.CameraType = Enum.CameraType.Scriptable
until
camera.CameraType == Enum.CameraType.Scriptable
hum:SetAttribute("Locked", true)
print("alpha")
elseif hum:GetAttribute("Locked") == true or hum.Died or Target.Hum.Died then
--ill do this later
hum:SetAttribute("Locked", false)
end
else
print("nah")
end
end
end
end
uis.InputEnded:Connect(function(input: InputObject, gameProcessedEvent: boolean)
getTarget(input, gameProcessedEvent)
end)```