I currently have a viewmodel with a custom camera animation for head bobbing and arms. The only problem is that when I set the camera cframe to the part’s frame it doesn’t apply the ability to look around as it’s constantly setting the cframe to the animated head bob part cframe. I was thinking of doing nested cameras, enabling the second camera while the first camera allows the ability to look around all while maintaining the ability to see the camera shakes, but unfortunately Roblox studio doesn’t allow this (might be wrong). Is there another way to do this? Or Do I have to take the long approach and use math? Let me know if you need to see the animation or the hierarchy of the viewmodel
Do you have a code sample? I don’t think any of your proposed solution is necessary
Id have to get home to get the code. If you encountered this, how would you approach this?
I would use the CameraOffset property of humanoids to bob the camera up and down. CameraOffset shifts the point your camera orbits your character, relative to the character’s CFrame.
This is my approach after messing around a bit with a procedural camera bob using math.sin()
It’s not very refined, but the general idea is there
viewmodel thing.rbxl (82.6 KB)
LocalScript
--[[ Variables ]]--
-- Services --
local CollectionService = game:GetService("CollectionService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")
-- Character --
local character = script.Parent
local rootPart = character:WaitForChild("HumanoidRootPart")
local humanoid = character:WaitForChild("Humanoid")
local characterJoints = {
leftShoulder = character:WaitForChild("Torso"):WaitForChild("Left Shoulder"),
rightShoulder = character:WaitForChild("Torso"):WaitForChild("Right Shoulder"),
}
-- Viewmodel --
local camera = workspace.CurrentCamera
local viewmodel = ReplicatedStorage.ViewmodelRig:Clone()
local viewmodelJoints = {
leftShoulder = viewmodel.Torso["Left Shoulder"],
rightShoulder = viewmodel.Torso["Right Shoulder"],
}
local currentBobTick = 0
local currentBobTransform = Vector3.zero
local currentTilt = CFrame.new()
-- Constants --
local CAMERA_TILT_ANGLE = math.rad(5)
local VIEWMODEL_CAMERA_OFFSET = CFrame.new(0, 0, -1)
local BOB_UP_AMPLITUDE = 0.5
local BOB_UP_FREQUENCY = 12
local BOB_RIGHT_AMPLITUDE = 0.5
local BOB_RIGHT_FREQUENCY = 6
--[[ Functions ]]--
local function animateViewmodelJoints() : ()
for jointName, joint in viewmodelJoints do
joint.Transform = characterJoints[jointName].Transform
joint.Part1.LocalTransparencyModifier = 0
end
end
local function getBobTransform(dt : number) : Vector3
local isBobbing = humanoid.MoveDirection.Magnitude > 0
if not isBobbing then
currentBobTick = 0
return currentBobTransform:Lerp(Vector3.zero, dt * 3)
end
currentBobTick += dt
local bobUp = math.sin(math.pi / 2 * currentBobTick * BOB_UP_FREQUENCY) * BOB_UP_AMPLITUDE
local bobRight = math.sin(math.pi / 2 * currentBobTick * BOB_RIGHT_FREQUENCY) * BOB_RIGHT_AMPLITUDE
local bob = Vector3.new(bobRight, bobUp, 0)
--// The way CameraOffset works is by shifting the point your camera orbits your character
--// This shift is relative to the character's CFrame
--// In first person this isn't an issue since your character and camera are always aligned, but
--// in third person, if your character isn't looking where your camera is, the bobbing will
--// go "into" the character
--// The following code simply ensures the camera offset is always relative to the camera
local look = (camera.CFrame.LookVector * Vector3.new(1, 0, 1)).Unit
local flatCameraCFrame = CFrame.lookAlong(Vector3.zero, look)
local v0 = flatCameraCFrame:VectorToObjectSpace(bob)
local v1 = rootPart.CFrame:VectorToObjectSpace(v0)
return currentBobTransform:Lerp(v1, 0.2)
end
local function getCameraTilt(dt : number) : CFrame
local dot = camera.CFrame.RightVector:Dot(humanoid.MoveDirection)
local angle = CAMERA_TILT_ANGLE * -dot
local tilt = CFrame.Angles(0, 0, angle)
return currentTilt:Lerp(tilt, dt * 10)
end
local function updateCamera(dt : number) : ()
local bobTransform = getBobTransform(dt)
currentBobTransform = bobTransform
humanoid.CameraOffset = bobTransform
local cameraCFrame = camera.CFrame
local cameraTilt = getCameraTilt(dt)
cameraCFrame *= cameraTilt
camera:PivotTo(cameraCFrame)
currentTilt = cameraTilt
local viewmodelCFrame = camera.CFrame * VIEWMODEL_CAMERA_OFFSET * CFrame.new(-bobTransform / 4)
viewmodel:PivotTo(viewmodelCFrame)
animateViewmodelJoints()
end
local function clearViewmodels() : ()
for _, v in CollectionService:GetTagged("Viewmodel") do
v:Destroy()
end
end
local function initViewmodel() : ()
viewmodel.Parent = camera
viewmodel.Torso.CanCollide = false
characterJoints.leftShoulder.Enabled = false
characterJoints.rightShoulder.Enabled = false
viewmodelJoints.leftShoulder.Enabled = true
viewmodelJoints.leftShoulder.Part1 = characterJoints.leftShoulder.Part1
viewmodelJoints.rightShoulder.Enabled = true
viewmodelJoints.rightShoulder.Part1 = characterJoints.rightShoulder.Part1
end
local function init() : ()
clearViewmodels()
initViewmodel()
RunService:BindToRenderStep("viewmodel_update", Enum.RenderPriority.Camera.Value + 1, updateCamera)
end
init()
In my case above, where I already have a specific animation for the camera bob rather than using math. Math right now is my last resort
I don’t think there’s any other efficient way of making camera bob animations other than with math (math.cos, math.sin & math.abs).
A popular solution would be something like this:
local RunService = game:GetService(“RunService”)
local Players = game:GetService(“Players”)
local player = Players.LocalPlayer
local character = player.Character or player.CharacterAdded:Wait()
local humanoid = character:WaitForChild(“Humanoid”)
RunService.RenderStepped:Connect(function()
if humanoid.MoveDirection.Magnitude > 0 then
local x = math.sin(tick() * 5) * 0.5
local y = math.abs(math.cos * 5) * 0.5
humanoid.CameraOffset = humanoid.CameraOffset:Lerp(Vector3.new(x, y, 0), 0.25)
else
humanoid.CameraOffset *= 0.85
end
end)
Sorry for any typos or if I messed up something, I’m on mobile so I don’t have any way of checking if the code works but it should be able to provide you with a general idea of how to implement a camera bob.
Hey maybe we should request a new feature for nested cameras (unity / unreal engine like cameras) Are you guys down?
If you’re dead-set on using the pre-computed animation, the way I’d do it is to load its KeyframeSequence using AnimationClipProvider:GetAnimationClipAsync() and then “step” the animation by each frame delta and then set the humanoid CameraOffset to the head’s transform at the stepped timestamp
Will definitely try this. Ill let you know if it works
Yeah I wasn’t able to do it. Might just resort to math. The only disadvantage to this is that it’s gonna take some time tweaking the configuration settings to sync the animation with the camera bobbing if you want it to be realistic.
If you want to mess around with it I can send over the viewmodel, but for now I might have to use math so I can keep progressing with my game. DM me if you found a workaround
viewmodel.rbxm (25.6 KB)
If you have a clip of what you want the bob to resemble I could try and write a procedural animation for it
I’ve managed to somewhat replicate this using math. If you couldn’t tell, there are micro tremors present in the rotations of the animation
The video can be seen here
It is hard to make out the tremors, so this is the best I was able to replicate. I’ve attached a demo file if you’d like to experiment with it. Rotating on more than just one axis is possible, and would be pretty simple to implement with a similar algorithm I used here
camera thing 2.rbxl (66.0 KB)
local function animate(t : number) : CFrame
local bob = math.sin(t * math.pi / 2 * bobFrequency) * bobHeight
local animateCFrame = CFrame.new(0, bob, 0)
local shake = math.sin(t * math.pi / 2 * shakeFrequency) * shakeDegree
animateCFrame *= CFrame.Angles(0, math.rad(shake), 0)
return animateCFrame
end
I have a similar implementation, just a bit more complicated and I seem to like it. What do you think?
Wow that sin curve on the black block is super cool, how did you do that?
That looks pretty good!
It’s a debugging function I wrote to visualize the camera effects, it just casts a ray after updating the camera offset and puts a dot on anything it hits
local function makeDot()
local dotPosition = Vector3.zero
local castOrigin = workspace.CurrentCamera.CFrame.Position
local castDirection = workspace.CurrentCamera.CFrame.LookVector * 15
local castResult = workspace:Raycast(castOrigin, castDirection)
if castResult then
dotPosition = castResult.Position
else
dotPosition = castOrigin + castDirection
end
local dot = Instance.new("Part")
dot.Size = Vector3.new(0.05, 0.05, 0.05)
dot.Position = dotPosition
dot.BrickColor = BrickColor.Red()
dot.Anchored = true
dot.CanCollide = false
dot.CanQuery = false
dot.Shape = Enum.PartType.Block
dot.Parent = workspace
Debris:AddItem(dot, 0.5)
end
lol if it hits anything but the part it’s gonna be a bunch of dots all around the map
This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.