I’m trying to make a script that leans the character left or right and tilts their camera a little bit based off of which key they press (E or Q) but I can’t figure out how to tween the camera CFrame for the tilt smoothly.
I’ve tried looking for solutions on the forums but I can’t find anything that works or I can understand well enough to implement myself.
My Current Script
local character = script.Parent
local humanoid = character:WaitForChild("Humanoid")
local UIS = game:GetService("UserInputService")
local tweenService = game:GetService("TweenService")
local camera = game.Workspace.Camera
local tweenInfo = TweenInfo.new(
0.25,
Enum.EasingStyle.Linear
)
UIS.InputBegan:Connect(function(key, isTyping)
if key.KeyCode == Enum.KeyCode.E then
tweenService:Create(humanoid, tweenInfo, {CameraOffset = Vector3.new(2, 0, 0)}):Play()
game:GetService("RunService").RenderStepped:Connect(function()
camera.CFrame = camera.CFrame * CFrame.Angles(0, 0, -.075)
end)
end
if key.KeyCode == Enum.KeyCode.Q then
tweenService:Create(humanoid, tweenInfo, {CameraOffset = Vector3.new(-2, 0, 0)}):Play()
game:GetService("RunService").RenderStepped:Connect(function()
camera.CFrame = camera.CFrame * CFrame.Angles(0,0,.075)
end)
end
end)
UIS.InputEnded:Connect(function(key)
if key.KeyCode == Enum.KeyCode.E then
tweenService:Create(humanoid, tweenInfo, {CameraOffset = Vector3.new(0, 0, 0)}):Play()
game:GetService("RunService").RenderStepped:Connect(function()
camera.CFrame = camera.CFrame * CFrame.Angles(0, 0, .075)
end)
elseif key.KeyCode == Enum.KeyCode.Q then
tweenService:Create(humanoid, tweenInfo, {CameraOffset = Vector3.new(0, 0, 0)}):Play()
game:GetService("RunService").RenderStepped:Connect(function()
camera.CFrame = camera.CFrame * CFrame.Angles(0, 0, -.075)
end)
end
end)
Any help is greatly appreciated. Thanks so much in advance!
Hello @Secerne! I took your script and tried to solve your problem. Below, you can see what I did. I hope this is what you want.
local character = script.Parent
local humanoid = character:WaitForChild("Humanoid")
local UIS = game:GetService("UserInputService")
local tweenService = game:GetService("TweenService")
local camera = game.Workspace.CurrentCamera
local tweenInfo = TweenInfo.new(
0.25,
Enum.EasingStyle.Linear
)
local cameraTilt = 0
local function UpdateCameraTilt(angle)
local targetCFrame = camera.CFrame * CFrame.Angles(0, 0, angle)
tweenService:Create(camera, tweenInfo, {CFrame = targetCFrame}):Play()
end
UIS.InputBegan:Connect(function(input, gameProcessedEvent)
if gameProcessedEvent then
return
end
if input.KeyCode == Enum.KeyCode.E then
UpdateCameraTilt(-math.rad(10))
elseif input.KeyCode == Enum.KeyCode.Q then
UpdateCameraTilt(math.rad(10))
end
end)
UIS.InputEnded:Connect(function(input)
if input.KeyCode == Enum.KeyCode.E or input.KeyCode == Enum.KeyCode.Q then
UpdateCameraTilt(0)
end
end)
If you found this answer useful, do not forget to mark it as solved .
One thing that I noticed in your script is that you’re not cleaning up RenderStepped connections. This is a memory leak. To prevent this, you have to disconnect “unbound” connections. That seems like a lot of work, right? Not really. Connections get disconnected once the instance they are connected to gets destroyed. You can’t destroy the RunService, so you have to keep track of these kinds of connections. Store them somewhere and disconnect them with the :Disconnect function once you don’t need them anymore. I’m not too sure how Roblox’s garbage collector works, but I once saw it disconnect all UserInputService connections in a script when it got destroyed. This didn’t happen when I used a module script, so I’m not yet sure.
Back to the topic. Here’s the script. Place it into StarterPlayerScripts:
local UserInputService = game:GetService("UserInputService")
local TweenService = game:GetService("TweenService")
local RunService = game:GetService("RunService")
local Camera = workspace.CurrentCamera
local TiltLength = 0.5
local TiltAngle = 30
local TiltDirection = Enum.EasingDirection.Out
local TiltStyle = Enum.EasingStyle.Cubic
local LeftConnections = {}
local RightConnections = {}
local LeftOffset = CFrame.identity
local RightOffset = CFrame.identity
local function CleanupConnections(list)
for _, Connection in ipairs(list) do
Connection:Disconnect()
end
table.clear(list)
end
local function LerpOffset(offset, left, cleanup)
local OffsetCF = left and LeftOffset or RightOffset
local Elapsed = 0
local Connection
Connection = RunService.RenderStepped:Connect(function(deltaTime)
if Elapsed >= TiltLength and cleanup then
Connection:Disconnect()
end
local Normal = math.clamp(Elapsed / TiltLength, 0, 1)
Normal = TweenService:GetValue(Normal, TiltStyle, TiltDirection)
local CF = OffsetCF:Lerp(
offset,
Normal
)
if left then
LeftOffset = CF
else
RightOffset = CF
end
Camera.CFrame *= CF
Elapsed += deltaTime
end)
return Connection
end
UserInputService.InputBegan:Connect(function(input, gameProcessed)
if gameProcessed then
return
end
if input.KeyCode == Enum.KeyCode.Q then
CleanupConnections(LeftConnections)
local Connection = LerpOffset(
CFrame.Angles(0, 0, math.rad(TiltAngle)),
true
)
table.insert(LeftConnections, Connection)
elseif input.KeyCode == Enum.KeyCode.E then
CleanupConnections(RightConnections)
local Connection = LerpOffset(
CFrame.Angles(0, 0, math.rad(-TiltAngle)),
false
)
table.insert(RightConnections, Connection)
end
end)
UserInputService.InputEnded:Connect(function(input)
if input.KeyCode == Enum.KeyCode.Q then
CleanupConnections(LeftConnections)
local Connection = LerpOffset(
CFrame.identity,
true,
true
)
table.insert(LeftConnections, Connection)
elseif input.KeyCode == Enum.KeyCode.E then
CleanupConnections(RightConnections)
local Connection = LerpOffset(
CFrame.identity,
false,
true
)
table.insert(RightConnections, Connection)
end
end)
Here’s a demo:
There are 4 configuration variables. Tweak them to your liking.
I know this category is not for making entire scripts, but regardless. I wouldn’t say it looks the best, but it works well. Let me know if you have a question or want me to explain how it works.