Hello developers. I currently in the midst of making a wizard roleplaying type of game, and for the game, I and (by far mostly) NeonD00m have made a camera script. (Note: Me and NeonD00m are not affiliated elsewhere, he helped me with the third person camera system and not other parts of the game).
Anyways, the system works pretty great in terms of functionality, it’s toggle-able, mobile supported (working on the Xbox support), collisions, and the character moves with the camera. But there is one major issue, a bug, that occurs when you have the camera angle either on the max or min of the Y axis. Allow me to explain:
In order for the camera not flip out or being able to look backwards there is a limit on how far up and down you can look with your camera. To illustrate:
The red outline shows the area of how high and low you can adjust your camera. This can be adjusted using the mouse, or touch on the phone.
You can only move the camera on the Y axis (semi freely) without the character cha
ging direction too. Check out the videos below to get what I mean:
As you can see the camera kind of gets stuck in that position when reaching Y axis limits. It’s a little hard to explain, so your best bet is probably to download the Roblox Studio file linked below, and check it out.
This is how the scripts ancestry is:
So, the code is here below: (main camera script)
------------------------------------------------------------------
-- Scripted by NeonD00m, special thanks to oxazolone and Spathi --
------------------------------------------------------------------
-- SETTINGS
local faceInCameraDirection = true
local primaryOffset = CFrame.new(1.75, 1.75, 0)--affects raycast starting point
local mainOffset = CFrame.new(0, 0, 5)--affects camera end point
local senseX = 1 --sense gets divided by 100 for some devices
local senseY = 1
-- SERVICES/IMPORTANT STUFF
local Input = game:GetService("UserInputService")
Input.MouseBehavior = Enum.MouseBehavior.LockCenter
local cam = workspace.CurrentCamera
cam.CameraType = Enum.CameraType.Scriptable
-- INSTANCES
local plr = game.Players.LocalPlayer
local mouse = plr:GetMouse()
local char = plr.Character or plr.CharacterAdded:Wait()
local humanoid = char:WaitForChild("Humanoid")
local camroot = char:WaitForChild("HumanoidRootPart")
local moreInput = require(script:WaitForChild("moreInput"))
-- VARIABLES
local roll = 0
local lastCFrame = CFrame.new()
local lX = 0
local lY = 0
local newTouch
local deltaToSubtract = Vector3.new()
local check = function(cX, cY)
local newX = cX
local checked = false
if newX > math.rad(22.5) then
newX = math.rad(22.5)
checked = true
elseif newX < math.rad(-45) then
newX = math.rad(-45)
checked = true
end
if faceInCameraDirection then
print("Testing")
return CFrame.fromEulerAnglesXYZ(newX, 0, 0), checked --error may be here? Forcing the other values to 0, and only account for the newX variable. Maybe?
else
return CFrame.fromEulerAnglesXYZ(0, cY or 0, 0) * CFrame.fromEulerAnglesXYZ(newX, 0, 0), checked
end
end
-- thanks oxazolone for fixing this \/
local function rayCheck(startPoint, endPoint, rotation)
local ray = Ray.new(startPoint.Position, (endPoint.Position-startPoint.Position).unit*5)
local dontCare, hit = workspace:FindPartOnRay(ray, char)
if hit == nil or (startPoint.Position - hit).Magnitude > (startPoint.Position - endPoint.Position).Magnitude then
return endPoint
else
return CFrame.new(hit, startPoint.Position)
end
end
--convert vector3's of input to vector2's
local function convert(v3)
return Vector2.new(v3.X, v3.Y)
end
local function getDelta()
if Input.MouseEnabled then
return Input:GetMouseDelta() * -1 / 100
elseif Input.TouchEnabled then
local touch, start = moreInput:GetLastTouch()
if newTouch ~= touch then
deltaToSubtract = start
newTouch = touch
print("diff touch")
else
print("same touch")
end
if touch and touch.Position - deltaToSubtract ~= Vector3.new(0, 0, 0) then
local lastDTS = deltaToSubtract
deltaToSubtract = touch.Position
return convert(touch.Position - lastDTS) * -1 / 100
else
return Vector2.new(0, 0)
end
elseif Input.GamepadEnabled then
local stick = moreInput:GetLastStickInput()
if stick and stick.Delta ~= Vector3.new(0, 0, 0) then
return convert(stick.Delta) / 100
else
return Vector2.new(0, 0)
end
else
return Vector2.new(0, 0)
end
end
local function RenderCamera()
cam.CameraType = Enum.CameraType.Scriptable
Input.MouseBehavior = Enum.MouseBehavior.LockCenter
local inputDelta = getDelta()
print("delta: " .. tostring(inputDelta))
local cX = senseY * inputDelta.Y + lX
local cY = senseX * inputDelta.X + lY
local newRootCF = CFrame.new(camroot.Position) * CFrame.fromEulerAnglesXYZ(0, cY, 0)
if faceInCameraDirection then
char.Humanoid.AutoRotate = false
camroot.CFrame = newRootCF
else
char.Humanoid.AutoRotate = true
end
local headPos = faceInCameraDirection and camroot.CFrame or camroot.CFrame * primaryOffset
headPos = faceInCameraDirection and headPos or CFrame.new(headPos.Position)
if math.abs(inputDelta.X) > 0 or math.abs(inputDelta.Y) > 0 then
local deltaCF, checked
if faceInCameraDirection then
deltaCF, checked = check(cX)
else
deltaCF, checked = check(cX, cY)
end
local startPoint = faceInCameraDirection and headPos * primaryOffset * deltaCF or headPos * deltaCF
local endPoint = startPoint * mainOffset
local endCFrame = rayCheck(startPoint, endPoint, deltaCF)
cam.CFrame = endCFrame
lastCFrame = deltaCF
if not checked then
lX = cX
lY = cY
end
else
local startPoint = faceInCameraDirection and headPos * primaryOffset * lastCFrame or headPos * lastCFrame
local endPoint = startPoint * mainOffset
local endCFrame = rayCheck(startPoint, endPoint, lastCFrame)
cam.CFrame = endCFrame
end
end
game:GetService("RunService"):BindToRenderStep("myCamera", Enum.RenderPriority.Camera.Value + 1, RenderCamera)
And the module handling input:
-----------------
-- BY NEOND00M --
-----------------
local module = {}
local input = game:GetService("UserInputService")
local lastStickInput
local lT
local lastTouch
local lTStart
local lastTouchStart
-- main functioning
input.TouchStarted:Connect(function(touch, gameEvent)
print(touch.Position.X .. "|" .. workspace.CurrentCamera.ViewportSize.X/2)
if not gameEvent and touch.Position.X >= workspace.CurrentCamera.ViewportSize.X/2 and touch.Position.Y <= (workspace.CurrentCamera.ViewportSize.Y-workspace.CurrentCamera.ViewportSize.Y/3) then
lT = touch
lTStart = touch.Position
end
end)
input.TouchMoved:Connect(function(touch, gameEvent)
if lT == touch and touch.Delta ~= Vector3.new() and not gameEvent then
lastTouch = touch
lastTouchStart = lTStart
end
end)
input.TouchEnded:Connect(function(touch, gameEvent)
if lastTouch == touch then
lastTouch = nil
end
end)
input.InputBegan:Connect(function(inputObject, gameEvent)
if inputObject.KeyCode == Enum.KeyCode.Thumbstick2 and not gameEvent then
if inputObject.Delta ~= Vector3.new() then
lastStickInput = inputObject
end
end
end)
input.InputEnded:Connect(function(inputObject, gameEvent)
if inputObject == lastStickInput then
lastStickInput = nil
end
end)
-- public functions
function module:GetLastTouch()
return lastTouch, lastTouchStart
end
function module:GetLastStickMovement()
return lastStickInput
end
return module
I made a comment on the code (the main camera code) of where I might believe the bug is. I might be wrong, I might be right. But I’d love to get your opinions on this and I would really appreciate it if you guys took the time to look at it the code - it really helps me a whole bunch!
But what I might think:
The part where it checks if the player has reached the maximum height or minimum height, it forces the player back to the max height limit - so if you try to go higher/or lower than the limit it will bring you back to the max/min. When doing that, it only accounts for the Y value, and not the X and Z value, hence, if you go a little upwards whilst moving to the side with the camera, it will force you 1. Back to the max height/min height and also back to the 0,0 position of the X and Z value, so there has to be some way to only account for the height, and then ignore or account with the side movement. I hope you get what I meant, hard to get this on paper.
As I said, I recommend test the code for yourself in studio, feel free to download:
ThirdPersonCameraBug.rbxl (27.2 KB)
If you need more info, please reply and I’ll answer! Thank you so much.