Ok I figured out the answer, so I’ll answer my own question.
After you press play, you can find under PlayerScripts/PlayerModule/CameraModule/BaseCamera the following piece of code:
if cameraSubject:IsA("Humanoid") then
local humanoid = cameraSubject
local humanoidIsDead = humanoid:GetState() == Enum.HumanoidStateType.Dead
if VRService.VREnabled and humanoidIsDead and humanoid == self.lastSubject then
result = self.lastSubjectCFrame
else
local bodyPartToFollow = humanoid.RootPart
-- If the humanoid is dead, prefer their head part as a follow target, if it exists
if humanoidIsDead then
if humanoid.Parent and humanoid.Parent:IsA("Model") then
bodyPartToFollow = humanoid.Parent:FindFirstChild("Head") or bodyPartToFollow
end
end
if bodyPartToFollow and bodyPartToFollow:IsA("BasePart") then
local heightOffset
if humanoid.RigType == Enum.HumanoidRigType.R15 then
if humanoid.AutomaticScalingEnabled then
heightOffset = R15_HEAD_OFFSET
local rootPart = humanoid.RootPart
if bodyPartToFollow == rootPart then
local rootPartSizeOffset = (rootPart.Size.Y - HUMANOID_ROOT_PART_SIZE.Y)/2
heightOffset = heightOffset + Vector3.new(0, rootPartSizeOffset, 0)
end
else
heightOffset = R15_HEAD_OFFSET_NO_SCALING
end
else
heightOffset = HEAD_OFFSET
end
if humanoidIsDead then
heightOffset = ZERO_VECTOR3
end
result = bodyPartToFollow.CFrame*CFrame.new(heightOffset + humanoid.CameraOffset)
end
end
The relevant bit comes down to:
heightOffset = R15_HEAD_OFFSET
local rootPart = humanoid.RootPart
if bodyPartToFollow == rootPart then
local rootPartSizeOffset = (rootPart.Size.Y - HUMANOID_ROOT_PART_SIZE.Y)/2
heightOffset = heightOffset + Vector3.new(0, rootPartSizeOffset, 0)
end
The part to follow (when the CameraSubject is humanoid) is the rootpart, but then an offset is applied. In my case, with R15 characters and automatic scaling and such, the above calculation gives the heightOffset. The final CFrame is then made of the rootpart’s CFrame + heightOffset (plus humanoid.CameraOffset, but it’s default 0.0.0).
These constants are hardcoded at the start of the script:
local R15_HEAD_OFFSET = Vector3.new(0, 1.5, 0)
local HUMANOID_ROOT_PART_SIZE = Vector3.new(2, 2, 1)
So in the end, I got:
local heightOffset = Vector3.new(0, rootPart.Size.Y/2 +.5, 0)
local targetPos = rootPart.CFrame.p + heightOffset
And it seems to work fine, at least for my character! See it in action here:
https://www.roblox.com/games/6691045926/
Please let me know of anything that might go wrong, or if anything didn’t work!