I am trying to add head bobbing that mimics human movement to my cutscene as part of a horror game. How would I go about doing this?
Example:
Current cutscene code:
local function MoveCamera(StartPart, EndPart, Duration, EasingStyle, EasingDirection)
Camera.CameraType = Enum.CameraType.Scriptable
Camera.CFrame = StartPart.CFrame
local Cutscene = TweenService:Create(Camera, TweenInfo.new(Duration, EasingStyle, EasingDirection), {CFrame = EndPart.CFrame})
Cutscene:Play()
wait(Duration)
end
local function Cutscene()
local CameraNodes = game.ReplicatedStorage:WaitForChild("CameraNodes"):GetChildren()
table.sort(CameraNodes, function(a, b)
return a.Name < b.Name
end)
MoveCamera(CameraNodes[1], CameraNodes[2], 10, Enum.EasingStyle.Quad, Enum.EasingDirection.Out)
wait(3)
MoveCamera(CameraNodes[3], CameraNodes[4], 10, Enum.EasingStyle.Quad, Enum.EasingDirection.Out)
MoveCamera(CameraNodes[5], CameraNodes[6], 10, Enum.EasingStyle.Quad, Enum.EasingDirection.Out)
MoveCamera(CameraNodes[6], CameraNodes[7], 10, Enum.EasingStyle.Quad, Enum.EasingDirection.Out)
for i = 7, 14 do
local speed = 3
local startCam = CameraNodes[i]
local ii = i + 1
local endCam = CameraNodes[ii]
local startPosition = startCam:GetPivot().Position
local endPosition = endCam.Position
local duration = (startPosition - endPosition).Magnitude / speed
MoveCamera(startCam, endCam, duration, Enum.EasingStyle.Linear, Enum.EasingDirection.Out)
if CameraNodes[i].Name == "CameraAN" then
break
end
end
Also I know the code is awful I’m using a modified version of a script I made when I was 13 because I cannot be asked to rewrite from scratch.
1 Like
Your tween is doing a long sweeping movement from one position to the next. As it’s targeting the CFrame, and CFrame includes the rotational element of the camera, I think it would be quite difficult to implement varying roll without spamming tweens for each movement of the camera.
There may be other tricks that you can do as I’ve not messed around with the camera in quite a while, but I would tween an invisible part or a CFrame value between the two coordinates to get the base CFrame (similar to what you already have), then in a RenderStepped bind I would set the camera’s CFrame to the tweening CFrame and multiply by a rotation value defined using some form of math.sin() function against time.
I’m sure there’s easier ways to do it, but I’m not sure.
1 Like
I would rewrite from scratch, this code is a mess, it uses tweening instead of lerping, which makes it less responsive, if you rewrite with your current knowledge it would probably be better.
I created a very basic example of what I was trying to explain:
local Player = game:GetService('Players').LocalPlayer;
local Camera = workspace.CurrentCamera;
repeat wait(); until Player.Character;
Camera.CameraType = Enum.CameraType.Scriptable;
local TweeningPart = workspace.CFramePart;
---
local Duration = 8;
local EasingStyle = Enum.EasingStyle.Quad;
local EasingDirection = Enum.EasingDirection.Out;
---
local StartPart = workspace.Start;
local EndPart = workspace.End;
function runCutscene()
TweeningPart.CFrame = StartPart.CFrame;
Camera.CFrame = TweeningPart.CFrame;
local CutsceneTween = game:GetService('TweenService'):Create(TweeningPart, TweenInfo.new(Duration, EasingStyle, EasingDirection), {CFrame = EndPart.CFrame});
local Run = game:GetService('RunService').RenderStepped:Connect(function()
Camera.CFrame = TweeningPart.CFrame *CFrame.Angles(0, math.pi, 0.05*math.sin(2*tick()));
end);
CutsceneTween:Play();
CutsceneTween.Completed:Connect(function()
Run:Disconnect();
end);
end
runCutscene();
I’ve used a part here to demonstrate, but you could lerp
a CFrame or use a CFrame value as an alternative.
I’ve done some research on lerping this is the code I’ve got so far, the first camera movement is fine but every other camera movement happens instantaneously, when it should last the duration which I have left as 10 for testing. I know its probably a silly mistake but I haven’t coded in lua in ages so am a bit lost.
local RunService = game:GetService("RunService")
local Camera = game.Workspace.CurrentCamera
Camera.CameraType = Enum.CameraType.Scriptable
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local StartVehicleMovementEvent = ReplicatedStorage:WaitForChild("StartVehicleMovement")
local CameraNodes = game.ReplicatedStorage:WaitForChild("CameraNodes"):GetChildren()
table.sort(CameraNodes, function(a, b)
return a.Name < b.Name
end)
local function MoveCamera(StartPart, TargetPart, Duration)
local startCFrame = StartPart.CFrame
local targetCFrame = TargetPart.CFrame
local runningTime = 0
local lerp
lerp = RunService.Heartbeat:Connect(function(deltaTime)
runningTime += deltaTime
local alpha = runningTime / Duration
Camera.CFrame = startCFrame:Lerp(targetCFrame, alpha)
if alpha >= 1 then
lerp:Disconnect()
end
end)
end
local function Cutscene()
MoveCamera(CameraNodes[1], CameraNodes[2], 10)
MoveCamera(CameraNodes[3], CameraNodes[4], 10)
for i = 5, 14 do
local startCam = CameraNodes[i]
local endCam = CameraNodes[i+1]
MoveCamera(startCam, endCam, 10)
task.wait()
end
end
I think you forget to set the running time back to 0 when everything is done.
The cleanup should look something like this.
if alpha >= 1 then
lerp:Disconnect()
runningTime = 0
end
Doing that just freezes the camera after the first movement.
I managed to fix the issue
local RunService = game:GetService("RunService")
local Camera = game.Workspace.CurrentCamera
Camera.CameraType = Enum.CameraType.Scriptable
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local StartVehicleMovementEvent = ReplicatedStorage:WaitForChild("StartVehicleMovement")
local CameraNodes = game.ReplicatedStorage:WaitForChild("CameraNodes"):GetChildren()
table.sort(CameraNodes, function(a, b)
return a.Name < b.Name
end)
local function MoveCamera(StartPart, TargetPart, Duration)
local startCFrame = StartPart.CFrame
local targetCFrame = TargetPart.CFrame
local runningTime = 0
local alpha = 0
Camera.CFrame = startCFrame
local lerp
local completed = false
lerp = RunService.Heartbeat:Connect(function(deltaTime)
runningTime += deltaTime
alpha = runningTime / Duration
Camera.CFrame = startCFrame:Lerp(targetCFrame, alpha)
if alpha >= 1 then
lerp:Disconnect()
completed = true
end
end)
while not completed do
task.wait()
end
end
local function Cutscene()
MoveCamera(CameraNodes[1], CameraNodes[2], 10)
MoveCamera(CameraNodes[3], CameraNodes[4], 10)
for i = 5, 14 do
local startCam = CameraNodes[i]
local endCam = CameraNodes[i+1]
MoveCamera(startCam, endCam, 10)
end
end
GUI = script.Parent.Parent
Confirm = script.Parent
GUI.Visible = true
Confirm.MouseButton1Click:connect(function()
StartVehicleMovementEvent:FireServer()
Confirm.Visible = false
task.wait(2)
GUI.Visible = false
Cutscene()
end)
1 Like
Aight Ima put in some annotations so I don’t forget what any of this does then I’ll try work out the head bobbing system and calibrating the speed of each camera movement.
This is actually worthy of getting its own studio extension I can’t lie
-- Roblox Service Imports
local RunService = game:GetService("RunService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
-- Replicated Storage Imports
local StartVehicleMovementEvent = ReplicatedStorage:WaitForChild("StartVehicleMovement")
local CameraNodes = game.ReplicatedStorage:WaitForChild("CameraNodes"):GetChildren()
-- Detecting Camera and Enabling Scripting
local Camera = game.Workspace.CurrentCamera
Camera.CameraType = Enum.CameraType.Scriptable
-- Sorting Camera Nodes Into Correct Order
table.sort(CameraNodes, function(a, b)
return a.Name < b.Name
end)
-- Camera Movement Function
local function MoveCamera(StartPart, TargetPart, speed)
-- Set up variables for lerp
local startCFrame = StartPart.CFrame
local targetCFrame = TargetPart.CFrame
local startPosition = StartPart.Position
local endPosition = TargetPart.Position
local duration = (startPosition - endPosition).Magnitude / speed -- Duration for each segment
local runningTime = 0
-- Initial Camera Position Setup
Camera.CFrame = startCFrame
local lerp
-- Camera Movement
local completed = false
lerp = RunService.Heartbeat:Connect(function(deltaTime)
-- Movement Time Calculations
runningTime += deltaTime
local alpha = runningTime / duration
-- Camera Movement
Camera.CFrame = startCFrame:Lerp(targetCFrame, alpha)
-- Completed Movement Check
if alpha >= 1 then
lerp:Disconnect()
completed = true
end
end)
-- Wait Until Movement Is Completed
while not completed do
task.wait()
end
end
-- Cutscene Editor
local function Cutscene()
-- Seperate Movements
MoveCamera(CameraNodes[1], CameraNodes[2], 1.75)
MoveCamera(CameraNodes[3], CameraNodes[4], 1.75)
-- Combined Movements
for i = 5, 14 do
local startCam = CameraNodes[i]
local endCam = CameraNodes[i+1]
MoveCamera(startCam, endCam, 0.5)
end
end
-- GUI Variables
GUI = script.Parent.Parent
Confirm = script.Parent
GUI.Visible = true
-- Run When Clicked
Confirm.MouseButton1Click:Connect(function()
StartVehicleMovementEvent:FireServer()
Confirm.Visible = false
task.wait(2)
GUI.Visible = false
Cutscene()
end)
Made A template if anyone wants it, now I just need to add headbobbing which was the entire purpose of this forum post.
-- I recommend storing this script in a StarterGUI
-- Create a folder called "CameraNodes" in Replicated Storage
-- Store all camera nodes in this folder
-- Use the naming scheme CameraNodeAAA, CameraNodeAAB, [...etc], CamerNodeABA, [etc]
--Using this naming scheme means Roblox can accurately sort the nodes, and gives you 17,576 possible cameras
-- If you ever require more cameras add an extra letter to the end of the naming scheme, this will give you 456,976 possible cameras
-- In the Cutscene editor the letters will be translated into numbers
-- On line 74 you will find the cutscene editor
-- It has 2 examples of separated camera movements
-- And an example of combined camera movements
-- Call the Cutscene with Cutscene()
-- Any calls to the cutscene must be at the bottom of this script
-- This can be put in a RemoteEvent e.g.
-- StartCutscene.OnServerEvent:Connect(Cutscene)
-- Or a GUI click detector e.g.
-- Confirm.MouseButton1Click:Connect(Cutscene)
-- Roblox Service Imports
local RunService = game:GetService("RunService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
-- Replicated Storage Imports
local StartVehicleMovementEvent = ReplicatedStorage:WaitForChild("StartVehicleMovement")
local CameraNodes = game.ReplicatedStorage:WaitForChild("CameraNodes"):GetChildren()
-- Detecting Camera and Enabling Scripting
local Camera = game.Workspace.CurrentCamera
Camera.CameraType = Enum.CameraType.Scriptable
-- Sorting Camera Nodes Into Correct Order
table.sort(CameraNodes, function(a, b)
return a.Name < b.Name
end)
-- Camera Movement Function
local function MoveCamera(StartPart, TargetPart, speed)
-- Set up variables for lerp
local startCFrame = StartPart.CFrame
local targetCFrame = TargetPart.CFrame
local startPosition = StartPart.Position
local endPosition = TargetPart.Position
local duration = (startPosition - endPosition).Magnitude / speed -- Duration for each segment
local runningTime = 0
-- Initial Camera Position Setup
Camera.CFrame = startCFrame
local lerp
-- Camera Movement
local completed = false
lerp = RunService.Heartbeat:Connect(function(deltaTime)
-- Movement Time Calculations
runningTime += deltaTime
local alpha = runningTime / duration
-- Camera Movement
Camera.CFrame = startCFrame:Lerp(targetCFrame, alpha)
-- Completed Movement Check
if alpha >= 1 then
lerp:Disconnect()
completed = true
end
end)
-- Wait Until Movement Is Completed
while not completed do
task.wait()
end
end
-- Cutscene Editor
local function Cutscene()
-- Seperate Movements
-- First Camera, Second Camera, Speed(Highly Sensitive Use Decimals)
MoveCamera(CameraNodes[1], CameraNodes[2], 1.75)
MoveCamera(CameraNodes[3], CameraNodes[4], 1.75)
-- Combined Movements
local combinedmovementspeed = 0.5
-- First Camera, Second To Last Camera
for i = 5, 14 do
local startCam = CameraNodes[i]
local endCam = CameraNodes[i+1]
MoveCamera(startCam, endCam, combinedmovementspeed)
end
end
-- Call Cutscene() after this line
-- A script by OliAPerson