"Look behind you" feature for first-person camera

I’m programming a camera for a first-person game. Right now, I’m working on a feature that will allow the player to look behind them while holding the ‘Q’ key. The camera will slowly pan 180 degrees horizontally while Q is held, and when let go will pan -180 degrees to the original orientation. Currently I’m using TweenService to achieve this, though if I end up using something else, I’ll still have one fundamental issue.

The issue is this: lets say you hold Q to pan 180 degrees, but you let go of Q halfway through. Now the script will try to subtract 180 degrees to reach the original orientation, but obviously it won’t. I fixed this by using os.clock() to time when each Tween started, then subtract that from the time recorded when the Tween was interrupted. I could then divide the elapsed time by the tweenInfo.Time and find its percent completion, and use that to find how many degrees I needed to pan the camera to return to its original orientation.

Not only does this feel overly complicated, but it also doesn’t fully solve the issue. Here’s another example: let’s say you did the same steps as before, but then you pressed Q again, interrupting the Tween once again. Now it’ll try to use the original interruption as the starting point to return to. Letting go of Q will then use that interruption as the starting point, and so on.

This is where it starts getting really confusing for me. I can’t wrap my head around any fix to this issue. An easy fix would just be to force the Tweens fully play out instead of interrupting every time Q is pressed or released, but this is not what I want. Let me summarize exactly what I want here:

  • I want this feature to be spammable. I should be able to press and release Q, interrupting the Tweens as many times as I want and have the camera return to its original orientation.

  • It needs to work while the camera is being moved around in other ways. Whether the player is walking, panning their camera around, etc., the ‘look behind you’ feature should smoothly rotate along with the player.

I’m not asking for a completely brand new script. I just need some ideas so solve my current issue. If you think there’s a better way to approach this entirely then feel free to say so. Let me know if you need further clarification

1 Like

Here is something I wrote up, that would only work with a custom camera system. That is, your script is getting the camera panning input and applying it. This wont work with the default roblox camera, because character movement always goes towards where the camera looks.

We would need an offset value, to apply to the camera AFTER the movement of the player is handled, that way you can continue to walk forward, while your camera faces backward.

This might give you an idea of what you need to do.

local RunService = game:GetService("RunService")
local UserInputService = game:GetService("UserInputService")
local TweenService = game:GetService("TweenService")

local camera = workspace.CurrentCamera

local easingValue = 0
local turningRate = 2 -- the speed at which the camera turns
local minRadians = 0
local maxRadians = math.pi -- 180 degrees

local update = function(deltaTime)
    -- subtract or add the rate * deltaTime to the easing value
	if UserInputService:IsKeyDown(Enum.KeyCode.Q) then
		easingValue += turningRate * deltaTime
	else
		easingValue -= turningRate * deltaTime
	end
	easingValue = math.clamp(easingValue, minRadians, maxRadians)
    -- the easing value applied to a tween's easing style. 
	local rotationScalar = TweenService:GetValue(easingValue, Enum.EasingStyle.Sine, Enum.EasingDirection.InOut)

	camera.CFrame = CFrame.new(camera.CFrame.Position) * CFrame.Angles(0, rotationScalar * maxRadians, 0)
end

RunService:BindToRenderStep("CameraUpdate", Enum.RenderPriority.First.Value, update)

This is the result. I am using the default camera, so it doesnt really work as you might want it to, but paired with a custom camera this could do what you want.

TLDR; its going to be more complicated than you think it is

This line of thinking was what I needed. I already had a custom cam script that’s pretty similar to your example so this wasn’t very hard to solve

The current issues with this are that looking up or down while turning around pans like twice as fast and completely screws everything up, and that the camera controls are inverted while turned around.

First I made a camera rotation offset using this as the base https://devforum.roblox.com/t/how-would-i-offset-the-camera-with-rotation/1667248/22

I then did this to set the offset every frame

local oldOffset = CFrame.new()
local offset = CFrame.new()
local angleStorage = 0

RS.Stepped:Connect(function(time: number, dt: number)
	camera.CFrame *= oldOffset:Inverse()
	local offsetPanPerFrame = math.rad(360)*dt
	if UIS:IsKeyDown(Enum.KeyCode.Q) then
		angleStorage = math.min(angleStorage + offsetPanPerFrame, math.rad(180))
	else
		angleStorage = math.max(angleStorage - offsetPanPerFrame, 0)
	end
	offset = CFrame.fromEulerAnglesYXZ(0,angleStorage,0)
	camera.CFrame *= offset
	oldOffset = offset
end

hopefully i didn’t leave anything out of that. i tried to only keep the relevant bits of my camera script

edit:
If you want the player to still walk forward when looking behind, do this:

Comment out line 673 in BaseCamera:

--CameraUtils.setRotationTypeOverride(Enum.RotationType.CameraRelative)

You’re then going to have to edit your camera script so that the player’s rotation is manually set to the camera’s rotation (make sure to add this ABOVE any code relating to the camera rotation offset). This makes it so that looking behind you will still have the character facing forward.

Then, you’ll have to override the default movement code to use the HumanoidRootPart’s orientation instead of the camera’s:

--put this in StarterPlayerScripts
local playerScripts = script.Parent
local playerModule = require(playerScripts:WaitForChild("PlayerModule"))
local controls = playerModule:GetControls()

local character = game:GetService("Players").LocalPlayer.Character or game:GetService("Players").LocalPlayer.CharacterAdded:Wait()
local HRP = character:WaitForChild("HumanoidRootPart")

controls.moveFunction = function(player : Player, direction : Vector3, relative : boolean)
	local move = HRP.CFrame:VectorToObjectSpace(direction)
	player.Move(player, Vector3.new(move.X,move.Y,move.Z), true)
end
1 Like