Skip to content

Instantly share code, notes, and snippets.

@Aerodos12
Last active January 13, 2018 21:50
Show Gist options
  • Save Aerodos12/16232937b5ccf4021e59bbc88b3b22d0 to your computer and use it in GitHub Desktop.
Save Aerodos12/16232937b5ccf4021e59bbc88b3b22d0 to your computer and use it in GitHub Desktop.
CameraScheme, CameraService and CameraScript
local PlayersService = game:GetService('Players')
local RootCameraCreator = require(script.Parent)
local ZERO_VECTOR2 = Vector2.new(0, 0)
local XZ_VECTOR = Vector3.new(1,0,1)
local Vector2_new = Vector2.new
local CFrame_new = CFrame.new
local math_atan2 = math.atan2
local math_min = math.min
local function IsFinite(num)
return num == num and num ~= 1/0 and num ~= -1/0
end
-- May return NaN or inf or -inf
-- This is a way of finding the angle between the two vectors:
local function findAngleBetweenXZVectors(vec2, vec1)
return math_atan2(vec1.X*vec2.Z-vec1.Z*vec2.X, vec1.X*vec2.X + vec1.Z*vec2.Z)
end
local function CreateAttachCamera()
local module = RootCameraCreator()
local lastUpdate = tick()
function module:Update()
local now = tick()
local camera = workspace.CurrentCamera
if lastUpdate == nil or now - lastUpdate > 1 then
module:ResetCameraLook()
self.LastCameraTransform = nil
end
local subjectPosition = self:GetSubjectPosition()
if subjectPosition and camera then
local zoom = self:GetCameraZoom()
if zoom <= 0 then
zoom = 0.1
end
local humanoid = self:GetHumanoid()
if lastUpdate and humanoid and humanoid.Torso then
-- Cap out the delta to 0.1 so we don't get some crazy things when we re-resume from
local delta = math_min(0.1, now - lastUpdate)
local gamepadRotation = self:UpdateGamepad()
self.RotateInput = self.RotateInput + (gamepadRotation * delta)
local forwardVector = humanoid.Torso.CFrame.lookVector
local y = findAngleBetweenXZVectors(forwardVector, self:GetCameraLook())
if IsFinite(y) then
-- Preserve vertical rotation from user input
self.RotateInput = Vector2_new(y, self.RotateInput.Y)
end
end
local newLookVector = self:RotateCamera(self:GetCameraLook(), self.RotateInput)
self.RotateInput = ZERO_VECTOR2
camera.Focus = CFrame_new(subjectPosition)
local newCFrame = CFrame_new(subjectPosition - (zoom * newLookVector), subjectPosition)
camera.CFrame = newCFrame
self.LastCameraTransform = newCFrame
end
lastUpdate = now
end
return module
end
return CreateAttachCamera
local CameraScheme = {}
CameraScheme.__index = CameraScheme
CameraScheme.CAMERA = workspace.CurrentCamera
local humanoidCache = {}
local VRService = game:GetService("VRService")
local function clamp(low, high, num)
return (num > high and high or num < low and low or num)
end
local V3 = Vector3.new
local V2 = Vector2.new
local CF = CFrame.new
local ASIN = math.asin
local ATAN2 = math.atan2
local PI = math.pi
local FLOOR = math.floor
local HUGE = math.huge
local MAX = math.max
local CFANG = CFrame.Angles
local R15_HEAD_OFFSET = V3(0, 2.0, 0)
local LANDSCAPE_DEFAULT_ZOOM = 12.5
local PORTRAIT_DEFAULT_ZOOM = 25
local VR_LOW_INTENSITY_ROTATION = V2(math.rad(15), 0)
local VR_HIGH_INTENSITY_ROTATION = V2(math.rad(45), 0)
local VR_LOW_INTENSITY_REPEAT = 0.1
local VR_HIGH_INTENSITY_REPEAT = 0.4
local ZERO_VECTOR2 = V2(0, 0)
local ZERO_VECTOR3 = V3(0, 0, 0)
local STATE_DEAD = Enum.HumanoidStateType.Dead
local SEAT_OFFSET = V3(0,5,0)
local VR_SEAT_OFFSET = V3(0, 4, 0)
local HEAD_OFFSET = V3(0, 1.5, 0)
local VR_ANGLE = math.rad(15)
local MIN_Y = math.rad(-80)
local MAX_Y = math.rad(80)
local PortraitMode = false
local UIS = game:GetService("UserInputService")
local StarterGui = game:GetService("StarterGui")
function CameraScheme.new(...)
local cScheme = {}
local args = {...}
cScheme.R15HeadHeight = R15_HEAD_OFFSET
cScheme.ActivateValue = args[1] or 0.7
cScheme.CameraChangedConnection = nil
cScheme.GestureArea = nil
cScheme.DefaultZoom = LANDSCAPE_DEFAULT_ZOOM
cScheme.Enabled = false
cScheme.RotateInput = ZERO_VECTOR2
cScheme.ActiveGamepad = nil
cScheme.LastSubject = nil
cScheme.lastSubjectPosition = V3(0, 5, 0)
cScheme.isFirstPerson = false
cScheme.isRightMouseDown = false
cScheme.isMiddleMouseDown = false
cScheme.tweens = {}
cScheme.lastVRRotation = 0
cScheme.vrRotateKeyCooldown = {}
cScheme.vrRotationIntensityExists = true
cScheme.lastVrRotationCheck = 0
cScheme.isDynamicThumbstickEnabled = false
cScheme.Player = game.Players.LocalPlayer
cScheme.GSettings = UserSettings()
cScheme.GamepadPanningCamera = V2(0,0)
cScheme.cameraTranslationConstraints = Vector3.new(1, 1, 1)
cScheme.humanoidJumpOrigin = nil
cScheme.trackingHumanoid = nil
cScheme.cameraFrozen = false
cScheme.startPos = nil
cScheme.lastPos = nil
cScheme.panBeginLook = nil
cScheme.lastTapTime = nil
cScheme.fingerTouches = {}
cScheme.NumUnsunkTouches = 0
cScheme.inputStartPositions = {}
cScheme.inputStartTimes = {}
cScheme.touchWorkspaceEventEnabled = pcall(function() local test = UIS.TouchTapInWorld end)
cScheme.StartingDiff = nil
cScheme.pinchBeginZoom = nil
cScheme.ZoomEnabled = true
cScheme.PanEnabled = true
cScheme.KeyPanEnabled = true
cScheme.subjectStateChangedConn = nil
cScheme.cameraSubjectChangedConn = nil
cScheme.workspaceChangedConn = nil
cScheme.humanoidChildAddedConn = nil
cScheme.humanoidChildRemovedConn = nil
cScheme.onResetCameraLook = function() end
cScheme.getCameraZoom = function(cs)
if cs.currentZoom == nil then
local player = cScheme.Player
cs.currentZoom = player and clamp(player.CameraMinZoomDistance, player.CameraMaxZoomDistance, cs.DefaultZoom) or cs.DefaultZoom
end
return cs.currentZoom
end
cScheme.zoomCamera = function(cs,desiredZoom)
local player = cs.Player
if player then
if player.CameraMode == Enum.CameraMode.LockFirstPerson then
cs.currentZoom = 0
else
cs.currentZoom = clamp(player.CameraMinZoomDistance, player.CameraMaxZoomDistance, desiredZoom)
end
end
-- set mouse behavior
cs:UpdateMouseBehavior()
return cs:GetCameraZoom()
end
cScheme.zoomCameraBy = function(cs,zoomScale)
local zoom = cs:GetCameraActualZoom()
if zoom then
-- Can break into more steps to get more accurate integration
zoom = CameraScheme.rk4Integrator(zoom, zoomScale, 1)
cs:ZoomCamera(zoom)
end
return cs:GetCameraZoom()
end
cScheme.zoomCameraFixedBy = function(cs,zoomIncrement)
return cs:ZoomCamera(cs:GetCameraZoom() + zoomIncrement)
end
cScheme.OnUpdate = function(cs,...)
end
return setmetatable(cScheme,CameraScheme)
end
function CameraScheme:GetActivateValue()
return self.ActivateValue
end
function CameraScheme:IsPortraitMode()
return PortraitMode
end
local function findPlayerHumanoid(player)
local character = player and player.Character
if character then
local resultHumanoid = humanoidCache[player]
if resultHumanoid and resultHumanoid.Parent == character then
return resultHumanoid
else
humanoidCache[player] = nil -- Bust Old Cache
local humanoid = character:FindFirstChildOfClass("Humanoid")
if humanoid then
humanoidCache[player] = humanoid
end
return humanoid
end
end
end
function CameraScheme.positionIntersectsGuiObject(position, guiObject)
if position.X < guiObject.AbsolutePosition.X + guiObject.AbsoluteSize.X
and position.X > guiObject.AbsolutePosition.X
and position.Y < guiObject.AbsolutePosition.Y + guiObject.AbsoluteSize.Y
and position.Y > guiObject.AbsolutePosition.Y then
return true
end
return false
end
function CameraScheme:LayoutGestureArea()
if self.GestureArea then
if self:IsPortraitMode() then
self.GestureArea.Size = UDim2.new(1, 0, .6, 0)
self.GestureArea.Position = UDim2.new(0, 0, 0, 0)
else
self.GestureArea.Size = UDim2.new(1, 0, .5, -18)
self.GestureArea.Position = UDim2.new(0, 0, 0, 0)
end
end
end
function CameraScheme.RecalibrateCamera()
CameraScheme.CAMERA = workspace.CurrentCamera
end
function CameraScheme:CheckPortrait()
if UIS.TouchEnabled then
if self.CameraChangedConnection then
self.CameraChangedConnection:Disconnect()
self.CameraChangedConnection = nil
end
CameraScheme.RecalibrateCamera()
if CameraScheme.CAMERA then
local size = CameraScheme.CAMERA.ViewportSize
PortraitMode = size.X < size.Y
self:LayoutGestureArea()
self.DefaultZoom = PortraitMode and PORTRAIT_DEFAULT_ZOOM or LANDSCAPE_DEFAULT_ZOOM
self.CameraChangedConnection = CameraScheme.CAMERA:GetPropertyChangedSignal("ViewportSize"):Connect(function()
size = CameraScheme.CAMERA.ViewportSize
PortraitMode = size.X < size.Y
self:LayoutGestureArea()
self.DefaultZoom = PortraitMode and PORTRAIT_DEFAULT_ZOOM or LANDSCAPE_DEFAULT_ZOOM
end)
end
end
end
function CameraScheme:GetRotateAmountValue(vrRotationIntensity)
vrRotationIntensity = vrRotationIntensity or StarterGui:GetCore("VRRotationIntensity")
if vrRotationIntensity then
if vrRotationIntensity == "Low" then
return VR_LOW_INTENSITY_ROTATION
elseif vrRotationIntensity == "High" then
return VR_HIGH_INTENSITY_ROTATION
end
end
return ZERO_VECTOR2
end
function CameraScheme:GetRepeatDelayValue(vrRotationIntensity)
vrRotationIntensity = vrRotationIntensity or StarterGui:GetCore("VRRotationIntensity")
if vrRotationIntensity then
if vrRotationIntensity == "Low" then
return VR_LOW_INTENSITY_REPEAT
elseif vrRotationIntensity == "High" then
return VR_HIGH_INTENSITY_REPEAT
end
end
return 0
end
function CameraScheme:GetHumanoid()
return findPlayerHumanoid(self.Player)
end
function CameraScheme:GetRenderCFrame(part)
return part:GetRenderCFrame()
end
local function getHumanoidPartToFollow(humanoid, humanoidStateType)
if humanoidStateType == STATE_DEAD then
local character = humanoid.Parent
if character then
return character:FindFirstChild("Head") or humanoid.Torso
else
return humanoid.Torso
end
else
return humanoid.Torso
end
end
function CameraScheme:GetSubjectPosition()
local result = nil
local camera = CameraScheme.CAMERA
local cameraSubject = camera and camera.CameraSubject
if cameraSubject then
if cameraSubject:IsA('Humanoid') then
local humanoidStateType = cameraSubject:GetState()
if VRService.VREnabled and humanoidStateType == STATE_DEAD and cameraSubject == self.lastSubject then
result = self.lastSubjectPosition
else
local humanoidRootPart = getHumanoidPartToFollow(cameraSubject, humanoidStateType)
if humanoidRootPart and humanoidRootPart:IsA('BasePart') then
local subjectCFrame = self:GetRenderCFrame(humanoidRootPart)
local heightOffset = ZERO_VECTOR3
if humanoidStateType ~= STATE_DEAD then
heightOffset = cameraSubject.RigType == Enum.HumanoidRigType.R15 and self.R15HeadHeight or HEAD_OFFSET
end
result = subjectCFrame.p +
subjectCFrame:vectorToWorldSpace(heightOffset + cameraSubject.CameraOffset)
end
end
elseif cameraSubject:IsA('VehicleSeat') then
local subjectCFrame = self:GetRenderCFrame(cameraSubject)
local offset = SEAT_OFFSET
if VRService.VREnabled then
offset = VR_SEAT_OFFSET
end
result = subjectCFrame.p + subjectCFrame:vectorToWorldSpace(offset)
elseif cameraSubject:IsA('SkateboardPlatform') then
local subjectCFrame = self:GetRenderCFrame(cameraSubject)
result = subjectCFrame.p + SEAT_OFFSET
elseif cameraSubject:IsA('BasePart') then
local subjectCFrame = self:GetRenderCFrame(cameraSubject)
result = subjectCFrame.p
elseif cameraSubject:IsA('Model') then
result = cameraSubject:GetModelCFrame().p
end
end
self.lastSubject = cameraSubject
self.lastSubjectPosition = result
return result
end
function CameraScheme:ExtendResetCamera(func)
self.onResetCameraLook = func
end
function CameraScheme:ResetCameraLook()
self.onResetCameraLook()
end
function CameraScheme:GetCameraLook()
return CameraScheme.CAMERA and CameraScheme.CAMERA.CFrame.lookVector or V3(0,0,1)
end
function CameraScheme:ExtendGetCameraZoom(func)
self.getCameraZoom = func
end
function CameraScheme:GetCameraZoom()
return self.getCameraZoom(self)
end
function CameraScheme:GetCameraActualZoom()
local camera = CameraScheme.CAMERA
if camera then
return (camera.CFrame.p - camera.Focus.p).magnitude
end
end
function CameraScheme:GetCameraHeight()
if VRService.VREnabled and not self:IsInFirstPerson() then
local zoom = self:GetCameraZoom()
return math.sin(VR_ANGLE) * zoom
end
return 0
end
function CameraScheme:ViewSizeX()
local result = 1024
local camera = CameraScheme.CAMERA
if camera then
result = camera.ViewportSize.X
end
return result
end
function CameraScheme:ViewSizeY()
local result = 768
local camera = CameraScheme.CAMERA
if camera then
result = camera.ViewportSize.Y
end
return result
end
function CameraScheme:ScreenTranslationToAngle(translationVector)
local screenX = self:ViewSizeX()
local screenY = self:ViewSizeY()
local xTheta = (translationVector.x / screenX)
local yTheta = (translationVector.y / screenY)
return V2(xTheta, yTheta)
end
function CameraScheme:MouseTranslationToAngle(translationVector)
local xTheta = (translationVector.x / 1920)
local yTheta = (translationVector.y / 1200)
return V2(xTheta, yTheta)
end
function CameraScheme:RotateVector(startVector, xyRotateVector)
local startCFrame = CF(ZERO_VECTOR3, startVector)
local resultLookVector = (CFANG(0, -xyRotateVector.x, 0) * startCFrame * CFANG(-xyRotateVector.y,0,0)).lookVector
return resultLookVector, V2(xyRotateVector.x, xyRotateVector.y)
end
function CameraScheme:RotateCamera(startLook, xyRotateVector)
if VRService.VREnabled then
local yawRotatedVector, xyRotateVector = self:RotateVector(startLook, V2(xyRotateVector.x, 0))
return V3(yawRotatedVector.x, 0, yawRotatedVector.z).unit, xyRotateVector
else
local startVertical = ASIN(startLook.y)
local yTheta = clamp(-MAX_Y + startVertical, -MIN_Y + startVertical, xyRotateVector.y)
return self:RotateVector(startLook, V2(xyRotateVector.x, yTheta))
end
end
function CameraScheme:IsInFirstPerson()
return self.isFirstPerson
end
function CameraScheme:UpdateMouseBehavior()
-- first time transition to first person mode or shiftlock
if self.isFirstPerson then
pcall(function() self.GSettings.RotationType = Enum.RotationType.CameraRelative end)
if UIS.MouseBehavior ~= Enum.MouseBehavior.LockCenter then
UIS.MouseBehavior = Enum.MouseBehavior.LockCenter
end
else
pcall(function() self.GSettings.RotationType = Enum.RotationType.MovementRelative end)
if self.isRightMouseDown or self.isMiddleMouseDown then
UIS.MouseBehavior = Enum.MouseBehavior.LockCurrentPosition
else
UIS.MouseBehavior = Enum.MouseBehavior.Default
end
end
end
function CameraScheme:ExtendZoomCamera(func)
self.zoomCamera = func
end
function CameraScheme:ZoomCamera(desiredZoom)
return self.zoomCamera(self,desiredZoom)
end
function CameraScheme.rk4Integrator(position, velocity, t)
local direction = velocity < 0 and -1 or 1
local function acceleration(p, v)
local accel = direction * MAX(1, (p / 3.3) + 0.5)
return accel
end
local p1 = position
local v1 = velocity
local a1 = acceleration(p1, v1)
local p2 = p1 + v1 * (t / 2)
local v2 = v1 + a1 * (t / 2)
local a2 = acceleration(p2, v2)
local p3 = p1 + v2 * (t / 2)
local v3 = v1 + a2 * (t / 2)
local a3 = acceleration(p3, v3)
local p4 = p1 + v3 * t
local v4 = v1 + a3 * t
local a4 = acceleration(p4, v4)
local positionResult = position + (v1 + 2 * v2 + 2 * v3 + v4) * (t / 6)
local velocityResult = velocity + (a1 + 2 * a2 + 2 * a3 + a4) * (t / 6)
return positionResult, velocityResult
end
function CameraScheme:ExtendZoomCameraBy(func)
self.zoomCameraBy = func
end
function CameraScheme:ZoomCameraBy(zoomScale)
return self.zoomCameraBy(self,zoomScale)
end
function CameraScheme:ExtendZoomCamFixedBy(func)
self.zoomCameraFixedBy = func
end
function CameraScheme:ZoomCameraFixedBy(zoomIncrement)
return self.zoomCameraFixedBy(self,zoomIncrement)
end
function CameraScheme:ExtendUpdate(func)
self.OnUpdate = func
end
function CameraScheme:Update(...)
self.OnUpdate(self,...)
end
function CameraScheme:ApplyVRTransform()
if not VRService.VREnabled then
return
end
--we only want this to happen in first person VR
local player = self.Player
if not (player and player.Character
and player.Character:FindFirstChild("HumanoidRootPart")
and player.Character.HumanoidRootPart:FindFirstChild("RootJoint")) then
return
end
local camera = CameraScheme.CAMERA
local cameraSubject = camera.CameraSubject
local isInVehicle = cameraSubject and cameraSubject:IsA('VehicleSeat')
if self:IsInFirstPerson() and not isInVehicle then
local vrFrame = VRService:GetUserCFrame(Enum.UserCFrame.Head)
local vrRotation = vrFrame - vrFrame.p
local rootJoint = player.Character.HumanoidRootPart.RootJoint
rootJoint.C0 = CF(vrRotation:vectorToObjectSpace(vrFrame.p)) * CF(0, 0, 0, -1, 0, 0, 0, 0, 1, 0, 1, 0)
else
local rootJoint = player.Character.HumanoidRootPart.RootJoint
rootJoint.C0 = CF(0, 0, 0, -1, 0, 0, 0, 0, 1, 0, 1, 0)
end
end
function CameraScheme:ShouldUseVRRotation()
if not VRService.VREnabled then
return false
end
if not self.vrRotationIntensityExists and tick() - self.lastVrRotationCheck < 1 then return false end
local success, vrRotationIntensity = pcall(function() return StarterGui:GetCore("VRRotationIntensity") end)
self.vrRotationIntensityExists = success and vrRotationIntensity ~= nil
self.lastVrRotationCheck = tick()
return success and vrRotationIntensity ~= nil and vrRotationIntensity ~= "Smooth"
end
function CameraScheme:GetVRRotationInput()
local vrRotateSum = ZERO_VECTOR2
local vrRotationIntensity = StarterGui:GetCore("VRRotationIntensity")
local vrGamepadRotation = self.GamepadPanningCamera or ZERO_VECTOR2
local delayExpired = (tick() - self.lastVRRotation) >= self:GetRepeatDelayValue(vrRotationIntensity)
if math.abs(vrGamepadRotation.x) >= self:GetActivateValue() then
if (delayExpired or not self.vrRotateKeyCooldown[Enum.KeyCode.Thumbstick2]) then
local sign = 1
if vrGamepadRotation.x < 0 then
sign = -1
end
vrRotateSum = vrRotateSum + self:GetRotateAmountValue(vrRotationIntensity) * sign
self.vrRotateKeyCooldown[Enum.KeyCode.Thumbstick2] = true
end
elseif math.abs(vrGamepadRotation.x) < self:GetActivateValue() - 0.1 then
self.vrRotateKeyCooldown[Enum.KeyCode.Thumbstick2] = nil
end
if self.TurningLeft then
if delayExpired or not self.vrRotateKeyCooldown[Enum.KeyCode.Left] then
vrRotateSum = vrRotateSum - self:GetRotateAmountValue(vrRotationIntensity)
self.vrRotateKeyCooldown[Enum.KeyCode.Left] = true
end
else
self.vrRotateKeyCooldown[Enum.KeyCode.Left] = nil
end
if self.TurningRight then
if (delayExpired or not self.vrRotateKeyCooldown[Enum.KeyCode.Right]) then
vrRotateSum = vrRotateSum + self:GetRotateAmountValue(vrRotationIntensity)
self.vrRotateKeyCooldown[Enum.KeyCode.Right] = true
end
else
self.vrRotateKeyCooldown[Enum.KeyCode.Right] = nil
end
if vrRotateSum ~= ZERO_VECTOR2 then
self.lastVRRotation = tick()
end
return vrRotateSum
end
function CameraScheme:cancelCameraFreeze(keepConstraints)
if not keepConstraints then
self.cameraTranslationConstraints = V3(self.cameraTranslationConstraints.x, 1, self.cameraTranslationConstraints.z)
end
if self.cameraFrozen then
self.trackingHumanoid = nil
self.cameraFrozen = false
end
end
function CameraScheme:startCameraFreeze(subjectPosition, humanoidToTrack)
if not self.cameraFrozen then
self.humanoidJumpOrigin = subjectPosition
self.trackingHumanoid = humanoidToTrack
self.cameraTranslationConstraints = Vector3.new(self.cameraTranslationConstraints.x, 0, self.cameraTranslationConstraints.z)
self.cameraFrozen = true
end
end
function CameraScheme:rescaleCameraOffset(newScaleFactor)
self.R15HeadHeight = R15_HEAD_OFFSET*newScaleFactor
end
function CameraScheme:CalibrateHumanoid(hum)
if self.humanoidChildAddedConn then
self.humanoidChildAddedConn:disconnect()
self.humanoidChildAddedConn = nil
end
if self.humanoidChildRemovedConn then
self.humanoidChildRemovedConn:disconnect()
self.humanoidChildRemovedConn = nil
end
if self.heightScaleChangedConn then
self.heightScaleChangedConn:disconnect()
self.heightScaleChangedConn = nil
end
if self.trackingHumanoid ~= hum then
self:cancelCameraFreeze()
end
if hum and hum:IsA('Humanoid') then
self.humanoidChildAddedConn = hum.ChildAdded:connect(function(child)
if child.Name == "BodyHeightScale" and child:IsA("NumberValue") then
if self.heightScaleChangedConn then
self.heightScaleChangedConn:disconnect()
end
self.heightScaleChangedConn = child.Changed:connect(function(value)
self:rescaleCameraOffset(value)
end)
self:rescaleCameraOffset(child.Value)
end
end)
self.humanoidChildRemovedConn = hum.ChildRemoved:connect(function(child)
if child.Name == "BodyHeightScale" then
self:rescaleCameraOffset(1)
if self.heightScaleChangedConn then
self.heightScaleChangedConn:disconnect()
self.heightScaleChangedConn = nil
end
end
end)
for _, child in pairs(hum:GetChildren()) do
if child.Name == "BodyHeightScale" and child:IsA("NumberValue") then
if self.heightScaleChangedConn then
self.heightScaleChangedConn:disconnect()
end
self.heightScaleChangedConn = child.Changed:connect(function(value)
self:rescaleCameraOffset(value)
end)
self:rescaleCameraOffset(child.Value)
end
end
self.subjectStateChangedConn = hum.StateChanged:connect(function(oldState, newState)
if VRService.VREnabled and newState == Enum.HumanoidStateType.Jumping and not self:IsInFirstPerson() then
self:startCameraFreeze(self:GetSubjectPosition(), hum)
elseif newState ~= Enum.HumanoidStateType.Jumping and newState ~= Enum.HumanoidStateType.Freefall then
self:cancelCameraFreeze(true)
end
end)
end
end
function CameraScheme:GetVRFocus(subjectPosition, timeDelta)
local newFocus = nil
local camera = CameraScheme.CAMERA
local lastFocus = self.LastCameraFocus or subjectPosition
if not self.cameraFrozen then
self.cameraTranslationConstraints = V3(self.cameraTranslationConstraints.x, math.min(1, self.cameraTranslationConstraints.y + 0.42 * timeDelta), self.cameraTranslationConstraints.z)
end
if self.cameraFrozen and self.humanoidJumpOrigin and self.humanoidJumpOrigin.y > lastFocus.y then
newFocus = CF(V3(subjectPosition.x, math.min(self.humanoidJumpOrigin.y, lastFocus.y + 5 * timeDelta), subjectPosition.z))
else
newFocus = CF(V3(subjectPosition.x, lastFocus.y, subjectPosition.z):lerp(subjectPosition, self.cameraTranslationConstraints.y))
end
if self.cameraFrozen then
-- No longer in 3rd person
if self:IsInFirstPerson() then -- not VRService.VREnabled
self:cancelCameraFreeze()
end
-- This case you jumped off a cliff and want to keep your character in view
-- 0.5 is to fix floating point error when not jumping off cliffs
if self.humanoidJumpOrigin and subjectPosition.y < (self.humanoidJumpOrigin.y - 0.5) then
self:cancelCameraFreeze()
end
end
return newFocus
end
function CameraScheme:SetGestureArea(area)
self.GestureArea = area
end
function CameraScheme:RegisterTouch(input, processed)
--If isDynamicThumbstickEnabled, then only process TouchBegan event if it starts in GestureArea
if (not self.touchWorkspaceEventEnabled and not self.isDynamicThumbstickEnabled) or CameraScheme.positionIntersectsGuiObject(input.Position, self.GestureArea) then
self.fingerTouches[input] = processed
if not processed then
self.inputStartPositions[input] = input.Position
self.inputStartTimes[input] = tick()
self.NumUnsunkTouches = self.NumUnsunkTouches + 1
end
end
end
return CameraScheme
local RunService = game:GetService('RunService')
local UserInputService = game:GetService('UserInputService')
local PlayersService = game:GetService('Players')
local VRService = game:GetService("VRService")
local StarterPlayer = game:GetService('StarterPlayer')
local RootCamera = script:WaitForChild('RootCamera')
local AttachCamera = require(RootCamera:WaitForChild('AttachCamera'))()
local FixedCamera = require(RootCamera:WaitForChild('FixedCamera'))()
local ScriptableCamera = require(RootCamera:WaitForChild('ScriptableCamera'))()
local TrackCamera = require(RootCamera:WaitForChild('TrackCamera'))()
local WatchCamera = require(RootCamera:WaitForChild('WatchCamera'))()
local OrbitalCamera = require(RootCamera:WaitForChild('OrbitalCamera'))()
local ClassicCamera = require(RootCamera:WaitForChild('ClassicCamera'))()
local FollowCamera = require(RootCamera:WaitForChild('FollowCamera'))()
local PopperCam = require(script:WaitForChild('PopperCam'))
local Invisicam = require(script:WaitForChild('Invisicam'))
local TransparencyController = require(script:WaitForChild('TransparencyController'))()
local VRCamera = require(RootCamera:WaitForChild("VRCamera"))()
local GameSettings = UserSettings().GameSettings
local AllCamerasInLua = false
local success, msg = pcall(function()
AllCamerasInLua = UserSettings():IsUserFeatureEnabled("UserAllCamerasInLua")
end)
if not success then
print("Couldn't get feature UserAllCamerasInLua because:" , msg)
end
local FFlagUserNoCameraClickToMoveSuccess, FFlagUserNoCameraClickToMoveResult = pcall(function() return UserSettings():IsUserFeatureEnabled("UserNoCameraClickToMove") end)
local FFlagUserNoCameraClickToMove = FFlagUserNoCameraClickToMoveSuccess and FFlagUserNoCameraClickToMoveResult
local ClickToMove = FFlagUserNoCameraClickToMove and nil or require(script:WaitForChild('ClickToMove'))()
local isOrbitalCameraEnabled = pcall(function() local test = Enum.CameraType.Orbital end)
-- register what camera scripts we are using
do
local PlayerScripts = PlayersService.LocalPlayer:WaitForChild("PlayerScripts")
local canRegisterCameras = pcall(function() PlayerScripts:RegisterTouchCameraMovementMode(Enum.TouchCameraMovementMode.Default) end)
if canRegisterCameras then
PlayerScripts:RegisterTouchCameraMovementMode(Enum.TouchCameraMovementMode.Follow)
PlayerScripts:RegisterTouchCameraMovementMode(Enum.TouchCameraMovementMode.Classic)
PlayerScripts:RegisterComputerCameraMovementMode(Enum.ComputerCameraMovementMode.Default)
PlayerScripts:RegisterComputerCameraMovementMode(Enum.ComputerCameraMovementMode.Follow)
PlayerScripts:RegisterComputerCameraMovementMode(Enum.ComputerCameraMovementMode.Classic)
end
end
local CameraTypeEnumMap =
{
[Enum.CameraType.Attach] = AttachCamera;
[Enum.CameraType.Fixed] = FixedCamera;
[Enum.CameraType.Scriptable] = ScriptableCamera;
[Enum.CameraType.Track] = TrackCamera;
[Enum.CameraType.Watch] = WatchCamera;
[Enum.CameraType.Follow] = FollowCamera;
}
if isOrbitalCameraEnabled then
CameraTypeEnumMap[Enum.CameraType.Orbital] = OrbitalCamera;
end
local EnabledCamera = nil
local EnabledOcclusion = nil
local cameraSubjectChangedConn = nil
local cameraTypeChangedConn = nil
local renderSteppedConn = nil
local lastInputType = nil
local hasLastInput = false
local function IsTouch()
return UserInputService.TouchEnabled
end
local function shouldUsePlayerScriptsCamera()
local player = PlayersService.LocalPlayer
local currentCamera = workspace.CurrentCamera
if AllCamerasInLua then
return true
else
if player then
if currentCamera == nil or (currentCamera.CameraType == Enum.CameraType.Custom)
or (isOrbitalCameraEnabled and currentCamera.CameraType == Enum.CameraType.Orbital) then
return true
end
end
end
return false
end
local function isClickToMoveOn()
local usePlayerScripts = shouldUsePlayerScriptsCamera()
local player = PlayersService.LocalPlayer
if usePlayerScripts and player then
if (hasLastInput and lastInputType == Enum.UserInputType.Touch) or IsTouch() then -- Touch
if player.DevTouchMovementMode == Enum.DevTouchMovementMode.ClickToMove or
(player.DevTouchMovementMode == Enum.DevTouchMovementMode.UserChoice and GameSettings.TouchMovementMode == Enum.TouchMovementMode.ClickToMove) then
return true
end
else -- Computer
if player.DevComputerMovementMode == Enum.DevComputerMovementMode.ClickToMove or
(player.DevComputerMovementMode == Enum.DevComputerMovementMode.UserChoice and GameSettings.ComputerMovementMode == Enum.ComputerMovementMode.ClickToMove) then
return true
end
end
end
return false
end
local function getCurrentCameraMode()
local usePlayerScripts = shouldUsePlayerScriptsCamera()
local player = PlayersService.LocalPlayer
if usePlayerScripts and player then
if (hasLastInput and lastInputType == Enum.UserInputType.Touch) or IsTouch() then -- Touch (iPad, etc...)
if not FFlagUserNoCameraClickToMove and isClickToMoveOn() then
return Enum.DevTouchMovementMode.ClickToMove.Name
elseif player.DevTouchCameraMode == Enum.DevTouchCameraMovementMode.UserChoice then
local touchMovementMode = GameSettings.TouchCameraMovementMode
if touchMovementMode == Enum.TouchCameraMovementMode.Default then
return Enum.TouchCameraMovementMode.Follow.Name
end
return touchMovementMode.Name
else
return player.DevTouchCameraMode.Name
end
else -- Computer
if not FFlagUserNoCameraClickToMove and isClickToMoveOn() then
return Enum.DevComputerMovementMode.ClickToMove.Name
elseif player.DevComputerCameraMode == Enum.DevComputerCameraMovementMode.UserChoice then
local computerMovementMode = GameSettings.ComputerCameraMovementMode
if computerMovementMode == Enum.ComputerCameraMovementMode.Default then
return Enum.ComputerCameraMovementMode.Classic.Name
end
return computerMovementMode.Name
else
return player.DevComputerCameraMode.Name
end
end
end
end
local function getCameraOcclusionMode()
local usePlayerScripts = shouldUsePlayerScriptsCamera()
local player = PlayersService.LocalPlayer
if usePlayerScripts and player then
return player.DevCameraOcclusionMode
end
end
local function Update()
if EnabledCamera then
EnabledCamera:Update()
end
if EnabledOcclusion and not VRService.VREnabled then
EnabledOcclusion:Update(EnabledCamera)
end
if shouldUsePlayerScriptsCamera() then
TransparencyController:Update()
end
end
local function SetEnabledCamera(newCamera)
if EnabledCamera ~= newCamera then
if EnabledCamera then
EnabledCamera:SetEnabled(false)
end
EnabledCamera = newCamera
if EnabledCamera then
EnabledCamera:SetEnabled(true)
end
end
end
local function OnCameraMovementModeChange(newCameraMode)
if newCameraMode == Enum.DevComputerMovementMode.ClickToMove.Name then
if FFlagUserNoCameraClickToMove then
--No longer responding to ClickToMove here!
return
end
ClickToMove:Start()
SetEnabledCamera(nil)
TransparencyController:SetEnabled(true)
else
local currentCameraType = workspace.CurrentCamera and workspace.CurrentCamera.CameraType
if VRService.VREnabled and currentCameraType ~= Enum.CameraType.Scriptable then
SetEnabledCamera(VRCamera)
TransparencyController:SetEnabled(false)
elseif (currentCameraType == Enum.CameraType.Custom or not AllCamerasInLua) and newCameraMode == Enum.ComputerCameraMovementMode.Classic.Name then
SetEnabledCamera(ClassicCamera)
TransparencyController:SetEnabled(true)
elseif (currentCameraType == Enum.CameraType.Custom or not AllCamerasInLua) and newCameraMode == Enum.ComputerCameraMovementMode.Follow.Name then
SetEnabledCamera(FollowCamera)
TransparencyController:SetEnabled(true)
elseif (currentCameraType == Enum.CameraType.Custom or not AllCamerasInLua) and (isOrbitalCameraEnabled and (newCameraMode == Enum.ComputerCameraMovementMode.Orbital.Name)) then
SetEnabledCamera(OrbitalCamera)
TransparencyController:SetEnabled(true)
elseif AllCamerasInLua and CameraTypeEnumMap[currentCameraType] then
SetEnabledCamera(CameraTypeEnumMap[currentCameraType])
TransparencyController:SetEnabled(false)
else -- Our camera movement code was disabled by the developer
SetEnabledCamera(nil)
TransparencyController:SetEnabled(false)
end
ClickToMove:Stop()
end
local newOcclusionMode = getCameraOcclusionMode()
if EnabledOcclusion == Invisicam and newOcclusionMode ~= Enum.DevCameraOcclusionMode.Invisicam then
Invisicam:Cleanup()
end
-- PopperCam does not work with OrbitalCamera, as OrbitalCamera's distance can be fixed.
if newOcclusionMode == Enum.DevCameraOcclusionMode.Zoom and ( isOrbitalCameraEnabled and newCameraMode ~= Enum.ComputerCameraMovementMode.Orbital.Name ) then
EnabledOcclusion = PopperCam
elseif newOcclusionMode == Enum.DevCameraOcclusionMode.Invisicam then
EnabledOcclusion = Invisicam
else
EnabledOcclusion = false
end
end
local function OnCameraTypeChanged(newCameraType)
if newCameraType == Enum.CameraType.Scriptable then
if UserInputService.MouseBehavior == Enum.MouseBehavior.LockCenter then
UserInputService.MouseBehavior = Enum.MouseBehavior.Default
end
end
end
local function OnCameraSubjectChanged(newSubject)
TransparencyController:SetSubject(newSubject)
end
local function OnNewCamera()
OnCameraMovementModeChange(getCurrentCameraMode())
local currentCamera = workspace.CurrentCamera
if currentCamera then
if cameraSubjectChangedConn then
cameraSubjectChangedConn:disconnect()
end
if cameraTypeChangedConn then
cameraTypeChangedConn:disconnect()
end
cameraSubjectChangedConn = currentCamera:GetPropertyChangedSignal("CameraSubject"):connect(function()
OnCameraSubjectChanged(currentCamera.CameraSubject)
end)
cameraTypeChangedConn = currentCamera:GetPropertyChangedSignal("CameraType"):connect(function()
OnCameraMovementModeChange(getCurrentCameraMode())
OnCameraTypeChanged(currentCamera.CameraType)
end)
OnCameraSubjectChanged(currentCamera.CameraSubject)
OnCameraTypeChanged(currentCamera.CameraType)
end
end
local function OnPlayerAdded(player)
workspace.Changed:connect(function(prop)
if prop == 'CurrentCamera' then
OnNewCamera()
end
end)
player.Changed:connect(function(prop)
OnCameraMovementModeChange(getCurrentCameraMode())
end)
GameSettings.Changed:connect(function(prop)
OnCameraMovementModeChange(getCurrentCameraMode())
end)
RunService:BindToRenderStep("cameraRenderUpdate", Enum.RenderPriority.Camera.Value, Update)
OnNewCamera()
OnCameraMovementModeChange(getCurrentCameraMode())
end
do
while PlayersService.LocalPlayer == nil do PlayersService.PlayerAdded:wait() end
hasLastInput = pcall(function()
lastInputType = UserInputService:GetLastInputType()
UserInputService.LastInputTypeChanged:connect(function(newLastInputType)
lastInputType = newLastInputType
end)
end)
OnPlayerAdded(PlayersService.LocalPlayer)
end
local function OnVREnabled()
OnCameraMovementModeChange(getCurrentCameraMode())
end
VRService:GetPropertyChangedSignal("VREnabled"):connect(OnVREnabled)
local CameraService = {}
local CamModes = require(script.CamModes)
local RAD = math.rad
local SIN = math.sin
local CF = CFrame.new
local V3 = Vector3.new
CameraService.Sine = function(i)
return ((SIN(RAD(i))+1)/2)
end
CameraService.Linear = function(i)
return ((i+1)/2)
end
CameraService.Quad = function(i)
return (((i^2)+1)/2)
end
local RS = game:GetService("RunService")
CameraService.Cam = workspace.CurrentCamera
CameraService.setFixedCam = function(CamCF)
CameraService.Cam.CameraType = Enum.CameraType.Fixed
CameraService.Cam.CFrame = CamCF
end
CameraService.setCustomCam = function(Character)
CameraService.Cam.CameraSubject = Character.Humanoid
CameraService.Cam.CameraType = Enum.CameraType.Custom
end
CameraService.scriptCam = function(scriptFunc)
CameraService.Cam.CameraType = Enum.CameraType.Scriptable
scriptFunc()
end
function CameraService:tweenCam(c1,f1,step,FOV,Roll,Alpha)
local c0,f0,fv0,r0 = CameraService.Cam.CFrame,CameraService.Cam.Focus,CameraService.Cam.FieldOfView,CameraService.Cam:GetRoll()
for i = -90,90,step do
local r = Alpha(i)
CameraService.Cam.CFrame = CF(c0.p:lerp(c1.p,r))
CameraService.Cam.Focus = f1 ~= nil and CF(f0.p:lerp(f1.p,r)) or CameraService.Cam.Focus
CameraService.Cam.FieldOfView = (V3(fv0,0,0):Lerp(V3(FOV,0,0),r).X)
CameraService.Cam:SetRoll(V3(r0,0,0):Lerp(V3(Roll,0,0),r).X)
CameraService.Cam.CoordinateFrame = CF(CameraService.Cam.CFrame.p,CameraService.Cam.Focus.p) -- This is used to force it to look at the focus.
RS.Heartbeat:wait()
end
end
function CameraService:setCamMode(mode,...)
CameraService.Cam.CameraType = Enum.CameraType.Scriptable
if mode then
local result = CamModes[mode] (CameraService.Cam,...)
if result then
return result
end
end
end
return CameraService
local CamModes = {}
local RunService = game:GetService("RunService")
local V3 = {
RAW = Vector3.new;
ID = Vector3.new();
SpectateNormal = Vector3.new(1,1,6);
}
local Spring = require(game.ReplicatedStorage.GunLibraries.Spring)
local FollowSpring = Spring.new(V3.ID);
FollowSpring.s = 8;
FollowSpring.d = 0.75;
CamModes["CharCustomization"] = function(Cam,...)
local args = {...}
local StagePart = args[1]
Cam.CameraSubject = StagePart
Cam.CFrame = StagePart.CFrame
Cam.Focus = CFrame.new(StagePart.CFrame.p,StagePart.CFrame.p + StagePart.CFrame.lookVector):inverse()
Cam.FieldOfView = 30
end
function getLoadoutStageCFrame()
local stages = game.Workspace.LoadoutStages:GetChildren()
local stage = stages[math.random(#stages)]
return CFrame.new(stage.ViewPart.CFrame.p,stage.ViewPart.CFrame.p + stage.ViewPart.CFrame.lookVector)
end
CamModes["IntermissionMenu"] = function(Cam,...)
Cam.CFrame = getLoadoutStageCFrame()
end
CamModes["CameraScheme"] = function(Cam,...)
end
CamModes["Regular"] = function(Cam,...)
Cam.CameraType = Enum.CameraType.Custom
local args = {...}
Cam.CameraSubject = args[1]:FindFirstChildOfClass("Humanoid")
end
CamModes["Spectate"] = function(Cam,...)
local SpectationController = {}
local args = {...}
local specTicket = args[1]
if specTicket then
SpectationController.SpecToken = "Spectate_"..specTicket.Actor.Name
SpectationController.CFrame = Cam:GetRenderCFrame()
SpectationController.FreecamEnabled = false
SpectationController.SetFreecamEnabled = function(bool)
SpectationController.FreecamEnabled = bool
end
FollowSpring.t = SpectationController.CFrame.p
FollowSpring.p = SpectationController.CFrame.p
FollowSpring.v = V3.ID;
RunService:BindToRenderStep(SpectationController.SpecToken,Enum.RenderPriority.Camera.Value,function()
if not SpectationController.FreecamEnabled then
Cam.CameraType = Enum.CameraType.Scriptable
SpectationController.CFrame = specTicket.Actor.PrimaryPart.CFrame
FollowSpring.t = SpectationController.CFrame * V3.SpectateNormal
local camCFrame = SpectationController.CFrame - SpectationController.CFrame.p + FollowSpring.p
Cam.CFrame = camCFrame
else
Cam.CameraType = Enum.CameraType.Custom
Cam.CameraSubject = specTicket.Actor:FindFirstChildOfClass("Humanoid")
end
end)
SpectationController.Disconnect = function()
RunService:UnbindFromRenderStep(SpectationController.SpecToken)
end
return SpectationController
end
end
return CamModes
local PlayersService = game:GetService('Players')
local VRService = game:GetService("VRService")
local RootCameraCreator = require(script.Parent)
local UP_VECTOR = Vector3.new(0, 1, 0)
local XZ_VECTOR = Vector3.new(1, 0, 1)
local ZERO_VECTOR2 = Vector2.new(0, 0)
local VR_PITCH_FRACTION = 0.25
local Vector3_new = Vector3.new
local CFrame_new = CFrame.new
local math_min = math.min
local math_max = math.max
local math_atan2 = math.atan2
local math_rad = math.rad
local math_abs = math.abs
local function clamp(low, high, num)
return (num > high and high or num < low and low or num)
end
local function IsFinite(num)
return num == num and num ~= 1/0 and num ~= -1/0
end
local function IsFiniteVector3(vec3)
return IsFinite(vec3.x) and IsFinite(vec3.y) and IsFinite(vec3.z)
end
-- May return NaN or inf or -inf
-- This is a way of finding the angle between the two vectors:
local function findAngleBetweenXZVectors(vec2, vec1)
return math_atan2(vec1.X*vec2.Z-vec1.Z*vec2.X, vec1.X*vec2.X + vec1.Z*vec2.Z)
end
local function CreateClassicCamera()
local module = RootCameraCreator()
local tweenAcceleration = math_rad(220)
local tweenSpeed = math_rad(0)
local tweenMaxSpeed = math_rad(250)
local timeBeforeAutoRotate = 2
local lastUpdate = tick()
module.LastUserPanCamera = tick()
function module:Update()
module:ProcessTweens()
local now = tick()
local timeDelta = (now - lastUpdate)
local userPanningTheCamera = (self.UserPanningTheCamera == true)
local camera = workspace.CurrentCamera
local player = PlayersService.LocalPlayer
local humanoid = self:GetHumanoid()
local cameraSubject = camera and camera.CameraSubject
local isInVehicle = cameraSubject and cameraSubject:IsA('VehicleSeat')
local isOnASkateboard = cameraSubject and cameraSubject:IsA('SkateboardPlatform')
if lastUpdate == nil or now - lastUpdate > 1 then
module:ResetCameraLook()
self.LastCameraTransform = nil
end
if lastUpdate then
local gamepadRotation = self:UpdateGamepad()
if self:ShouldUseVRRotation() then
self.RotateInput = self.RotateInput + self:GetVRRotationInput()
else
-- Cap out the delta to 0.1 so we don't get some crazy things when we re-resume from
local delta = math_min(0.1, now - lastUpdate)
if gamepadRotation ~= ZERO_VECTOR2 then
userPanningTheCamera = true
self.RotateInput = self.RotateInput + (gamepadRotation * delta)
end
local angle = 0
if not (isInVehicle or isOnASkateboard) then
angle = angle + (self.TurningLeft and -120 or 0)
angle = angle + (self.TurningRight and 120 or 0)
end
if angle ~= 0 then
self.RotateInput = self.RotateInput + Vector2.new(math_rad(angle * delta), 0)
userPanningTheCamera = true
end
end
end
-- Reset tween speed if user is panning
if userPanningTheCamera then
tweenSpeed = 0
module.LastUserPanCamera = tick()
end
local userRecentlyPannedCamera = now - module.LastUserPanCamera < timeBeforeAutoRotate
local subjectPosition = self:GetSubjectPosition()
if subjectPosition and player and camera then
local zoom = self:GetCameraZoom()
if zoom < 0.5 then
zoom = 0.5
end
if self:GetShiftLock() and not self:IsInFirstPerson() then
-- We need to use the right vector of the camera after rotation, not before
local newLookVector = self:RotateCamera(self:GetCameraLook(), self.RotateInput)
local offset = ((newLookVector * XZ_VECTOR):Cross(UP_VECTOR).unit * 1.75)
if IsFiniteVector3(offset) then
subjectPosition = subjectPosition + offset
end
else
if not userPanningTheCamera and self.LastCameraTransform then
local isInFirstPerson = self:IsInFirstPerson()
if (isInVehicle or isOnASkateboard) and lastUpdate and humanoid and humanoid.Torso then
if isInFirstPerson then
if self.LastSubjectCFrame and (isInVehicle or isOnASkateboard) and cameraSubject:IsA('BasePart') then
local y = -findAngleBetweenXZVectors(self.LastSubjectCFrame.lookVector, cameraSubject.CFrame.lookVector)
if IsFinite(y) then
self.RotateInput = self.RotateInput + Vector2.new(y, 0)
end
tweenSpeed = 0
end
elseif not userRecentlyPannedCamera then
local forwardVector = humanoid.Torso.CFrame.lookVector
if isOnASkateboard then
forwardVector = cameraSubject.CFrame.lookVector
end
tweenSpeed = clamp(0, tweenMaxSpeed, tweenSpeed + tweenAcceleration * timeDelta)
local percent = clamp(0, 1, tweenSpeed * timeDelta)
if self:IsInFirstPerson() then
percent = 1
end
local y = findAngleBetweenXZVectors(forwardVector, self:GetCameraLook())
if IsFinite(y) and math_abs(y) > 0.0001 then
self.RotateInput = self.RotateInput + Vector2.new(y * percent, 0)
end
end
end
end
end
local VREnabled = VRService.VREnabled
camera.Focus = VREnabled and self:GetVRFocus(subjectPosition, timeDelta) or CFrame_new(subjectPosition)
local cameraFocusP = camera.Focus.p
if VREnabled and not self:IsInFirstPerson() then
local cameraHeight = self:GetCameraHeight()
local vecToSubject = (subjectPosition - camera.CFrame.p)
local distToSubject = vecToSubject.magnitude
-- Only move the camera if it exceeded a maximum distance to the subject in VR
if distToSubject > zoom or self.RotateInput.x ~= 0 then
local desiredDist = math_min(distToSubject, zoom)
vecToSubject = self:RotateCamera(vecToSubject.unit * XZ_VECTOR, Vector2.new(self.RotateInput.x, 0)) * desiredDist
local newPos = cameraFocusP - vecToSubject
local desiredLookDir = camera.CFrame.lookVector
if self.RotateInput.x ~= 0 then
desiredLookDir = vecToSubject
end
local lookAt = Vector3.new(newPos.x + desiredLookDir.x, newPos.y, newPos.z + desiredLookDir.z)
self.RotateInput = ZERO_VECTOR2
camera.CFrame = CFrame_new(newPos, lookAt) + Vector3_new(0, cameraHeight, 0)
end
else
local newLookVector = self:RotateCamera(self:GetCameraLook(), self.RotateInput)
self.RotateInput = ZERO_VECTOR2
camera.CFrame = CFrame_new(cameraFocusP - (zoom * newLookVector), cameraFocusP)
end
self.LastCameraTransform = camera.CFrame
self.LastCameraFocus = camera.Focus
if (isInVehicle or isOnASkateboard) and cameraSubject:IsA('BasePart') then
self.LastSubjectCFrame = cameraSubject.CFrame
else
self.LastSubjectCFrame = nil
end
end
lastUpdate = now
end
return module
end
return CreateClassicCamera
-- Written By Kip Turner, Copyright Roblox 2014
local newClickToMove = script:FindFirstChild("NewClickToMove")
if newClickToMove then
local newClickToMoveFlagSuccess, newClickToMoveFlagEnabled = pcall(function()
return UserSettings():IsUserFeatureEnabled("UserUseNewClickToMove")
end)
local useNewClickToMove = newClickToMoveFlagSuccess and newClickToMoveFlagEnabled
if useNewClickToMove then
return require(newClickToMove)
end
end
local UIS = game:GetService("UserInputService")
local PathfindingService = game:GetService("PathfindingService")
local PlayerService = game:GetService("Players")
local RunService = game:GetService("RunService")
local DebrisService = game:GetService('Debris')
local ReplicatedStorage = game:GetService('ReplicatedStorage')
local CameraScript = script.Parent
local ClassicCameraModule = require(CameraScript:WaitForChild('RootCamera'):WaitForChild('ClassicCamera'))
local Player = PlayerService.localPlayer
local MyMouse = Player:GetMouse()
local DirectPathEnabled = false
local SHOW_PATH = false
local Y_VECTOR3 = Vector3.new(0, 1, 0)
local XZ_VECTOR3 = Vector3.new(1, 0, 1)
local ZERO_VECTOR3 = Vector3.new(0, 0, 0)
local ZERO_VECTOR2 = Vector2.new(0, 0)
local RayCastIgnoreList = workspace.FindPartOnRayWithIgnoreList
local GetPartsTouchingExtents = workspace.FindPartsInRegion3
local math_min = math.min
local math_max = math.max
local math_pi = math.pi
local math_floor = math.floor
local math_abs = math.abs
local math_deg = math.deg
local math_acos = math.acos
local math_sin = math.sin
local math_atan2 = math.atan2
local Vector3_new = Vector3.new
local Vector2_new = Vector2.new
local CFrame_new = CFrame.new
local CurrentSeatPart = nil
local DrivingTo = nil
-- Bindable for when we want touch emergency controls
-- TODO: Click to move should probably have it's own gui touch controls
-- to manage this.
local BindableEvent_OnFailStateChanged = nil
local BindableEvent_EnableTouchJump = nil
if UIS.TouchEnabled then
BindableEvent_OnFailStateChanged = Instance.new('BindableEvent')
BindableEvent_OnFailStateChanged.Name = "OnClickToMoveFailStateChange"
BindableEvent_EnableTouchJump = Instance.new('BindableEvent')
BindableEvent_EnableTouchJump.Name = "EnableTouchJump"
local CameraScript = script.Parent
local PlayerScripts = CameraScript.Parent
BindableEvent_OnFailStateChanged.Parent = PlayerScripts
BindableEvent_EnableTouchJump.Parent = PlayerScripts
end
local function clamp(low, high, num)
return (num > high and high or num < low and low or num)
end
--------------------------UTIL LIBRARY-------------------------------
local Utility = {}
do
local Signal = {}
function Signal.Create()
local sig = {}
local mSignaler = Instance.new('BindableEvent')
local mArgData = nil
local mArgDataCount = nil
function sig:fire(...)
mArgData = {...}
mArgDataCount = select('#', ...)
mSignaler:Fire()
end
function sig:connect(f)
if not f then error("connect(nil)", 2) end
return mSignaler.Event:connect(function()
f(unpack(mArgData, 1, mArgDataCount))
end)
end
function sig:wait()
mSignaler.Event:wait()
assert(mArgData, "Missing arg data, likely due to :TweenSize/Position corrupting threadrefs.")
return unpack(mArgData, 1, mArgDataCount)
end
return sig
end
Utility.Signal = Signal
function Utility.Create(instanceType)
return function(data)
local obj = Instance.new(instanceType)
for k, v in pairs(data) do
if type(k) == 'number' then
v.Parent = obj
else
obj[k] = v
end
end
return obj
end
end
local function ViewSizeX()
local camera = workspace.CurrentCamera
local x = camera and camera.ViewportSize.X or 0
local y = camera and camera.ViewportSize.Y or 0
if x == 0 then
return 1024
else
if x > y then
return x
else
return y
end
end
end
Utility.ViewSizeX = ViewSizeX
local function ViewSizeY()
local camera = workspace.CurrentCamera
local x = camera and camera.ViewportSize.X or 0
local y = camera and camera.ViewportSize.Y or 0
if y == 0 then
return 768
else
if x > y then
return y
else
return x
end
end
end
Utility.ViewSizeY = ViewSizeY
local function AspectRatio()
return ViewSizeX() / ViewSizeY()
end
Utility.AspectRatio = AspectRatio
local function FindChacterAncestor(part)
if part then
local humanoid = part:FindFirstChild("Humanoid")
if humanoid then
return part, humanoid
else
return FindChacterAncestor(part.Parent)
end
end
end
Utility.FindChacterAncestor = FindChacterAncestor
local function GetUnitRay(x, y, viewWidth, viewHeight, camera)
return camera:ScreenPointToRay(x, y)
end
Utility.GetUnitRay = GetUnitRay
local function Raycast(ray, ignoreNonCollidable, ignoreList)
local ignoreList = ignoreList or {}
local hitPart, hitPos = RayCastIgnoreList(workspace, ray, ignoreList)
if hitPart then
if ignoreNonCollidable and hitPart.CanCollide == false then
table.insert(ignoreList, hitPart)
return Raycast(ray, ignoreNonCollidable, ignoreList)
end
return hitPart, hitPos
end
return nil, nil
end
Utility.Raycast = Raycast
Utility.Round = function(num, roundToNearest)
roundToNearest = roundToNearest or 1
return math_floor((num + roundToNearest/2) / roundToNearest) * roundToNearest
end
local function AveragePoints(positions)
local avgPos = ZERO_VECTOR2
if #positions > 0 then
for i = 1, #positions do
avgPos = avgPos + positions[i]
end
avgPos = avgPos / #positions
end
return avgPos
end
Utility.AveragePoints = AveragePoints
local function FuzzyEquals(numa, numb)
return numa + 0.1 > numb and numa - 0.1 < numb
end
Utility.FuzzyEquals = FuzzyEquals
local LastInput = 0
UIS.InputBegan:connect(function(inputObject, wasSunk)
if not wasSunk then
if inputObject.UserInputType == Enum.UserInputType.Touch or
inputObject.UserInputType == Enum.UserInputType.MouseButton1 or
inputObject.UserInputType == Enum.UserInputType.MouseButton2 then
LastInput = tick()
end
end
end)
Utility.GetLastInput = function()
return LastInput
end
end
local humanoidCache = {}
local function findPlayerHumanoid(player)
local character = player and player.Character
if character then
local resultHumanoid = humanoidCache[player]
if resultHumanoid and resultHumanoid.Parent == character then
return resultHumanoid
else
humanoidCache[player] = nil -- Bust Old Cache
local humanoid = character:FindFirstChildOfClass("Humanoid")
if humanoid then
humanoidCache[player] = humanoid
end
return humanoid
end
end
end
local GetThetaBetweenCFrames; do
local components = CFrame.new().components
local inverse = CFrame.new().inverse
local acos = math.acos
GetThetaBetweenCFrames = function(c0, c1) -- (CFrame from, CFrame to) -> (float theta)
local _, _, _, xx, yx, zx,
xy, yy, zy,
xz, yz, zz = components(inverse(c0)*c1)
local cosTheta = (xx + yy + zz - 1)/2
if cosTheta >= 0.999 then
-- Same rotation
return 0
elseif cosTheta <= -0.999 then
-- Oposite rotations
return math_pi
else
return acos(cosTheta)
end
end
end
---------------------------------------------------------
local Signal = Utility.Signal
local Create = Utility.Create
--------------------------CHARACTER CONTROL-------------------------------
local function CreateController()
local this = {}
this.TorsoLookPoint = nil
function this:SetTorsoLookPoint(point)
local humanoid = findPlayerHumanoid(Player)
if humanoid then
humanoid.AutoRotate = false
end
this.TorsoLookPoint = point
self:UpdateTorso()
delay(2,
function()
-- this isnt technically correct for detecting if this is the last issue to the setTorso function
if this.TorsoLookPoint == point then
this.TorsoLookPoint = nil
if humanoid then
humanoid.AutoRotate = true
end
end
end)
end
function this:UpdateTorso(point)
if this.TorsoLookPoint then
point = this.TorsoLookPoint
else
return
end
local humanoid = findPlayerHumanoid(Player)
local torso = humanoid and humanoid.Torso
if torso then
local lookVec = (point - torso.CFrame.p).unit
local squashedLookVec = Vector3_new(lookVec.X, 0, lookVec.Z).unit
torso.CFrame = CFrame.new(torso.CFrame.p, torso.CFrame.p + squashedLookVec)
end
end
return this
end
local CharacterControl = CreateController()
-----------------------------------------------------------------------
--------------------------PC AUTO JUMPER-------------------------------
local function GetCharacter()
return Player and Player.Character
end
local function GetTorso()
local humanoid = findPlayerHumanoid(Player)
return humanoid and humanoid.Torso
end
local function IsPartAHumanoid(part)
return part and part.Parent and (part.Parent:FindFirstChild('Humanoid') ~= nil)
end
local function doAutoJump()
local character = GetCharacter()
if (character == nil) then
return;
end
local humanoid = findPlayerHumanoid(Player)
if (humanoid == nil) then
return;
end
local rayLength = 1.5;
-- This is how high a ROBLOXian jumps from the mid point of his torso
local jumpHeight = 7.0;
local torso = GetTorso()
if (torso == nil) then
return;
end
local torsoCFrame = torso.CFrame;
local torsoLookVector = torsoCFrame.lookVector;
local torsoPos = torsoCFrame.p;
local torsoRay = Ray.new(torsoPos + Vector3_new(0, -torso.Size.Y/2, 0), torsoLookVector * rayLength);
local jumpRay = Ray.new(torsoPos + Vector3_new(0, jumpHeight - torso.Size.Y, 0), torsoLookVector * rayLength);
local hitPart, _ = RayCastIgnoreList(workspace, torsoRay, {character}, false)
local jumpHitPart, _ = RayCastIgnoreList(workspace, jumpRay, {character}, false)
if (hitPart and jumpHitPart == nil and hitPart.CanCollide == true) then
-- NOTE: this follow line is not in the C++ impl, but an improvement in Click to Move
if not IsPartAHumanoid(hitPart) then
humanoid.Jump = true;
end
end
end
local NO_JUMP_STATES =
{
[Enum.HumanoidStateType.FallingDown] = false;
[Enum.HumanoidStateType.Flying] = false;
[Enum.HumanoidStateType.Freefall] = false;
[Enum.HumanoidStateType.GettingUp] = false;
[Enum.HumanoidStateType.Ragdoll] = false;
[Enum.HumanoidStateType.Running] = false;
[Enum.HumanoidStateType.Seated] = false;
[Enum.HumanoidStateType.Swimming] = false;
-- Special case to detect if we are on a ladder
[Enum.HumanoidStateType.Climbing] = false;
}
local function enableAutoJump()
local humanoid = findPlayerHumanoid(Player)
local currentState = humanoid and humanoid:GetState()
if currentState then
return NO_JUMP_STATES[currentState] == nil
end
return false
end
local function getAutoJump()
return true
end
local function vec3IsZero(vec3)
return vec3.magnitude < 0.05
end
-- NOTE: This function is radically different from the engine's implementation
local walkVelocityVector = Vector3_new(1,1,1)
local function calcDesiredWalkVelocity()
-- TEMP
return walkVelocityVector
end
local function preStepSimulatorSide(dt)
if getAutoJump() and enableAutoJump() then
local desiredWalkVelocity = calcDesiredWalkVelocity();
if (not vec3IsZero(desiredWalkVelocity)) then
doAutoJump();
end
end
end
local function AutoJumper()
local this = {}
local running = false
local runRoutine = nil
function this:Run()
running = true
local thisRoutine = nil
thisRoutine = coroutine.create(function()
while running and thisRoutine == runRoutine do
this:Step()
wait()
end
end)
runRoutine = thisRoutine
coroutine.resume(thisRoutine)
end
function this:Stop()
running = false
end
function this:Step()
preStepSimulatorSide()
end
return this
end
-----------------------------------------------------------------------------
-----------------------------------PATHER--------------------------------------
local function CreateDestinationIndicator(pos)
local destinationGlobe = Create'Part'
{
Name = 'PathGlobe';
TopSurface = 'Smooth';
BottomSurface = 'Smooth';
Shape = 'Ball';
CanCollide = false;
Size = Vector3_new(2,2,2);
BrickColor = BrickColor.new('Institutional white');
Transparency = 0;
Anchored = true;
CFrame = CFrame.new(pos);
}
return destinationGlobe
end
local function Pather(character, point)
local this = {}
this.Cancelled = false
this.Started = false
this.Finished = Signal.Create()
this.PathFailed = Signal.Create()
this.PathStarted = Signal.Create()
this.PathComputed = false
function this:YieldUntilPointReached(character, point, timeout)
timeout = timeout or 10000000
local humanoid = findPlayerHumanoid(Player)
local torso = humanoid and humanoid.Torso
local start = tick()
local lastMoveTo = start
while torso and tick() - start < timeout and this.Cancelled == false do
local diffVector = (point - torso.CFrame.p)
local xzMagnitude = (diffVector * XZ_VECTOR3).magnitude
if xzMagnitude < 6 then
-- Jump if the path is telling is to go upwards
if diffVector.Y >= 2.2 then
humanoid.Jump = true
end
end
-- The hard-coded number 2 here is from the engine's MoveTo implementation
if xzMagnitude < 2 then
return true
end
-- Keep on issuing the move command because it will automatically quit every so often.
if tick() - lastMoveTo > 1.5 then
humanoid:MoveTo(point)
lastMoveTo = tick()
end
CharacterControl:UpdateTorso(point)
wait()
end
return false
end
function this:Cancel()
this.Cancelled = true
local humanoid = findPlayerHumanoid(Player)
local torso = humanoid and humanoid.Torso
if humanoid and torso then
humanoid:MoveTo(torso.CFrame.p)
end
end
function this:CheckOcclusion(point1, point2, character, torsoRadius)
local humanoid = findPlayerHumanoid(Player)
local torso = humanoid and humanoid.Torso
if torsoRadius == nil then
torsoRadius = torso and Vector3_new(torso.Size.X/2,0,torso.Size.Z/2) or XZ_VECTOR3
end
local diffVector = point2 - point1
local directionVector = diffVector.unit
local rightVector = Y_VECTOR3:Cross(directionVector) * torsoRadius
local rightPart, _ = Utility.Raycast(Ray.new(point1 + rightVector, diffVector + rightVector), true, {character})
local hitPart, _ = Utility.Raycast(Ray.new(point1, diffVector), true, {character})
local leftPart, _ = Utility.Raycast(Ray.new(point1 - rightVector, diffVector - rightVector), true, {character})
if rightPart or hitPart or leftPart then
return false
end
-- Make sure we have somewhere to stand on
local midPt = (point2 + point1) / 2
local studsBetweenSamples = 2
for i = 1, math_floor(diffVector.magnitude/studsBetweenSamples) do
local downPart, _ = Utility.Raycast(Ray.new(point1 + directionVector * i * studsBetweenSamples, Vector3_new(0,-7,0)), true, {character})
if not downPart then
return false
end
end
return true
end
function this:SmoothPoints(pathToSmooth)
local result = {}
local humanoid = findPlayerHumanoid(Player)
local torso = humanoid and humanoid.Torso
for i = 1, #pathToSmooth do
table.insert(result, pathToSmooth[i])
end
-- Backwards for safe-deletion
for i = #result - 1, 1, -1 do
if i + 1 <= #result then
local nextPoint = result[i+1]
local thisPoint = result[i]
local lastPoint = result[i-1]
if lastPoint == nil then
lastPoint = torso and Vector3_new(torso.CFrame.p.X, thisPoint.Y, torso.CFrame.p.Z)
end
if lastPoint and Utility.FuzzyEquals(thisPoint.Y, lastPoint.Y) and Utility.FuzzyEquals(thisPoint.Y, nextPoint.Y) then
if this:CheckOcclusion(lastPoint, nextPoint, character) then
table.remove(result, i)
-- Move i back one to recursively-smooth
i = i + 1
end
end
end
end
return result
end
function this:CheckNeighboringCells(character)
local pathablePoints = {}
local humanoid = findPlayerHumanoid(Player)
local torso = character and humanoid and humanoid.Torso
if torso then
local torsoCFrame = torso.CFrame
local torsoPos = torsoCFrame.p
-- Minus and plus 2 is so we can get it into the cell-corner space and then translate it back into cell-center space
local roundedPos = Vector3_new(Utility.Round(torsoPos.X-2,4)+2, Utility.Round(torsoPos.Y-2,4)+2, Utility.Round(torsoPos.Z-2,4)+2)
local neighboringCells = {}
for x = -4, 4, 8 do
for z = -4, 4, 8 do
table.insert(neighboringCells, roundedPos + Vector3_new(x,0,z))
end
end
for _, testPoint in pairs(neighboringCells) do
local pathable = this:CheckOcclusion(roundedPos, testPoint, character, ZERO_VECTOR3)
if pathable then
table.insert(pathablePoints, testPoint)
end
end
end
return pathablePoints
end
function this:ComputeDirectPath()
local humanoid = findPlayerHumanoid(Player)
local torso = humanoid and humanoid.Torso
if torso then
local startPt = torso.CFrame.p
local finishPt = point
if (finishPt - startPt).magnitude < 150 then
-- move back the destination by 2 studs or otherwise the pather will collide with the object we are trying to reach
finishPt = finishPt - (finishPt - startPt).unit * 2
if this:CheckOcclusion(startPt, finishPt, character, ZERO_VECTOR3) then
local pathResult = {}
pathResult.Status = Enum.PathStatus.Success
function pathResult:GetPointCoordinates()
return {finishPt}
end
return pathResult
end
end
end
end
local function AllAxisInThreshhold(targetPt, otherPt, threshold)
return math_abs(targetPt.X - otherPt.X) <= threshold and
math_abs(targetPt.Y - otherPt.Y) <= threshold and
math_abs(targetPt.Z - otherPt.Z) <= threshold
end
function this:ComputePath()
local smoothed = false
local humanoid = findPlayerHumanoid(Player)
local torso = humanoid and humanoid.Torso
if torso then
if this.PathComputed then return end
this.PathComputed = true
-- Will yield the script since it is an Async script (start, finish, maxDistance)
-- Try to use the smooth function, but it may not exist yet :(
local success = pcall(function()
-- 3 is height from torso cframe to ground
this.pathResult = PathfindingService:ComputeSmoothPathAsync(torso.CFrame.p - Vector3_new(0,3,0), point, 400)
smoothed = true
end)
if not success then
-- 3 is height from torso cframe to ground
this.pathResult = PathfindingService:ComputeRawPathAsync(torso.CFrame.p - Vector3_new(0,3,0), point, 400)
smoothed = false
end
this.pointList = this.pathResult and this.pathResult:GetPointCoordinates()
local pathFound = false
if this.pathResult.Status == Enum.PathStatus.FailFinishNotEmpty then
-- Lets try again with a slightly set back start point; it is ok to do this again so the FailFinishNotEmpty uses little computation
local diffVector = point - workspace.CurrentCamera.CoordinateFrame.p
if diffVector.magnitude > 2 then
local setBackPoint = point - (diffVector).unit * 2.1
local success = pcall(function()
this.pathResult = PathfindingService:ComputeSmoothPathAsync(torso.CFrame.p, setBackPoint, 400)
smoothed = true
end)
if not success then
this.pathResult = PathfindingService:ComputeRawPathAsync(torso.CFrame.p, setBackPoint, 400)
smoothed = false
end
this.pointList = this.pathResult and this.pathResult:GetPointCoordinates()
pathFound = true
end
end
if this.pathResult.Status == Enum.PathStatus.ClosestNoPath and #this.pointList >= 1 and pathFound == false then
local otherPt = this.pointList[#this.pointList]
if AllAxisInThreshhold(point, otherPt, 4) and (torso.CFrame.p - point).magnitude > (otherPt - point).magnitude then
local pathResult = {}
pathResult.Status = Enum.PathStatus.Success
function pathResult:GetPointCoordinates()
return {this.pointList}
end
this.pathResult = pathResult
pathFound = true
end
end
if (this.pathResult.Status == Enum.PathStatus.FailStartNotEmpty or this.pathResult.Status == Enum.PathStatus.ClosestNoPath) and pathFound == false then
local pathablePoints = this:CheckNeighboringCells(character)
for _, otherStart in pairs(pathablePoints) do
local pathResult;
local success = pcall(function()
pathResult = PathfindingService:ComputeSmoothPathAsync(otherStart, point, 400)
smoothed = true
end)
if not success then
pathResult = PathfindingService:ComputeRawPathAsync(otherStart, point, 400)
smoothed = false
end
if pathResult and pathResult.Status == Enum.PathStatus.Success then
this.pathResult = pathResult
if this.pathResult then
this.pointList = this.pathResult:GetPointCoordinates()
table.insert(this.pointList, 1, otherStart)
end
break
end
end
end
if DirectPathEnabled then
if this.pathResult.Status ~= Enum.PathStatus.Success then
local directPathResult = this:ComputeDirectPath()
if directPathResult and directPathResult.Status == Enum.PathStatus.Success then
this.pathResult = directPathResult
this.pointList = directPathResult:GetPointCoordinates()
end
end
end
end
return smoothed
end
function this:IsValidPath()
this:ComputePath()
local pathStatus = this.pathResult.Status
return pathStatus == Enum.PathStatus.Success
end
function this:GetPathStatus()
this:ComputePath()
return this.pathResult.Status
end
function this:Start()
if CurrentSeatPart then
return
end
spawn(function()
local humanoid = findPlayerHumanoid(Player)
--humanoid.AutoRotate = false
local torso = humanoid and humanoid.Torso
if torso then
if this.Started then return end
this.Started = true
-- Will yield the script since it is an Async function script (start, finish, maxDistance)
local smoothed = this:ComputePath()
if this:IsValidPath() then
this.PathStarted:fire()
-- smooth out zig-zaggy paths
local smoothPath = smoothed and this.pointList or this:SmoothPoints(this.pointList)
for i, point in pairs(smoothPath) do
if humanoid then
if this.Cancelled then
return
end
local wayPoint = nil
if SHOW_PATH then
wayPoint = CreateDestinationIndicator(point)
wayPoint.BrickColor = BrickColor.new("New Yeller")
wayPoint.Parent = workspace
print(wayPoint.CFrame.p)
end
humanoid:MoveTo(point)
local distance = ((torso.CFrame.p - point) * XZ_VECTOR3).magnitude
local approxTime = 10
if math_abs(humanoid.WalkSpeed) > 0 then
approxTime = distance / math_abs(humanoid.WalkSpeed)
end
local yielding = true
if i == 1 then
--local rotatedCFrame = CameraModule:LookAtPreserveHeight(point)
if CameraModule then
local rotatedCFrame = CameraModule:LookAtPreserveHeight(smoothPath[#smoothPath])
local finishedSignal, duration = CameraModule:TweenCameraLook(rotatedCFrame)
end
--CharacterControl:SetTorsoLookPoint(point)
end
---[[
if (humanoid.Torso.CFrame.p - point).magnitude > 9 then
spawn(function()
while yielding and this.Cancelled == false do
if CameraModule then
local look = CameraModule:GetCameraLook()
local squashedLook = (look * XZ_VECTOR3).unit
local direction = ((point - CameraModule.cframe.p) * XZ_VECTOR3).unit
local theta = math_deg(math_acos(squashedLook:Dot(direction)))
if tick() - Utility.GetLastInput() > 2 and theta > (workspace.CurrentCamera.FieldOfView / 2) then
local rotatedCFrame = CameraModule:LookAtPreserveHeight(point)
local finishedSignal, duration = CameraModule:TweenCameraLook(rotatedCFrame)
--return
end
end
wait(0.1)
end
end)
end
--]]
local didReach = this:YieldUntilPointReached(character, point, approxTime * 3 + 1)
yielding = false
if SHOW_PATH then
wayPoint:Destroy()
end
if not didReach then
this.PathFailed:fire()
return
end
end
end
this.Finished:fire()
return
end
end
this.PathFailed:fire()
end)
end
return this
end
-------------------------------------------------------------------------
local function FlashRed(object)
local origColor = object.BrickColor
local redColor = BrickColor.new("Really red")
local start = tick()
local duration = 4
spawn(function()
while object and tick() - start < duration do
object.BrickColor = origColor
wait(0.13)
if object then
object.BrickColor = redColor
end
wait(0.13)
end
end)
end
--local joystickWidth = 250
--local joystickHeight = 250
local function IsInBottomLeft(pt)
local joystickHeight = math.min(Utility.ViewSizeY() * 0.33, 250)
local joystickWidth = joystickHeight
return pt.X <= joystickWidth and pt.Y > Utility.ViewSizeY() - joystickHeight
end
local function IsInBottomRight(pt)
local joystickHeight = math.min(Utility.ViewSizeY() * 0.33, 250)
local joystickWidth = joystickHeight
return pt.X >= Utility.ViewSizeX() - joystickWidth and pt.Y > Utility.ViewSizeY() - joystickHeight
end
local function CheckAlive(character)
local humanoid = findPlayerHumanoid(Player)
return humanoid ~= nil and humanoid.Health > 0
end
local function GetEquippedTool(character)
if character ~= nil then
for _, child in pairs(character:GetChildren()) do
if child:IsA('Tool') then
return child
end
end
end
end
local function ExploreWithRayCast(currentPoint, originDirection)
local TestDistance = 40
local TestVectors = {}
do
local forwardVector = originDirection;
for i = 0, 15 do
table.insert(TestVectors, CFrame.Angles(0, math.pi / 8 * i, 0) * forwardVector)
end
end
local testResults = {}
-- Heuristic should be something along the lines of distance and closeness to the traveling direction
local function ExploreHeuristic()
for _, testData in pairs(testResults) do
local walkDirection = -1 * originDirection
local directionCoeff = (walkDirection:Dot(testData['Vector']) + 1) / 2
local distanceCoeff = testData['Distance'] / TestDistance
testData["Value"] = directionCoeff * distanceCoeff
end
end
for i, vec in pairs(TestVectors) do
local hitPart, hitPos = Utility.Raycast(Ray.new(currentPoint, vec * TestDistance), true, {Player.Character})
if hitPos then
table.insert(testResults, {Vector = vec; Distance = (hitPos - currentPoint).magnitude})
else
table.insert(testResults, {Vector = vec; Distance = TestDistance})
end
end
ExploreHeuristic()
table.sort(testResults, function(a,b) return a["Value"] > b["Value"] end)
return testResults
end
local TapId = 1
local ExistingPather = nil
local ExistingIndicator = nil
local PathCompleteListener = nil
local PathFailedListener = nil
local function CleanupPath()
DrivingTo = nil
if ExistingPather then
ExistingPather:Cancel()
end
if PathCompleteListener then
PathCompleteListener:disconnect()
PathCompleteListener = nil
end
if PathFailedListener then
PathFailedListener:disconnect()
PathFailedListener = nil
end
if ExistingIndicator then
DebrisService:AddItem(ExistingIndicator, 0)
ExistingIndicator = nil
end
end
local function getExtentsSize(Parts)
local maxX = Parts[1].Position.X
local maxY = Parts[1].Position.Y
local maxZ = Parts[1].Position.Z
local minX = Parts[1].Position.X
local minY = Parts[1].Position.Y
local minZ = Parts[1].Position.Z
for i = 2, #Parts do
maxX = math_max(maxX, Parts[i].Position.X)
maxY = math_max(maxY, Parts[i].Position.Y)
maxZ = math_max(maxZ, Parts[i].Position.Z)
minX = math_min(minX, Parts[i].Position.X)
minY = math_min(minY, Parts[i].Position.Y)
minZ = math_min(minZ, Parts[i].Position.Z)
end
return Region3.new(Vector3_new(minX, minY, minZ), Vector3_new(maxX, maxY, maxZ))
end
local function inExtents(Extents, Position)
if Position.X < (Extents.CFrame.p.X - Extents.Size.X/2) or Position.X > (Extents.CFrame.p.X + Extents.Size.X/2) then
return false
end
if Position.Z < (Extents.CFrame.p.Z - Extents.Size.Z/2) or Position.Z > (Extents.CFrame.p.Z + Extents.Size.Z/2) then
return false
end
--ignoring Y for now
return true
end
local AutoJumperInstance = nil
local ShootCount = 0
local FailCount = 0
local function OnTap(tapPositions, goToPoint)
-- Good to remember if this is the latest tap event
TapId = TapId + 1
local thisTapId = TapId
local camera = workspace.CurrentCamera
local character = Player.Character
if not CheckAlive(character) then return end
-- This is a path tap position
if #tapPositions == 1 or goToPoint then
if camera then
local unitRay = Utility.GetUnitRay(tapPositions[1].x, tapPositions[1].y, MyMouse.ViewSizeX, MyMouse.ViewSizeY, camera)
local ray = Ray.new(unitRay.Origin, unitRay.Direction*400)
local hitPart, hitPt = Utility.Raycast(ray, true, {character})
local hitChar, hitHumanoid = Utility.FindChacterAncestor(hitPart)
local torso = character and character:FindFirstChild("Humanoid") and character:FindFirstChild("Humanoid").Torso
local startPos = torso.CFrame.p
if goToPoint then
hitPt = goToPoint
hitChar = nil
end
if hitChar and hitHumanoid and hitHumanoid.Torso and (hitHumanoid.Torso.CFrame.p - torso.CFrame.p).magnitude < 7 then
CleanupPath()
local myHumanoid = findPlayerHumanoid(Player)
if myHumanoid then
myHumanoid:MoveTo(hitPt)
end
ShootCount = ShootCount + 1
local thisShoot = ShootCount
-- Do shooot
local currentWeapon = GetEquippedTool(character)
if currentWeapon then
currentWeapon:Activate()
LastFired = tick()
end
elseif hitPt and character and not CurrentSeatPart then
local thisPather = Pather(character, hitPt)
if thisPather:IsValidPath() then
FailCount = 0
-- TODO: Remove when bug in engine is fixed
Player:Move(Vector3_new(1, 0, 0))
Player:Move(ZERO_VECTOR3)
thisPather:Start()
if BindableEvent_OnFailStateChanged then
BindableEvent_OnFailStateChanged:Fire(false)
end
CleanupPath()
local destinationGlobe = CreateDestinationIndicator(hitPt)
destinationGlobe.Parent = camera
ExistingPather = thisPather
ExistingIndicator = destinationGlobe
if AutoJumperInstance then
AutoJumperInstance:Run()
end
PathCompleteListener = thisPather.Finished:connect(function()
if AutoJumperInstance then
AutoJumperInstance:Stop()
end
if destinationGlobe then
if ExistingIndicator == destinationGlobe then
ExistingIndicator = nil
end
DebrisService:AddItem(destinationGlobe, 0)
destinationGlobe = nil
end
if hitChar then
local humanoid = findPlayerHumanoid(Player)
ShootCount = ShootCount + 1
local thisShoot = ShootCount
-- Do shoot
local currentWeapon = GetEquippedTool(character)
if currentWeapon then
currentWeapon:Activate()
LastFired = tick()
end
if humanoid then
humanoid:MoveTo(hitPt)
end
end
local finishPos = torso and torso.CFrame.p --hitPt
if finishPos and startPos and tick() - Utility.GetLastInput() > 2 then
local exploreResults = ExploreWithRayCast(finishPos, ((startPos - finishPos) * XZ_VECTOR3).unit)
-- Check for Nans etc..
if exploreResults[1] and exploreResults[1]["Vector"] and exploreResults[1]["Vector"].magnitude >= 0.5 and exploreResults[1]["Distance"] > 3 then
if CameraModule then
local rotatedCFrame = CameraModule:LookAtPreserveHeight(finishPos + exploreResults[1]["Vector"] * exploreResults[1]["Distance"])
local finishedSignal, duration = CameraModule:TweenCameraLook(rotatedCFrame)
end
end
end
end)
PathFailedListener = thisPather.PathFailed:connect(function()
if AutoJumperInstance then
AutoJumperInstance:Stop()
end
if destinationGlobe then
FlashRed(destinationGlobe)
DebrisService:AddItem(destinationGlobe, 3)
end
end)
else
if hitPt then
-- Feedback here for when we don't have a good path
local failedGlobe = CreateDestinationIndicator(hitPt)
FlashRed(failedGlobe)
DebrisService:AddItem(failedGlobe, 1)
failedGlobe.Parent = camera
if ExistingIndicator == nil then
FailCount = FailCount + 1
if FailCount >= 3 then
if BindableEvent_OnFailStateChanged then
BindableEvent_OnFailStateChanged:Fire(true)
end
CleanupPath()
end
end
end
end
elseif hitPt and character and CurrentSeatPart then
local destinationGlobe = CreateDestinationIndicator(hitPt)
destinationGlobe.Parent = camera
ExistingIndicator = destinationGlobe
DrivingTo = hitPt
local ConnectedParts = CurrentSeatPart:GetConnectedParts(true)
while wait() do
if CurrentSeatPart and ExistingIndicator == destinationGlobe then
local ExtentsSize = getExtentsSize(ConnectedParts)
if inExtents(ExtentsSize, destinationGlobe.Position) then
DebrisService:AddItem(destinationGlobe, 0)
destinationGlobe = nil
DrivingTo = nil
break
end
else
DebrisService:AddItem(destinationGlobe, 0)
if CurrentSeatPart == nil and destinationGlobe == ExistingIndicator then
DrivingTo = nil
OnTap(tapPositions, hitPt)
end
destinationGlobe = nil
break
end
end
else
-- no hit pt
end
end
elseif #tapPositions >= 2 then
if camera then
ShootCount = ShootCount + 1
local thisShoot = ShootCount
-- Do shoot
local avgPoint = Utility.AveragePoints(tapPositions)
local unitRay = Utility.GetUnitRay(avgPoint.x, avgPoint.y, MyMouse.ViewSizeX, MyMouse.ViewSizeY, camera)
local currentWeapon = GetEquippedTool(character)
if currentWeapon then
currentWeapon:Activate()
LastFired = tick()
end
end
end
end
local function CreateClickToMoveModule()
local this = {}
local LastStateChange = 0
local LastState = Enum.HumanoidStateType.Running
local FingerTouches = {}
local NumUnsunkTouches = 0
-- PC simulation
local mouse1Down = tick()
local mouse1DownPos = Vector2.new()
local mouse2Down = tick()
local mouse2DownPos = Vector2.new()
local mouse2Up = tick()
local movementKeys = {
[Enum.KeyCode.W] = true;
[Enum.KeyCode.A] = true;
[Enum.KeyCode.S] = true;
[Enum.KeyCode.D] = true;
[Enum.KeyCode.Up] = true;
[Enum.KeyCode.Down] = true;
}
local TapConn = nil
local InputBeganConn = nil
local InputChangedConn = nil
local InputEndedConn = nil
local HumanoidDiedConn = nil
local CharacterChildAddedConn = nil
local OnCharacterAddedConn = nil
local CharacterChildRemovedConn = nil
local RenderSteppedConn = nil
local HumanoidSeatedConn = nil
local function disconnectEvent(event)
if event then
event:disconnect()
end
end
local function DisconnectEvents()
disconnectEvent(TapConn)
disconnectEvent(InputBeganConn)
disconnectEvent(InputChangedConn)
disconnectEvent(InputEndedConn)
disconnectEvent(HumanoidDiedConn)
disconnectEvent(CharacterChildAddedConn)
disconnectEvent(OnCharacterAddedConn)
disconnectEvent(RenderSteppedConn)
disconnectEvent(CharacterChildRemovedConn)
pcall(function() RunService:UnbindFromRenderStep("ClickToMoveRenderUpdate") end)
disconnectEvent(HumanoidSeatedConn)
end
local function IsFinite(num)
return num == num and num ~= 1/0 and num ~= -1/0
end
local function findAngleBetweenXZVectors(vec2, vec1)
return math_atan2(vec1.X*vec2.Z-vec1.Z*vec2.X, vec1.X*vec2.X + vec1.Z*vec2.Z)
end
-- Setup the camera
CameraModule = ClassicCameraModule()
do
-- Extend The Camera Module Class
function CameraModule:LookAtPreserveHeight(newLookAtPt)
local camera = workspace.CurrentCamera
local focus = camera.Focus.p
local cameraCFrame = CameraModule.cframe
local mag = Vector3_new(cameraCFrame.lookVector.x, 0, cameraCFrame.lookVector.z).magnitude
local newLook = (Vector3_new(newLookAtPt.x, focus.y, newLookAtPt.z) - focus).unit * mag
local flippedLook = newLook + Vector3_new(0, cameraCFrame.lookVector.y, 0)
local distance = (focus - cameraCFrame.p).magnitude
local newCamPos = focus - flippedLook.unit * distance
return CFrame.new(newCamPos, newCamPos + flippedLook)
end
local lerp = CFrame.new().lerp
function CameraModule:TweenCameraLook(desiredCFrame, speed)
local e = 2.718281828459
local function SCurve(t)
return 1/(1 + e^(-t*1.5))
end
local function easeOutSine(t, b, c, d)
if t >= d then return b + c end
return c * math_sin(t/d * (math_pi/2)) + b;
end
local c0, c1 = CFrame_new(ZERO_VECTOR3, self:GetCameraLook()), desiredCFrame - desiredCFrame.p
local theta = GetThetaBetweenCFrames(c0, c1)
theta = clamp(0, math_pi, theta)
local duration = 0.65 * SCurve(theta - math_pi/4) + 0.15
if speed then
duration = theta / speed
end
local start = tick()
local finish = start + duration
self.UpdateTweenFunction = function()
local currTime = tick() - start
local alpha = clamp(0, 1, easeOutSine(currTime, 0, 1, duration))
local newCFrame = lerp(c0, c1, alpha)
local y = findAngleBetweenXZVectors(newCFrame.lookVector, self:GetCameraLook())
if IsFinite(y) and math_abs(y) > 0.0001 then
self.RotateInput = self.RotateInput + Vector2_new(y, 0)
end
return (currTime >= finish or alpha >= 1)
end
end
end
--- Done Extending
local function OnTouchBegan(input, processed)
if FingerTouches[input] == nil and not processed then
NumUnsunkTouches = NumUnsunkTouches + 1
end
FingerTouches[input] = processed
end
local function OnTouchChanged(input, processed)
if FingerTouches[input] == nil then
FingerTouches[input] = processed
if not processed then
NumUnsunkTouches = NumUnsunkTouches + 1
end
end
end
local function OnTouchEnded(input, processed)
--print("Touch tap fake:" , processed)
--if not processed then
-- OnTap({input.Position})
--end
if FingerTouches[input] ~= nil and FingerTouches[input] == false then
NumUnsunkTouches = NumUnsunkTouches - 1
end
FingerTouches[input] = nil
end
local function OnCharacterAdded(character)
DisconnectEvents()
InputBeganConn = UIS.InputBegan:connect(function(input, processed)
if input.UserInputType == Enum.UserInputType.Touch then
OnTouchBegan(input, processed)
-- Give back controls when they tap both sticks
local wasInBottomLeft = IsInBottomLeft(input.Position)
local wasInBottomRight = IsInBottomRight(input.Position)
if wasInBottomRight or wasInBottomLeft then
for otherInput, _ in pairs(FingerTouches) do
if otherInput ~= input then
local otherInputInLeft = IsInBottomLeft(otherInput.Position)
local otherInputInRight = IsInBottomRight(otherInput.Position)
if otherInput.UserInputState ~= Enum.UserInputState.End and ((wasInBottomLeft and otherInputInRight) or (wasInBottomRight and otherInputInLeft)) then
if BindableEvent_OnFailStateChanged then
BindableEvent_OnFailStateChanged:Fire(true)
end
return
end
end
end
end
end
-- Cancel path when you use the keyboard controls.
if processed == false and input.UserInputType == Enum.UserInputType.Keyboard and movementKeys[input.KeyCode] then
CleanupPath()
end
if input.UserInputType == Enum.UserInputType.MouseButton1 then
mouse1Down = tick()
mouse1DownPos = input.Position
end
if input.UserInputType == Enum.UserInputType.MouseButton2 then
mouse2Down = tick()
mouse2DownPos = input.Position
end
end)
InputChangedConn = UIS.InputChanged:connect(function(input, processed)
if input.UserInputType == Enum.UserInputType.Touch then
OnTouchChanged(input, processed)
end
end)
InputEndedConn = UIS.InputEnded:connect(function(input, processed)
if input.UserInputType == Enum.UserInputType.Touch then
OnTouchEnded(input, processed)
end
if input.UserInputType == Enum.UserInputType.MouseButton2 then
mouse2Up = tick()
local currPos = input.Position
if mouse2Up - mouse2Down < 0.25 and (currPos - mouse2DownPos).magnitude < 5 then
local positions = {currPos}
OnTap(positions)
end
end
end)
TapConn = UIS.TouchTap:connect(function(touchPositions, processed)
if not processed then
OnTap(touchPositions)
end
end)
if not UIS.TouchEnabled then -- PC
if AutoJumperInstance then
AutoJumperInstance:Stop()
AutoJumperInstance = nil
end
AutoJumperInstance = AutoJumper()
end
local function getThrottleAndSteer(object, point)
local lookVector = (point - object.Position)
lookVector = Vector3_new(lookVector.X, 0, lookVector.Z).unit
local objectVector = Vector3_new(object.CFrame.lookVector.X, 0, object.CFrame.lookVector.Z).unit
local dirVector = lookVector - objectVector
local mag = dirVector.magnitude
local degrees = math_deg(math_acos(lookVector:Dot(objectVector)))
local side = (object.CFrame:pointToObjectSpace(point).X > 0)
local throttle = 0
if mag < 0.25 then
throttle = 1
end
if mag > 1.8 then
throttle = -1
end
local distance = CurrentSeatPart.Position - DrivingTo
local velocity = CurrentSeatPart.Velocity
if velocity.magnitude*1.5 > distance.magnitude then
if velocity.magnitude*0.5 > distance.magnitude then
throttle = -throttle
else
throttle = 0
end
end
local steer = 0
if degrees > 5 and degrees < 175 then
if side then
steer = 1
else
steer = -1
end
end
local rotatingAt = math_deg(CurrentSeatPart.RotVelocity.magnitude)
local degreesAway = math_max(math_min(degrees, 180 - degrees), 10)
if (CurrentSeatPart.RotVelocity.X < 0)== (steer < 0) then
if rotatingAt*1.5 > degreesAway then
if rotatingAt*0.5 > degreesAway then
steer = -steer
else
steer = 0
end
end
end
return throttle, steer
end
local function Update()
if CameraModule then
if CameraModule.UserPanningTheCamera then
CameraModule.UpdateTweenFunction = nil
else
if CameraModule.UpdateTweenFunction then
local done = CameraModule.UpdateTweenFunction()
if done then
CameraModule.UpdateTweenFunction = nil
end
end
end
CameraModule:Update()
end
if CurrentSeatPart then
if DrivingTo then
local throttle, steer = getThrottleAndSteer(CurrentSeatPart, DrivingTo)
CurrentSeatPart.Throttle = throttle
CurrentSeatPart.Steer = steer
end
end
end
local success = pcall(function() RunService:BindToRenderStep("ClickToMoveRenderUpdate",Enum.RenderPriority.Camera.Value - 1,Update) end)
if not success then
if RenderSteppedConn then
RenderSteppedConn:disconnect()
end
RenderSteppedConn = RunService.RenderStepped:connect(Update)
end
local WasAutoJumper = false
local WasAutoJumpMobile = false
local function onSeated(child, active, currentSeatPart)
if active then
if BindableEvent_EnableTouchJump then
BindableEvent_EnableTouchJump:Fire(true)
end
if currentSeatPart and currentSeatPart.ClassName == "VehicleSeat" then
CurrentSeatPart = currentSeatPart
if AutoJumperInstance then
AutoJumperInstance:Stop()
AutoJumperInstance = nil
WasAutoJumper = true
else
WasAutoJumper = false
end
if child.AutoJumpEnabled then
WasAutoJumpMobile = true
child.AutoJumpEnabled = false
end
end
else
CurrentSeatPart = nil
if BindableEvent_EnableTouchJump then
BindableEvent_EnableTouchJump:Fire(false)
end
if WasAutoJumper then
AutoJumperInstance = AutoJumper()
WasAutoJumper = false
end
if WasAutoJumpMobile then
child.AutoJumpEnabled = true
WasAutoJumpMobile = false
end
end
end
local function OnCharacterChildAdded(child)
if UIS.TouchEnabled then
if child:IsA('Tool') then
child.ManualActivationOnly = true
end
end
if child:IsA('Humanoid') then
disconnectEvent(HumanoidDiedConn)
HumanoidDiedConn = child.Died:connect(function()
DebrisService:AddItem(ExistingIndicator, 1)
if AutoJumperInstance then
AutoJumperInstance:Stop()
AutoJumperInstance = nil
end
end)
local WasAutoJumper = false
local WasAutoJumpMobile = false
HumanoidSeatedConn = child.Seated:connect(function(active, seat) onSeated(child, active, seat) end)
if child.SeatPart then
onSeated(child, true, child.SeatPart)
end
end
end
CharacterChildAddedConn = character.ChildAdded:connect(function(child)
OnCharacterChildAdded(child)
end)
CharacterChildRemovedConn = character.ChildRemoved:connect(function(child)
if UIS.TouchEnabled then
if child:IsA('Tool') then
child.ManualActivationOnly = false
end
end
end)
for _, child in pairs(character:GetChildren()) do
OnCharacterChildAdded(child)
end
end
local Running = false
function this:Stop()
if Running then
DisconnectEvents()
CleanupPath()
if AutoJumperInstance then
AutoJumperInstance:Stop()
AutoJumperInstance = nil
end
if CameraModule then
CameraModule.UpdateTweenFunction = nil
CameraModule:SetEnabled(false)
end
-- Restore tool activation on shutdown
if UIS.TouchEnabled then
local character = Player.Character
if character then
for _, child in pairs(character:GetChildren()) do
if child:IsA('Tool') then
child.ManualActivationOnly = false
end
end
end
end
DrivingTo = nil
Running = false
end
end
function this:Start()
if not Running then
if Player.Character then -- retro-listen
OnCharacterAdded(Player.Character)
end
OnCharacterAddedConn = Player.CharacterAdded:connect(OnCharacterAdded)
if CameraModule then
CameraModule:SetEnabled(true)
end
Running = true
end
end
return this
end
return CreateClickToMoveModule
-----------------[ = INITIALIZATION = ]-----------------
local Plr = game.Players.LocalPlayer
local Cam = game.workspace.CurrentCamera
local Display = Plr.PlayerGui:WaitForChild("CutsceneGui"):WaitForChild("Display")
local DialogInputs = {Enum.KeyCode.ButtonA, Enum.KeyCode.ButtonB, Enum.KeyCode.ButtonX, Enum.KeyCode.ButtonY}
local Busy = false
local CAS = game:GetService("ContextActionService")
-----------------[ = FUNCTIONS = ]-----------------
function XBoxDialogChoise(ActionName, State, InputObj)
if State == Enum.UserInputState.Begin then
local Picks = {
[Enum.KeyCode.ButtonA] = 1,
[Enum.KeyCode.ButtonB] = 2,
[Enum.KeyCode.ButtonX] = 3,
[Enum.KeyCode.ButtonY] = 4
}
if Picks[InputObj.KeyCode]~=nil then
Display.API.RemoteClickAnswer:Fire(Picks[InputObj.KeyCode])
end
end
end
function ShowDialogAsync(Comp)
coroutine.resume(
coroutine.create(
function()
local AnswerTable = {}
for i = 1, math.min(#Comp.Dialog.Answers, 4) do -- dialog only supports 4 options max
AnswerTable[i] = Comp.Dialog.Answers[i].Text
end
Display.API.ShowDialog:Invoke(Comp.Dialog.Text, Comp.Dialog.Title, AnswerTable, Comp.Dialog.Alignment, Comp.Dialog.MaxDuration)
end
)
)
end
local LastCompNumber = 0
function RunComponent(Scene, CompNum)
local Component = Scene.Structure[CompNum]
print(Scene, CompNum)
LastCompNumber = CompNum
-- Trigger action if it is set to trigger at the start
if Component.Action.OnEnter~=nil then
Component.Action.OnEnter()
end
-- Dialog which is set to display during transition
if Component.Dialog.Text~="" and Component.Dialog.Text~=nil and Component.Dialog.DisplayMoment == "OnEnter" then
ShowDialogAsync(Component)
end
-- Transition camera
if Component.Transition.Type == "Jump" then
Cam.CFrame = Component.Focus.CFrame
elseif Component.Transition.Type == "Interpolate" then
Cam:Interpolate(Component.Focus.CFrame, Component.Focus.CFrame*CFrame.new(0, 0, -20), Component.Transition.Duration)
Cam.InterpolationFinished:wait()
else
local BeginTime = tick()
local BeginCFrame = Cam.CFrame
repeat
Cam.CFrame = BeginCFrame:lerp(Component.Focus.CFrame, math.min((tick()-BeginTime)/Component.Transition.Duration, 1))
game:GetService("RunService").RenderStepped:wait()
until tick()-BeginTime >= Component.Transition.Duration
Cam.CFrame = Component.Focus.CFrame
end
-- Trigger action if it is set to trigger on focus
if Component.Action.OnFocus~=nil then
Component.Action.OnFocus()
end
-- Dialog which is set to display on focus, this doesn't count towards the focus duration
if Component.Dialog.Text~="" and Component.Dialog.Text~=nil and Component.Dialog.DisplayMoment == "OnFocus" then
local AnswerTable = {}
for i = 1, math.min(#Component.Dialog.Answers, 4) do -- dialog only supports 4 options max
AnswerTable[i] = Component.Dialog.Answers[i].Text
end
Display.API.ShowDialog:Invoke(Component.Dialog.Text, Component.Dialog.Title, AnswerTable, Component.Dialog.Alignment, Component.Dialog.MaxDuration)
end
-- Wait for an input or timeout before continuing
if Component.Dialog.Text~=nil and Component.Dialog.Text~="" then
if #Component.Dialog.Answers > 0 then -- dialog with options
local AnswerNum, AnswerText = Display.API.Events.AnswerClicked.Event:Wait()
Display.API.HideDialog:Fire()
if Component.Dialog.MaxDuration~=nil then -- dialog with timeout on options, check for nil
if AnswerNum~=nil then -- check if an answer was given or if the timeout triggered stuff
RunComponent(Scene, Component.Dialog.Answers[AnswerNum].LinkedComponent)
else -- no given answer, skip to the 'timeout scene'
RunComponent(Scene, Component.TimeoutComponent)
end
else -- dialog with options but no timeout
RunComponent(Scene, Component.Dialog.Answers[AnswerNum].LinkedComponent)
end
else -- dialog but no options
wait(Component.Focus.Duration)
Display.API.HideDialog:Fire()
if Component.NextComponent~=nil then
RunComponent(Scene, Component.NextComponent)
end
end
else -- no dialog
wait(Component.Focus.Duration)
if Component.NextComponent~=nil then
RunComponent(Scene, Component.NextComponent)
end
end
return LastCompNumber
end
-----------------[ = API = ]-----------------
script.API.RunScene.Event:connect(
function(Scene, StartComp)
if Scene.ClassName == "Scene" and Busy == false then
Busy = true
local BeginCamType = game.workspace.CurrentCamera.CameraType
local CurComponent = tonumber(StartComp)==nil and 1 or StartComp
script.API.Events.CutsceneStarted:Fire(Scene.Name, CurComponent)
CAS:BindAction("CutsceneInput", XBoxDialogChoise, false, unpack(DialogInputs))
if Plr.Character then
Plr.Character:WaitForChild("Humanoid"):SetStateEnabled(Enum.HumanoidStateType.Dead, false)
end
Cam.CameraType = Enum.CameraType.Scriptable
Display.API.ShowBars:Fire(true)
local LastCompNum = RunComponent(Scene, CurComponent) -- this is the main 'meat' of the cutscene system
Display.API.ShowBars:Fire(false)
Cam.CameraType = BeginCamType
if Plr.Character then
Plr.Character:WaitForChild("Humanoid"):SetStateEnabled(Enum.HumanoidStateType.Dead, true)
end
CAS:UnbindAction("CutsceneInput")
script.API.Events.CutsceneEnded:Fire(Scene.Name, LastCompNum)
Busy = false
end
end
)
local CutsceneSystem = {
["NewScene"] = function(NewName)
local CutsceneObject = {
["ClassName"] = "Scene",
["Name"] = NewName,
["Structure"] = {},
["AddComponent"] = function(Self, Data)
Self.Structure[#Self.Structure+1] = Data
return #Self.Structure
end
}
return CutsceneObject
end,
["NewComponent"] = function()
local Component = {
["Focus"] = {
["CFrame"] = CFrame.new(0, 0, 0),
["Duration"] = 5 -- only has an effect if Dialog.Text == "" or Dialog.Text == nil
},
["Dialog"] = {
["Text"] = "", -- empty text isn't shown
["Title"] = "", -- empty title isn't shown
["Alignment"] = "Right", -- enum: "Left" or "Right", Answer order is mirrored with Alignment == "Left"
["DisplayMoment"] = "OnEnter", -- enum: "OnEnter", "OnFocus"
["MaxDuration"] = nil, -- nil or int, reaching the limit triggers Diplay.API.DialogDurationReached
["Answers"] = {},
["AddAnswer"] = function(Self, Answer, LinkedComponentNumber)
Self.Answers[#Self.Answers+1] = {
["Text"] = Answer,
["LinkedComponent"] = LinkedComponentNumber
}
end
},
["Transition"] = {
["Type"] = "Interpolate", -- enum: "Interpolate", "Tween", "Jump"
["Duration"] = 1
},
["Action"] = {
["OnEnter"] = nil, -- function which triggers when component is shown, nil to do nothing
["OnFocus"] = nil -- function which triggers when component focusses, nil to do nothing
},
["NextComponent"] = nil, -- next component for the cutscene, doesn't do anything if #Dialog.Answers > 0, cutscene stops if nil
["TimeoutComponent"] = nil -- next component in the case that DialogTimeoutReached triggers
}
return Component
end
}
return CutsceneSystem
-----------------[ = INITIALIZATION = ]-----------------
local WaitForEvent = require(script:WaitForChild("WaitForEvent"))
-----------------[ = XBOX/PC DISPLAY TYPE = ]-----------------
function SwitchToUI(UIType)
local function Scan(Obj)
local P = Obj:GetChildren()
for i = 1, #P do
if P[i].Name == "XBoxIndicator" or P[i].Name == "PCIndicator" then
if (UIType == "XBox" and P[i].Name == "XBoxIndicator") or (UIType == "PC" and P[i].Name == "PCIndicator") then
P[i].Visible = true
else
P[i].Visible = false
end
end
if #P[i]:GetChildren() > 0 then
Scan(P[i])
end
end
end
Scan(script.Parent.Dialog)
end
game:GetService("UserInputService").GamepadConnected:connect(
function(GamepadNum)
if GamepadNum == Enum.UserInputType.Gamepad1 then
SwitchToUI("XBox")
end
end
)
game:GetService("UserInputService").GamepadDisconnected:connect(
function(GamepadNum)
if GamepadNum == Enum.UserInputType.Gamepad1 then
SwitchToUI("PC")
end
end
)
if game:GetService("UserInputService"):GetGamepadConnected(Enum.UserInputType.Gamepad1) then SwitchToUI("XBox") else SwitchToUI("PC") end
-----------------[ = ANSWER PICKING = ]-----------------
local Answers = script.Parent.Dialog.Buttons:GetChildren()
for a = 1, #Answers do
Answers[a].MouseButton1Click:connect(
function()
local P = script.Parent.Dialog.Buttons:GetChildren()
for i = 1, #P do
P[i].Visible = false
end
script.Parent.Dialog.Visible = false
script.API.Events.AnswerClicked:Fire(tonumber(string.sub(Answers[a].Name, 7, 7)), Answers[a].Text)
end
)
end
script.API.RemoteClickAnswer.Event:connect(
function(AnswerNum)
local But = script.Parent.Dialog.Buttons:FindFirstChild("Option"..AnswerNum)
if But.Visible == true then
local P = script.Parent.Dialog.Buttons:GetChildren()
for i = 1, #P do
P[i].Visible = false
end
script.Parent.Dialog.Visible = false
script.API.Events.AnswerClicked:Fire(AnswerNum, But.Text)
end
end
)
-----------------[ = MAIN API = ]-----------------
script.API.ShowDialog.OnInvoke = function(TheText, Title, Options, Alignment, ReactTime) -- Options[int] = "Text"
-- Hide all option buttons first
local P = script.Parent.Dialog.Buttons:GetChildren()
for i = 1, #P do
P[i].Visible = false
end
-- Set title
if Title~=nil and Title~="" then
script.Parent.Dialog.DialogText.Size = UDim2.new(1, 0, 1, -10)
script.Parent.Dialog.Title.TitleText.Text = Title
script.Parent.Dialog.Title.Size = UDim2.new(0, script.Parent.Dialog.Title.TitleText.TextBounds.X+70, 0, 40)
if Alignment == "Right" then
script.Parent.Dialog.Title.Position = UDim2.new(0, 5, 0, 5)
else
script.Parent.Dialog.Title.Position = UDim2.new(1, -script.Parent.Dialog.Title.Size.X.Offset-5, 0, 5)
end
script.Parent.Dialog.Title.Visible = true
else
script.Parent.Dialog.DialogText.Size = UDim2.new(1, 0, 1, 0)
script.Parent.Dialog.Title.Visible = false
end
-- Align dialog box
if Alignment == "Right" then
script.Parent.Dialog.Position = UDim2.new(1, -20, 0.85, -56)
else
script.Parent.Dialog.Position = UDim2.new(0, 470, 0.85, -56)
end
-- Now show the dialog one character per frame
script.Parent.Dialog.DialogText.Text = ""
script.Parent.Dialog.Visible = true
for i = 1, string.len(TheText) do
script.Parent.Dialog.DialogText.Text = string.sub(TheText, 1, i)
game:GetService("RunService").Heartbeat:wait()
end
-- Finally show the given answers
local Depth = 0
local BStart, BEnd, BStep = 1, #Options, 1
if Alignment == "Left" then
BStart, BEnd, BStep = #Options, 1, -1
end
for i = BStart, BEnd, BStep do
local OptionButton = script.Parent.Dialog.Buttons:FindFirstChild("Option"..i)
OptionButton.Text = Options[i]
local Width = math.max(90, OptionButton.TextBounds.X+20)
OptionButton.Size = UDim2.new(0, Width, 1, 0)
if Alignment == "Right" then
OptionButton.Position = UDim2.new(0, Depth, 0, 0)
else
OptionButton.Position = UDim2.new(1, -Width-Depth, 0, 0)
end
OptionButton.Visible = true
Depth = Depth+Width
end
-- Trigger timeout event if nothing has been clicked yet
if ReactTime~=nil and #Options > 0 then
coroutine.resume(
coroutine.create(
function()
local Result = WaitForEvent(script.API.Events.AnswerClicked.Event, ReactTime)
if Result == nil then
script.API.Events.AnswerClicked:Fire()
end
end
)
)
end
return true
end
script.API.ShowBars.Event:connect(
function(DoShow)
local TweenTime = .75
if DoShow then
script.Parent.TopBar.Visible = true
script.Parent.BottomBar.Visible = true
script.Parent.TopBar:TweenPosition(UDim2.new(0, 0, 0, -36), Enum.EasingDirection.Out, Enum.EasingStyle.Quad, TweenTime, true)
script.Parent.BottomBar:TweenPosition(UDim2.new(0, 0, 1, 0), Enum.EasingDirection.Out, Enum.EasingStyle.Quad, TweenTime, true)
else
script.Parent.TopBar:TweenPosition(UDim2.new(0, 0, -.15, -36), Enum.EasingDirection.In, Enum.EasingStyle.Quad, TweenTime, true,
function(TweenStatus)
if TweenStatus == Enum.TweenStatus.Completed then
script.Parent.TopBar.Visible = false
end
end
)
script.Parent.BottomBar:TweenPosition(UDim2.new(0, 0, 1.15, 36), Enum.EasingDirection.In, Enum.EasingStyle.Quad, TweenTime, true,
function(TweenStatus)
if TweenStatus == Enum.TweenStatus.Completed then
script.Parent.BottomBar.Visible = false
end
end
)
end
end
)
script.API.HideDialog.Event:connect(
function()
script.Parent.Dialog.Visible = false
end
)
local PlayersService = game:GetService('Players')
local RootCameraCreator = require(script.Parent)
local ZERO_VECTOR2 = Vector2.new(0, 0)
local CFrame_new = CFrame.new
local math_min = math.min
local function CreateFixedCamera()
local module = RootCameraCreator()
local lastUpdate = tick()
function module:Update()
local now = tick()
local camera = workspace.CurrentCamera
local player = PlayersService.LocalPlayer
if lastUpdate == nil or now - lastUpdate > 1 then
module:ResetCameraLook()
self.LastCameraTransform = nil
end
if lastUpdate then
-- Cap out the delta to 0.1 so we don't get some crazy things when we re-resume from
local delta = math_min(0.1, now - lastUpdate)
local gamepadRotation = self:UpdateGamepad()
self.RotateInput = self.RotateInput + (gamepadRotation * delta)
end
local subjectPosition = self:GetSubjectPosition()
if subjectPosition and player and camera then
local zoom = self:GetCameraZoom()
if zoom <= 0 then
zoom = 0.1
end
local newLookVector = self:RotateCamera(self:GetCameraLook(), self.RotateInput)
self.RotateInput = ZERO_VECTOR2
camera.CoordinateFrame = CFrame_new(camera.Focus.p - (zoom * newLookVector), camera.Focus.p)
self.LastCameraTransform = camera.CFrame
end
lastUpdate = now
end
return module
end
return CreateFixedCamera
local PlayersService = game:GetService('Players')
local VRService = game:GetService("VRService")
local RootCameraCreator = require(script.Parent)
local CFrame_new = CFrame.new
local Vector2_new = Vector2.new
local Vector3_new = Vector3.new
local math_min = math.min
local math_max = math.max
local math_atan2 = math.atan2
local math_rad = math.rad
local math_abs = math.abs
local HUMANOIDSTATE_CLIMBING = Enum.HumanoidStateType.Climbing
local ZERO_VECTOR2 = Vector2.new(0, 0)
local UP_VECTOR = Vector3.new(0, 1, 0)
local XZ_VECTOR = Vector3.new(1, 0, 1)
local ZERO_VECTOR3 = Vector3.new(0, 0, 0)
local PORTRAIT_OFFSET = Vector3.new(0, -3, 0)
local function clamp(low, high, num)
return num > high and high or num < low and low or num
end
local function IsFinite(num)
return num == num and num ~= 1/0 and num ~= -1/0
end
local function IsFiniteVector3(vec3)
return IsFinite(vec3.x) and IsFinite(vec3.y) and IsFinite(vec3.z)
end
-- May return NaN or inf or -inf
local function findAngleBetweenXZVectors(vec2, vec1)
-- This is a way of finding the angle between the two vectors:
return math_atan2(vec1.X*vec2.Z-vec1.Z*vec2.X, vec1.X*vec2.X + vec1.Z*vec2.Z)
end
local function CreateFollowCamera()
local module = RootCameraCreator()
local tweenAcceleration = math_rad(220)
local tweenSpeed = math_rad(0)
local tweenMaxSpeed = math_rad(250)
local timeBeforeAutoRotate = 2
local lastUpdate = tick()
module.LastUserPanCamera = tick()
function module:Update()
module:ProcessTweens()
local now = tick()
local timeDelta = (now - lastUpdate)
local userPanningTheCamera = (self.UserPanningTheCamera == true)
local camera = workspace.CurrentCamera
local player = PlayersService.LocalPlayer
local humanoid = self:GetHumanoid()
local cameraSubject = camera and camera.CameraSubject
local isClimbing = humanoid and humanoid:GetState() == HUMANOIDSTATE_CLIMBING
local isInVehicle = cameraSubject and cameraSubject:IsA('VehicleSeat')
local isOnASkateboard = cameraSubject and cameraSubject:IsA('SkateboardPlatform')
if lastUpdate == nil or now - lastUpdate > 1 then
module:ResetCameraLook()
self.LastCameraTransform = nil
end
if lastUpdate then
if self:ShouldUseVRRotation() then
self.RotateInput = self.RotateInput + self:GetVRRotationInput()
else
-- Cap out the delta to 0.1 so we don't get some crazy things when we re-resume from
local delta = math_min(0.1, now - lastUpdate)
local angle = 0
-- NOTE: Traditional follow camera does not rotate with arrow keys
if not (isInVehicle or isOnASkateboard) then
angle = angle + (self.TurningLeft and -120 or 0)
angle = angle + (self.TurningRight and 120 or 0)
end
local gamepadRotation = self:UpdateGamepad()
if gamepadRotation ~= Vector2.new(0,0) then
userPanningTheCamera = true
self.RotateInput = self.RotateInput + (gamepadRotation * delta)
end
if angle ~= 0 then
userPanningTheCamera = true
self.RotateInput = self.RotateInput + Vector2_new(math_rad(angle * delta), 0)
end
end
end
-- Reset tween speed if user is panning
if userPanningTheCamera then
tweenSpeed = 0
module.LastUserPanCamera = tick()
end
local userRecentlyPannedCamera = now - module.LastUserPanCamera < timeBeforeAutoRotate
local subjectPosition = self:GetSubjectPosition()
if subjectPosition and player and camera then
local zoom = self:GetCameraZoom()
if zoom < 0.5 then
zoom = 0.5
end
if self:GetShiftLock() and not self:IsInFirstPerson() then
local newLookVector = self:RotateCamera(self:GetCameraLook(), self.RotateInput)
local offset = ((newLookVector * XZ_VECTOR):Cross(UP_VECTOR).unit * 1.75)
if IsFiniteVector3(offset) then
subjectPosition = subjectPosition + offset
end
else
if self.LastCameraTransform and not userPanningTheCamera then
local isInFirstPerson = self:IsInFirstPerson()
if (isClimbing or isInVehicle or isOnASkateboard) and lastUpdate and humanoid and humanoid.Torso then
if isInFirstPerson then
if self.LastSubjectCFrame and (isInVehicle or isOnASkateboard) and cameraSubject:IsA('BasePart') then
local y = -findAngleBetweenXZVectors(self.LastSubjectCFrame.lookVector, cameraSubject.CFrame.lookVector)
if IsFinite(y) then
self.RotateInput = self.RotateInput + Vector2.new(y, 0)
end
tweenSpeed = 0
end
elseif not userRecentlyPannedCamera then
local forwardVector = humanoid.Torso.CFrame.lookVector
if isOnASkateboard then
forwardVector = cameraSubject.CFrame.lookVector
end
tweenSpeed = clamp(0, tweenMaxSpeed, tweenSpeed + tweenAcceleration * timeDelta)
local percent = clamp(0, 1, tweenSpeed*timeDelta)
if not isClimbing and self:IsInFirstPerson() then
percent = 1
end
local y = findAngleBetweenXZVectors(forwardVector, self:GetCameraLook())
-- Check for NaN
if IsFinite(y) and math_abs(y) > 0.0001 then
self.RotateInput = self.RotateInput + Vector2.new(y * percent, 0)
end
end
elseif not (isInFirstPerson or userRecentlyPannedCamera) and not VRService.VREnabled then
local lastVec = -(self.LastCameraTransform.p - subjectPosition)
local y = findAngleBetweenXZVectors(lastVec, self:GetCameraLook())
-- This cutoff is to decide if the humanoid's angle of movement,
-- relative to the camera's look vector, is enough that
-- we want the camera to be following them. The point is to provide
-- a sizable deadzone to allow more precise forward movements.
local thetaCutoff = 0.4
-- Check for NaNs
if IsFinite(y) and math.abs(y) > 0.0001 and math_abs(y) > thetaCutoff*timeDelta then
self.RotateInput = self.RotateInput + Vector2.new(y, 0)
end
end
end
end
local newLookVector = self:RotateCamera(self:GetCameraLook(), self.RotateInput)
self.RotateInput = ZERO_VECTOR2
if VRService.VREnabled then
camera.Focus = self:GetVRFocus(subjectPosition, timeDelta)
elseif self:IsPortraitMode() then
camera.Focus = CFrame_new(subjectPosition + PORTRAIT_OFFSET)
else
camera.Focus = CFrame_new(subjectPosition)
end
camera.CFrame = CFrame_new(camera.Focus.p - (zoom * newLookVector), camera.Focus.p) + Vector3.new(0, self:GetCameraHeight(), 0)
self.LastCameraTransform = camera.CFrame
self.LastCameraFocus = camera.Focus
if isInVehicle or isOnASkateboard and cameraSubject:IsA('BasePart') then
self.LastSubjectCFrame = cameraSubject.CFrame
else
self.LastSubjectCFrame = nil
end
end
lastUpdate = now
end
return module
end
return CreateFollowCamera
--[[
Invisicam
Modified 5.16.2017 by AllYourBlox to consider combined transparency when looking through multiple parts, and to add
a mode that takes advantage of the reduced cost of ray casts from re-implementing GetPartsObscuringTarget
on the C++ side to be O(N) for N parts hit, rather than O(N^2), and to have optimizations specifically
improving performance of closely bundled rays. Fading is also removed, since it is a frame rate killer with high-poly models,
and on mobile.
Based on Invisicam Version 2.5 by OnlyTwentyCharacters
--]]
local Invisicam = {}
---------------
-- Constants --
---------------
local USE_STACKING_TRANSPARENCY = true -- Multiple items between the subject and camera get transparency values that add up to TARGET_TRANSPARENCY
local TARGET_TRANSPARENCY = 0.75 -- Classic Invisicam's Value, also used by new invisicam for parts hit by head and torso rays
local TARGET_TRANSPARENCY_PERIPHERAL = 0.5 -- Used by new SMART_CIRCLE mode for items not hit by head and torso rays
local MODE = {
CUSTOM = 1, -- Whatever you want!
LIMBS = 2, -- Track limbs
MOVEMENT = 3, -- Track movement
CORNERS = 4, -- Char model corners
CIRCLE1 = 5, -- Circle of casts around character
CIRCLE2 = 6, -- Circle of casts around character, camera relative
LIMBMOVE = 7, -- LIMBS mode + MOVEMENT mode
SMART_CIRCLE = 8, -- More sample points on and around character
CHAR_OUTLINE = 9,
}
Invisicam.MODE = MODE
local STARTING_MODE = MODE.SMART_CIRCLE
local LIMB_TRACKING_SET = {
-- Common to R6, R15
['Head'] = true,
-- R6 Only
['Left Arm'] = true,
['Right Arm'] = true,
['Left Leg'] = true,
['Right Leg'] = true,
-- R15 Only
['LeftLowerArm'] = true,
['RightLowerArm'] = true,
['LeftUpperLeg'] = true,
['RightUpperLeg'] = true
}
local CORNER_FACTORS = {
Vector3.new(1,1,-1),
Vector3.new(1,-1,-1),
Vector3.new(-1,-1,-1),
Vector3.new(-1,1,-1)
}
local CIRCLE_CASTS = 10
local MOVE_CASTS = 3
local SMART_CIRCLE_CASTS = 24
local SMART_CIRCLE_INCREMENT = 2.0 * math.pi / SMART_CIRCLE_CASTS
local CHAR_OUTLINE_CASTS = 24
---------------
-- Variables --
---------------
local RunService = game:GetService('RunService')
local PlayersService = game:GetService('Players')
local Player = PlayersService.LocalPlayer
local Camera = nil
local Character = nil
local HumanoidRootPart = nil
local TorsoPart = nil
local HeadPart = nil
local Mode = nil
local BehaviorFunction = nil
local childAddedConn = nil
local childRemovedConn = nil
local Behaviors = {} -- Map of modes to behavior fns
local SavedHits = {} -- Objects currently being faded in/out
local TrackedLimbs = {} -- Used in limb-tracking casting modes
---------------
--| Utility |--
---------------
local math_min = math.min
local math_max = math.max
local math_cos = math.cos
local math_sin = math.sin
local math_pi = math.pi
local Vector3_new = Vector3.new
local ZERO_VECTOR3 = Vector3_new(0,0,0)
local function AssertTypes(param, ...)
local allowedTypes = {}
local typeString = ''
for _, typeName in pairs({...}) do
allowedTypes[typeName] = true
typeString = typeString .. (typeString == '' and '' or ' or ') .. typeName
end
local theType = type(param)
assert(allowedTypes[theType], typeString .. " type expected, got: " .. theType)
end
-----------------------
--| Local Functions |--
-----------------------
local function LimbBehavior(castPoints)
for limb, _ in pairs(TrackedLimbs) do
castPoints[#castPoints + 1] = limb.Position
end
end
local function MoveBehavior(castPoints)
for i = 1, MOVE_CASTS do
local position, velocity = HumanoidRootPart.Position, HumanoidRootPart.Velocity
local horizontalSpeed = Vector3_new(velocity.X, 0, velocity.Z).Magnitude / 2
local offsetVector = (i - 1) * HumanoidRootPart.CFrame.lookVector * horizontalSpeed
castPoints[#castPoints + 1] = position + offsetVector
end
end
local function CornerBehavior(castPoints)
local cframe = HumanoidRootPart.CFrame
local centerPoint = cframe.p
local rotation = cframe - centerPoint
local halfSize = Character:GetExtentsSize() / 2 --NOTE: Doesn't update w/ limb animations
castPoints[#castPoints + 1] = centerPoint
for i = 1, #CORNER_FACTORS do
castPoints[#castPoints + 1] = centerPoint + (rotation * (halfSize * CORNER_FACTORS[i]))
end
end
local function CircleBehavior(castPoints)
local cframe = nil
if Mode == MODE.CIRCLE1 then
cframe = HumanoidRootPart.CFrame
else
local camCFrame = Camera.CoordinateFrame
cframe = camCFrame - camCFrame.p + HumanoidRootPart.Position
end
castPoints[#castPoints + 1] = cframe.p
for i = 0, CIRCLE_CASTS - 1 do
local angle = (2 * math_pi / CIRCLE_CASTS) * i
local offset = 3 * Vector3_new(math_cos(angle), math_sin(angle), 0)
castPoints[#castPoints + 1] = cframe * offset
end
end
local function LimbMoveBehavior(castPoints)
LimbBehavior(castPoints)
MoveBehavior(castPoints)
end
local function CharacterOutlineBehavior(castPoints)
local torsoUp = TorsoPart.CFrame.upVector.unit
local torsoRight = TorsoPart.CFrame.rightVector.unit
-- Torso cross of points for interior coverage
castPoints[#castPoints + 1] = TorsoPart.CFrame.p
castPoints[#castPoints + 1] = TorsoPart.CFrame.p + torsoUp
castPoints[#castPoints + 1] = TorsoPart.CFrame.p - torsoUp
castPoints[#castPoints + 1] = TorsoPart.CFrame.p + torsoRight
castPoints[#castPoints + 1] = TorsoPart.CFrame.p - torsoRight
if HeadPart then
castPoints[#castPoints + 1] = HeadPart.CFrame.p
end
local cframe = CFrame.new(ZERO_VECTOR3,Vector3_new(Camera.CoordinateFrame.lookVector.X,0,Camera.CoordinateFrame.lookVector.Z))
local centerPoint = (TorsoPart and TorsoPart.Position or HumanoidRootPart.Position)
local partsWhitelist = {TorsoPart}
if HeadPart then
partsWhitelist[#partsWhitelist + 1] = HeadPart
end
for i = 1, CHAR_OUTLINE_CASTS do
local angle = (2 * math_pi * i / CHAR_OUTLINE_CASTS)
local offset = cframe * (3 * Vector3_new(math_cos(angle), math_sin(angle), 0))
offset = Vector3_new(offset.X, math_max(offset.Y, -2.25), offset.Z)
local ray = Ray.new(centerPoint + offset, -3 * offset)
local hit, hitPoint = game.Workspace:FindPartOnRayWithWhitelist(ray, partsWhitelist, false, false)
if hit then
-- Use hit point as the cast point, but nudge it slightly inside the character so that bumping up against
-- walls is less likely to cause a transparency glitch
castPoints[#castPoints + 1] = hitPoint + 0.2 * (centerPoint - hitPoint).unit
end
end
end
-- Helper function for Determinant of 3x3
local function Det3x3(a,b,c,d,e,f,g,h,i)
return (a*(e*i-f*h)-b*(d*i-f*g)+c*(d*h-e*g))
end
-- Smart Circle mode needs the intersection of 2 rays that are known to be in the same plane
-- because they are generated from cross products with a common vector. This function is computing
-- that intersection, but it's actually the general solution for the point halfway between where
-- two skew lines come nearest to each other, which is more forgiving.
local function RayIntersection(p0, v0, p1, v1)
local v2 = v0:Cross(v1)
local d1 = p1.x - p0.x
local d2 = p1.y - p0.y
local d3 = p1.z - p0.z
local denom = Det3x3(v0.x,-v1.x,v2.x,v0.y,-v1.y,v2.y,v0.z,-v1.z,v2.z)
if (denom == 0) then
return ZERO_VECTOR3 -- No solution (rays are parallel)
end
local t0 = Det3x3(d1,-v1.x,v2.x,d2,-v1.y,v2.y,d3,-v1.z,v2.z) / denom
local t1 = Det3x3(v0.x,d1,v2.x,v0.y,d2,v2.y,v0.z,d3,v2.z) / denom
local s0 = p0 + t0 * v0
local s1 = p1 + t1 * v1
local s = s0 + 0.5 * ( s1 - s0 )
-- 0.25 studs is a threshold for deciding if the rays are
-- close enough to be considered intersecting, found through testing
if (s1-s0).Magnitude < 0.25 then
return s
else
return ZERO_VECTOR3
end
end
local function SmartCircleBehavior(castPoints)
local torsoUp = TorsoPart.CFrame.upVector.unit
local torsoRight = TorsoPart.CFrame.rightVector.unit
-- SMART_CIRCLE mode includes rays to head and 5 to the torso.
-- Hands, arms, legs and feet are not included since they
-- are not canCollide and can therefore go inside of parts
castPoints[#castPoints + 1] = TorsoPart.CFrame.p
castPoints[#castPoints + 1] = TorsoPart.CFrame.p + torsoUp
castPoints[#castPoints + 1] = TorsoPart.CFrame.p - torsoUp
castPoints[#castPoints + 1] = TorsoPart.CFrame.p + torsoRight
castPoints[#castPoints + 1] = TorsoPart.CFrame.p - torsoRight
if HeadPart then
castPoints[#castPoints + 1] = HeadPart.CFrame.p
end
local cameraOrientation = Camera.CFrame - Camera.CFrame.p
local torsoPoint = Vector3_new(0,0.5,0) + (TorsoPart and TorsoPart.Position or HumanoidRootPart.Position)
local radius = 2.5
-- This loop first calculates points in a circle of radius 2.5 around the torso of the character, in the
-- plane orthogonal to the camera's lookVector. Each point is then raycast to, to determine if it is within
-- the free space surrounding the player (not inside anything). Two iterations are done to adjust points that
-- are inside parts, to try to move them to valid locations that are still on their camera ray, so that the
-- circle remains circular from the camera's perspective, but does not cast rays into walls or parts that are
-- behind, below or beside the character and not really obstructing view of the character. This minimizes
-- the undesirable situation where the character walks up to an exterior wall and it is made invisible even
-- though it is behind the character.
for i = 1, SMART_CIRCLE_CASTS do
local angle = SMART_CIRCLE_INCREMENT * i - 0.5 * math.pi
local offset = radius * Vector3_new(math_cos(angle), math_sin(angle), 0)
local circlePoint = torsoPoint + cameraOrientation * offset
-- Vector from camera to point on the circle being tested
local vp = circlePoint - Camera.CFrame.p
local ray = Ray.new(torsoPoint, circlePoint - torsoPoint)
local hit, hp, hitNormal = game.Workspace:FindPartOnRayWithIgnoreList(ray, {Character}, false, false )
local castPoint = circlePoint
if hit then
local hprime = hp + 0.1 * hitNormal.unit -- Slightly offset hit point from the hit surface
local v0 = hprime - torsoPoint -- Vector from torso to offset hit point
local d0 = v0.magnitude
local perp = (v0:Cross(vp)).unit
-- Vector from the offset hit point, along the hit surface
local v1 = (perp:Cross(hitNormal)).unit
-- Vector from camera to offset hit
local vprime = (hprime - Camera.CFrame.p).unit
-- This dot product checks to see if the vector along the hit surface would hit the correct
-- side of the invisicam cone, or if it would cross the camera look vector and hit the wrong side
if ( v0.unit:Dot(-v1) < v0.unit:Dot(vprime)) then
castPoint = RayIntersection(hprime, v1, circlePoint, vp)
if castPoint.Magnitude > 0 then
local ray = Ray.new(hprime, castPoint - hprime)
local hit, hitPoint, hitNormal = game.Workspace:FindPartOnRayWithIgnoreList(ray, {Character}, false, false )
if hit then
local hprime2 = hitPoint + 0.1 * hitNormal.unit
castPoint = hprime2
end
else
castPoint = hprime
end
else
castPoint = hprime
end
local ray = Ray.new(torsoPoint, (castPoint - torsoPoint))
local hit, hitPoint, hitNormal = game.Workspace:FindPartOnRayWithIgnoreList(ray, {Character}, false, false )
if hit then
local castPoint2 = hitPoint - 0.1 * (castPoint - torsoPoint).unit
castPoint = castPoint2
end
end
castPoints[#castPoints + 1] = castPoint
end
end
local function CheckTorsoReference()
if Character then
TorsoPart = Character:FindFirstChild("Torso")
if not TorsoPart then
TorsoPart = Character:FindFirstChild("UpperTorso")
if not TorsoPart then
TorsoPart = Character:FindFirstChild("HumanoidRootPart")
end
end
HeadPart = Character:FindFirstChild("Head")
end
end
local function OnCharacterAdded(character)
if childAddedConn then
childAddedConn:disconnect()
childAddedConn = nil
end
if childRemovedConn then
childRemovedConn:disconnect()
childRemovedConn = nil
end
Character = character
TrackedLimbs = {}
local function childAdded(child)
if child:IsA('BasePart') then
if LIMB_TRACKING_SET[child.Name] then
TrackedLimbs[child] = true
end
if (child.Name == 'Torso' or child.Name == 'UpperTorso') then
TorsoPart = child
end
if (child.Name == 'Head') then
HeadPart = child
end
end
end
local function childRemoved(child)
TrackedLimbs[child] = nil
-- If removed/replaced part is 'Torso' or 'UpperTorso' double check that we still have a TorsoPart to use
CheckTorsoReference()
end
childAddedConn = character.ChildAdded:connect(childAdded)
childRemovedConn = character.ChildRemoved:connect(childRemoved)
for _, child in pairs(Character:GetChildren()) do
childAdded(child)
end
end
local function OnCurrentCameraChanged()
local newCamera = workspace.CurrentCamera
if newCamera then
Camera = newCamera
end
end
-----------------------
-- Exposed Functions --
-----------------------
-- Update. Called every frame after the camera movement step
function Invisicam:Update()
-- Bail if there is no Character
if not Character then return end
-- Make sure we still have a HumanoidRootPart
if not HumanoidRootPart then
local humanoid = Character:FindFirstChildOfClass("Humanoid")
if humanoid and humanoid.Torso then
HumanoidRootPart = humanoid.Torso
else
-- Not set up with Humanoid? Try and see if there's one in the Character at all:
HumanoidRootPart = Character:FindFirstChild("HumanoidRootPart")
if not HumanoidRootPart then
-- Bail out, since we're relying on HumanoidRootPart existing
return
end
end
local ancestryChangedConn;
ancestryChangedConn = HumanoidRootPart.AncestryChanged:connect(function(child, parent)
if child == HumanoidRootPart and not parent then
HumanoidRootPart = nil
if ancestryChangedConn and ancestryChangedConn.Connected then
ancestryChangedConn:Disconnect()
ancestryChangedConn = nil
end
end
end)
end
if not TorsoPart then
CheckTorsoReference()
if not TorsoPart then
-- Bail out, since we're relying on Torso existing, should never happen since we fall back to using HumanoidRootPart as torso
return
end
end
-- Make a list of world points to raycast to
local castPoints = {}
BehaviorFunction(castPoints)
-- Cast to get a list of objects between the camera and the cast points
local currentHits = {}
local ignoreList = {Character}
local function add(hit)
currentHits[hit] = true
if not SavedHits[hit] then
SavedHits[hit] = hit.LocalTransparencyModifier
end
end
local hitParts
local hitPartCount = 0
-- Hash table to treat head-ray-hit parts differently than the rest of the hit parts hit by other rays
-- head/torso ray hit parts will be more transparent than peripheral parts when USE_STACKING_TRANSPARENCY is enabled
local headTorsoRayHitParts = {}
local partIsTouchingCamera = {}
local perPartTransparencyHeadTorsoHits = TARGET_TRANSPARENCY
local perPartTransparencyOtherHits = TARGET_TRANSPARENCY
if USE_STACKING_TRANSPARENCY then
-- This first call uses head and torso rays to find out how many parts are stacked up
-- for the purpose of calculating required per-part transparency
local headPoint = HeadPart and HeadPart.CFrame.p or castPoints[1]
local torsoPoint = TorsoPart and TorsoPart.CFrame.p or castPoints[2]
hitParts = Camera:GetPartsObscuringTarget({headPoint, torsoPoint}, ignoreList)
-- Count how many things the sample rays passed through, including decals. This should only
-- count decals facing the camera, but GetPartsObscuringTarget does not return surface normals,
-- so my compromise for now is to just let any decal increase the part count by 1. Only one
-- decal per part will be considered.
for i = 1, #hitParts do
local hitPart = hitParts[i]
hitPartCount = hitPartCount + 1 -- count the part itself
headTorsoRayHitParts[hitPart] = true
for _, child in pairs(hitPart:GetChildren()) do
if child:IsA('Decal') or child:IsA('Texture') then
hitPartCount = hitPartCount + 1 -- count first decal hit, then break
break
end
end
end
if (hitPartCount > 0) then
perPartTransparencyHeadTorsoHits = math.pow( ((0.5 * TARGET_TRANSPARENCY) + (0.5 * TARGET_TRANSPARENCY / hitPartCount)), 1 / hitPartCount )
perPartTransparencyOtherHits = math.pow( ((0.5 * TARGET_TRANSPARENCY_PERIPHERAL) + (0.5 * TARGET_TRANSPARENCY_PERIPHERAL / hitPartCount)), 1 / hitPartCount )
end
end
-- Now get all the parts hit by all the rays
hitParts = Camera:GetPartsObscuringTarget(castPoints, ignoreList)
local partTargetTransparency = {}
-- Include decals and textures
for i = 1, #hitParts do
local hitPart = hitParts[i]
partTargetTransparency[hitPart] =headTorsoRayHitParts[hitPart] and perPartTransparencyHeadTorsoHits or perPartTransparencyOtherHits
-- If the part is not already as transparent or more transparent than what invisicam requires, add it to the list of
-- parts to be modified by invisicam
if hitPart.Transparency < partTargetTransparency[hitPart] then
add(hitPart)
end
-- Check all decals and textures on the part
for _, child in pairs(hitPart:GetChildren()) do
if child:IsA('Decal') or child:IsA('Texture') then
if (child.Transparency < partTargetTransparency[hitPart]) then
partTargetTransparency[child] = partTargetTransparency[hitPart]
add(child)
end
end
end
end
-- Invisibilize objects that are in the way, restore those that aren't anymore
for hitPart, originalLTM in pairs(SavedHits) do
if currentHits[hitPart] then
-- LocalTransparencyModifier gets whatever value is required to print the part's total transparency to equal perPartTransparency
hitPart.LocalTransparencyModifier = (hitPart.Transparency < 1) and ((partTargetTransparency[hitPart] - hitPart.Transparency) / (1.0 - hitPart.Transparency)) or 0
else -- Restore original pre-invisicam value of LTM
hitPart.LocalTransparencyModifier = originalLTM
SavedHits[hitPart] = nil
end
end
end
function Invisicam:SetMode(newMode)
AssertTypes(newMode, 'number')
for modeName, modeNum in pairs(MODE) do
if modeNum == newMode then
Mode = newMode
BehaviorFunction = Behaviors[Mode]
return
end
end
error("Invalid mode number")
end
function Invisicam:SetCustomBehavior(func)
AssertTypes(func, 'function')
Behaviors[MODE.CUSTOM] = func
if Mode == MODE.CUSTOM then
BehaviorFunction = func
end
end
function Invisicam:GetObscuredParts()
return SavedHits
end
-- Want to turn off Invisicam? Be sure to call this after.
function Invisicam:Cleanup()
for hit, originalFade in pairs(SavedHits) do
hit.LocalTransparencyModifier = originalFade
end
end
---------------------
--| Running Logic |--
---------------------
-- Connect to the current and all future cameras
workspace:GetPropertyChangedSignal("CurrentCamera"):connect(OnCurrentCameraChanged)
OnCurrentCameraChanged()
Player.CharacterAdded:connect(OnCharacterAdded)
if Player.Character then
OnCharacterAdded(Player.Character)
end
Behaviors[MODE.CUSTOM] = function() end -- (Does nothing until SetCustomBehavior)
Behaviors[MODE.LIMBS] = LimbBehavior
Behaviors[MODE.MOVEMENT] = MoveBehavior
Behaviors[MODE.CORNERS] = CornerBehavior
Behaviors[MODE.CIRCLE1] = CircleBehavior
Behaviors[MODE.CIRCLE2] = CircleBehavior
Behaviors[MODE.LIMBMOVE] = LimbMoveBehavior
Behaviors[MODE.SMART_CIRCLE] = SmartCircleBehavior
Behaviors[MODE.CHAR_OUTLINE] = CharacterOutlineBehavior
Invisicam:SetMode(STARTING_MODE)
return Invisicam
-- Written By Kip Turner, Copyright Roblox 2014
-- Updated by Garnold to utilize the new PathfindingService API, 2017
local UIS = game:GetService("UserInputService")
local PathfindingService = game:GetService("PathfindingService")
local PlayerService = game:GetService("Players")
local RunService = game:GetService("RunService")
local DebrisService = game:GetService('Debris')
local ReplicatedStorage = game:GetService('ReplicatedStorage')
local tweenService = game:GetService("TweenService")
local CameraScript = script:FindFirstAncestor("CameraScript")
local InvisicamModule = require(CameraScript:WaitForChild("Invisicam"))
local OrbitalCamModule = require(CameraScript:WaitForChild('RootCamera'):WaitForChild('OrbitalCamera'))
local Player = PlayerService.LocalPlayer
local PlayerScripts = Player.PlayerScripts
local ControlScript = PlayerScripts:FindFirstChild("ControlScript")
local MasterControl, TouchJump
if ControlScript then
MasterControl = ControlScript:FindFirstChild("MasterControl")
if MasterControl then
local TouchJumpModule = MasterControl:FindFirstChild("TouchJump")
if TouchJumpModule then
TouchJump = require(TouchJumpModule)
end
end
end
local SHOW_PATH = true
local RayCastIgnoreList = workspace.FindPartOnRayWithIgnoreList
local math_min = math.min
local math_max = math.max
local math_pi = math.pi
local math_atan2 = math.atan2
local Vector3_new = Vector3.new
local Vector2_new = Vector2.new
local CFrame_new = CFrame.new
local CurrentSeatPart = nil
local DrivingTo = nil
local XZ_VECTOR3 = Vector3_new(1, 0, 1)
local ZERO_VECTOR3 = Vector3_new(0, 0, 0)
local ZERO_VECTOR2 = Vector2_new(0, 0)
local lastFailedPosition = nil
local BindableEvent_OnFailStateChanged = nil
if UIS.TouchEnabled then
BindableEvent_OnFailStateChanged = Instance.new('BindableEvent')
BindableEvent_OnFailStateChanged.Name = "OnClickToMoveFailStateChange"
BindableEvent_OnFailStateChanged.Parent = PlayerScripts
end
--------------------------UTIL LIBRARY-------------------------------
local Utility = {}
do
local Signal = {}
function Signal.Create()
local sig = {}
local mSignaler = Instance.new('BindableEvent')
local mArgData = nil
local mArgDataCount = nil
function sig:fire(...)
mArgData = {...}
mArgDataCount = select('#', ...)
mSignaler:Fire()
end
function sig:connect(f)
if not f then error("connect(nil)", 2) end
return mSignaler.Event:connect(function()
f(unpack(mArgData, 1, mArgDataCount))
end)
end
function sig:wait()
mSignaler.Event:wait()
assert(mArgData, "Missing arg data, likely due to :TweenSize/Position corrupting threadrefs.")
return unpack(mArgData, 1, mArgDataCount)
end
return sig
end
Utility.Signal = Signal
function Utility.Create(instanceType)
return function(data)
local obj = Instance.new(instanceType)
for k, v in pairs(data) do
if type(k) == 'number' then
v.Parent = obj
else
obj[k] = v
end
end
return obj
end
end
local function ViewSizeX()
local camera = workspace.CurrentCamera
local x = camera and camera.ViewportSize.X or 0
local y = camera and camera.ViewportSize.Y or 0
if x == 0 then
return 1024
else
if x > y then
return x
else
return y
end
end
end
Utility.ViewSizeX = ViewSizeX
local function ViewSizeY()
local camera = workspace.CurrentCamera
local x = camera and camera.ViewportSize.X or 0
local y = camera and camera.ViewportSize.Y or 0
if y == 0 then
return 768
else
if x > y then
return y
else
return x
end
end
end
Utility.ViewSizeY = ViewSizeY
local function FindChacterAncestor(part)
if part then
local humanoid = part:FindFirstChild("Humanoid")
if humanoid then
return part, humanoid
else
return FindChacterAncestor(part.Parent)
end
end
end
Utility.FindChacterAncestor = FindChacterAncestor
local function Raycast(ray, ignoreNonCollidable, ignoreList)
local ignoreList = ignoreList or {}
local hitPart, hitPos, hitNorm, hitMat = RayCastIgnoreList(workspace, ray, ignoreList)
if hitPart then
if ignoreNonCollidable and hitPart.CanCollide == false then
table.insert(ignoreList, hitPart)
return Raycast(ray, ignoreNonCollidable, ignoreList)
end
return hitPart, hitPos, hitNorm, hitMat
end
return nil, nil
end
Utility.Raycast = Raycast
local function AveragePoints(positions)
local avgPos = ZERO_VECTOR2
if #positions > 0 then
for i = 1, #positions do
avgPos = avgPos + positions[i]
end
avgPos = avgPos / #positions
end
return avgPos
end
Utility.AveragePoints = AveragePoints
local function FuzzyEquals(numa, numb)
return numa + 0.1 > numb and numa - 0.1 < numb
end
Utility.FuzzyEquals = FuzzyEquals
local LastInput = 0
UIS.InputBegan:connect(function(inputObject, wasSunk)
if not wasSunk then
if inputObject.UserInputType == Enum.UserInputType.Touch or
inputObject.UserInputType == Enum.UserInputType.MouseButton1 or
inputObject.UserInputType == Enum.UserInputType.MouseButton2 then
LastInput = tick()
end
end
end)
Utility.GetLastInput = function()
return LastInput
end
end
local humanoidCache = {}
local function findPlayerHumanoid(player)
local character = player and player.Character
if character then
local resultHumanoid = humanoidCache[player]
if resultHumanoid and resultHumanoid.Parent == character then
return resultHumanoid
else
humanoidCache[player] = nil -- Bust Old Cache
local humanoid = character:FindFirstChildOfClass("Humanoid")
if humanoid then
humanoidCache[player] = humanoid
end
return humanoid
end
end
end
---------------------------------------------------------
local Signal = Utility.Signal
local Create = Utility.Create
--------------------------CHARACTER CONTROL-------------------------------
local CurrentIgnoreList
local function GetCharacter()
return Player and Player.Character
end
local function GetTorso()
local humanoid = findPlayerHumanoid(Player)
return humanoid and humanoid.Torso
end
local function getIgnoreList()
if CurrentIgnoreList then
return CurrentIgnoreList
end
CurrentIgnoreList = {}
table.insert(CurrentIgnoreList, GetCharacter())
return CurrentIgnoreList
end
-----------------------------------------------------------------------------
-----------------------------------PATHER--------------------------------------
local function createNewPopup(popupType)
local newModel = Instance.new("ImageHandleAdornment")
newModel.AlwaysOnTop = false
newModel.Image = "rbxasset://textures/Cursors/Gamepad/[email protected]"
newModel.ZIndex = 2
local size = ZERO_VECTOR2
if popupType == "DestinationPopup" then
newModel.Color3 = Color3.fromRGB(0, 175, 255)
size = Vector2.new(4,4)
elseif popupType == "DirectWalkPopup" then
newModel.Color3 = Color3.fromRGB(255, 255, 100)
size = Vector2.new(4,4)
elseif popupType == "FailurePopup" then
newModel.Color3 = Color3.fromRGB(255, 100, 100)
size = Vector2.new(4,4)
elseif popupType == "PatherPopup" then
newModel.Color3 = Color3.fromRGB(255, 255, 255)
size = Vector2.new(3,3)
newModel.ZIndex = 1
end
local dataStructure = {}
dataStructure.Model = newModel
function dataStructure:TweenIn()
local tween1 = tweenService:Create(self.Model,
TweenInfo.new(
1,
Enum.EasingStyle.Elastic,
Enum.EasingDirection.Out,
0,
false,
0
),{
Size = size
}
)
tween1:Play()
return tween1
end
function dataStructure:TweenOut()
local tween1 = tweenService:Create(self.Model,
TweenInfo.new(
.25,
Enum.EasingStyle.Quad,
Enum.EasingDirection.In,
0,
false,
0
),{
Size = ZERO_VECTOR2
}
)
tween1:Play()
return tween1
end
function dataStructure:Place(position, dest)
-- place the model at position
if not self.Model.Parent then
self.Model.Parent = workspace.Terrain
self.Model.Adornee = workspace.Terrain
self.Model.CFrame = CFrame.new(position,dest)*CFrame.Angles(math.pi/2,0,0)-Vector3.new(0,-.2,0)
end
end
return dataStructure
end
local function createPopupPath(points, numCircles)
-- creates a path with the provided points, using the path and number of circles provided
local popups = {}
local stopTraversing = false
local function killPopup(i)
-- kill all popups before and at i
for iter, v in pairs(popups) do
if iter <= i then
local tween = v:TweenOut()
spawn(function()
tween.Completed:wait()
v.Model:Destroy()
end)
popups[iter] = nil
end
end
end
local function stopFunction()
stopTraversing = true
killPopup(#points)
end
spawn(function()
for i = 1, #points do
if stopTraversing then
break
end
local includeWaypoint = i % numCircles == 0
and i < #points
and (points[#points].Position - points[i].Position).magnitude > 4
if includeWaypoint then
local popup = createNewPopup("PatherPopup")
popups[i] = popup
local nextPopup = points[i+1]
popup:Place(points[i].Position, nextPopup and nextPopup.Position or points[#points].Position)
local tween = popup:TweenIn()
wait(0.2)
end
end
end)
return stopFunction, killPopup
end
local function Pather(character, endPoint, surfaceNormal)
local this = {}
this.Cancelled = false
this.Started = false
this.Finished = Signal.Create()
this.PathFailed = Signal.Create()
this.PathStarted = Signal.Create()
this.PathComputing = false
this.PathComputed = false
this.TargetPoint = endPoint
this.TargetSurfaceNormal = surfaceNormal
this.MoveToConn = nil
this.CurrentPoint = 0
function this:Cleanup()
if this.stopTraverseFunc then
this.stopTraverseFunc()
end
if this.MoveToConn then
this.MoveToConn:disconnect()
this.MoveToConn = nil
this.humanoid = nil
end
this.humanoid = nil
end
function this:Cancel()
this.Cancelled = true
this:Cleanup()
end
function this:ComputePath()
local humanoid = findPlayerHumanoid(Player)
local torso = humanoid and humanoid.Torso
local success = false
if torso then
if this.PathComputed or this.PathComputing then return end
this.PathComputing = true
success = pcall(function()
this.pathResult = PathfindingService:FindPathAsync(torso.CFrame.p, this.TargetPoint)
end)
this.pointList = this.pathResult and this.pathResult:GetWaypoints()
this.PathComputing = false
this.PathComputed = this.pathResult and this.pathResult.Status == Enum.PathStatus.Success or false
end
return true
end
function this:IsValidPath()
if not this.pathResult then
this:ComputePath()
end
return this.pathResult.Status == Enum.PathStatus.Success
end
function this:OnPointReached(reached)
if reached and not this.Cancelled then
this.CurrentPoint = this.CurrentPoint + 1
if this.CurrentPoint > #this.pointList then
-- End of path reached
if this.stopTraverseFunc then
this.stopTraverseFunc()
end
this.Finished:fire()
this:Cleanup()
else
-- If next action == Jump, but the humanoid
-- is still jumping from a previous action
-- wait until it gets to the ground
if this.CurrentPoint + 1 <= #this.pointList then
local nextAction = this.pointList[this.CurrentPoint + 1].Action
if nextAction == Enum.PathWaypointAction.Jump then
local currentState = this.humanoid:GetState()
if currentState == Enum.HumanoidStateType.FallingDown or
currentState == Enum.HumanoidStateType.Freefall or
currentState == Enum.HumanoidStateType.Jumping then
this.humanoid.FreeFalling:wait()
-- Give time to the humanoid's state to change
-- Otherwise, the jump flag in Humanoid
-- will be reset by the state change
wait(0.1)
end
end
end
-- Move to the next point
if this.setPointFunc then
this.setPointFunc(this.CurrentPoint)
end
local nextWaypoint = this.pointList[this.CurrentPoint]
if nextWaypoint.Action == Enum.PathWaypointAction.Jump then
this.humanoid.Jump = true
end
this.humanoid:MoveTo(nextWaypoint.Position)
end
else
this.PathFailed:fire()
this:Cleanup()
end
end
function this:Start()
if CurrentSeatPart then
return
end
this.humanoid = findPlayerHumanoid(Player)
if this.Started then return end
this.Started = true
if SHOW_PATH then
-- choose whichever one Mike likes best
this.stopTraverseFunc, this.setPointFunc = createPopupPath(this.pointList, 4)
end
if #this.pointList > 0 then
this.MoveToConn = this.humanoid.MoveToFinished:connect(function(reached) this:OnPointReached(reached) end)
this.CurrentPoint = 1 -- The first waypoint is always the start location. Skip it.
this:OnPointReached(true) -- Move to first point
else
this.PathFailed:fire()
if this.stopTraverseFunc then
this.stopTraverseFunc()
end
end
end
this:ComputePath()
if not this.PathComputed then
-- set the end point towards the camera and raycasted towards the ground in case we hit a wall
local offsetPoint = this.TargetPoint + this.TargetSurfaceNormal*1.5
local ray = Ray.new(offsetPoint, Vector3_new(0,-1,0)*50)
local newHitPart, newHitPos = RayCastIgnoreList(workspace, ray, getIgnoreList())
if newHitPart then
this.TargetPoint = newHitPos
end
-- try again
this:ComputePath()
end
return this
end
-------------------------------------------------------------------------
local function IsInBottomLeft(pt)
local joystickHeight = math_min(Utility.ViewSizeY() * 0.33, 250)
local joystickWidth = joystickHeight
return pt.X <= joystickWidth and pt.Y > Utility.ViewSizeY() - joystickHeight
end
local function IsInBottomRight(pt)
local joystickHeight = math_min(Utility.ViewSizeY() * 0.33, 250)
local joystickWidth = joystickHeight
return pt.X >= Utility.ViewSizeX() - joystickWidth and pt.Y > Utility.ViewSizeY() - joystickHeight
end
local function CheckAlive(character)
local humanoid = findPlayerHumanoid(Player)
return humanoid ~= nil and humanoid.Health > 0
end
local function GetEquippedTool(character)
if character ~= nil then
for _, child in pairs(character:GetChildren()) do
if child:IsA('Tool') then
return child
end
end
end
end
local ExistingPather = nil
local ExistingIndicator = nil
local PathCompleteListener = nil
local PathFailedListener = nil
local function CleanupPath()
DrivingTo = nil
if ExistingPather then
ExistingPather:Cancel()
end
if PathCompleteListener then
PathCompleteListener:disconnect()
PathCompleteListener = nil
end
if PathFailedListener then
PathFailedListener:disconnect()
PathFailedListener = nil
end
if ExistingIndicator then
local obj = ExistingIndicator
local tween = obj:TweenOut()
local tweenCompleteEvent = nil
tweenCompleteEvent = tween.Completed:connect(function()
tweenCompleteEvent:disconnect()
obj.Model:Destroy()
end)
ExistingIndicator = nil
end
end
local function getExtentsSize(Parts)
local maxX,maxY,maxZ = -math.huge,-math.huge,-math.huge
local minX,minY,minZ = math.huge,math.huge,math.huge
for i = 1, #Parts do
maxX,maxY,maxZ = math_max(maxX, Parts[i].Position.X), math_max(maxY, Parts[i].Position.Y), math_max(maxZ, Parts[i].Position.Z)
minX,minY,minZ = math_min(minX, Parts[i].Position.X), math_min(minY, Parts[i].Position.Y), math_min(minZ, Parts[i].Position.Z)
end
return Region3.new(Vector3_new(minX, minY, minZ), Vector3_new(maxX, maxY, maxZ))
end
local function inExtents(Extents, Position)
if Position.X < (Extents.CFrame.p.X - Extents.Size.X/2) or Position.X > (Extents.CFrame.p.X + Extents.Size.X/2) then
return false
end
if Position.Z < (Extents.CFrame.p.Z - Extents.Size.Z/2) or Position.Z > (Extents.CFrame.p.Z + Extents.Size.Z/2) then
return false
end
--ignoring Y for now
return true
end
local FailCount = 0
local function OnTap(tapPositions, goToPoint)
-- Good to remember if this is the latest tap event
local camera = workspace.CurrentCamera
local character = Player.Character
if not CheckAlive(character) then return end
-- This is a path tap position
if #tapPositions == 1 or goToPoint then
if camera then
local unitRay = camera:ScreenPointToRay(tapPositions[1].x, tapPositions[1].y)
local ray = Ray.new(unitRay.Origin, unitRay.Direction*400)
-- inivisicam stuff
local initIgnore = getIgnoreList()
local invisicamParts = InvisicamModule:GetObscuredParts()
local ignoreTab = {}
-- add to the ignore list
for i, v in pairs(invisicamParts) do
ignoreTab[#ignoreTab+1] = i
end
for i = 1, #initIgnore do
ignoreTab[#ignoreTab+1] = initIgnore[i]
end
--
local myHumanoid = findPlayerHumanoid(Player)
local hitPart, hitPt, hitNormal, hitMat = Utility.Raycast(ray, true, ignoreTab)
local hitChar, hitHumanoid = Utility.FindChacterAncestor(hitPart)
local torso = GetTorso()
local startPos = torso.CFrame.p
if goToPoint then
hitPt = goToPoint
hitChar = nil
end
if hitChar and hitHumanoid and hitHumanoid.Torso and (hitHumanoid.Torso.CFrame.p - torso.CFrame.p).magnitude < 7 then
CleanupPath()
if myHumanoid then
myHumanoid:MoveTo(hitPt)
end
-- Do shoot
local currentWeapon = GetEquippedTool(character)
if currentWeapon then
currentWeapon:Activate()
LastFired = tick()
end
elseif hitPt and character and not CurrentSeatPart then
local thisPather = Pather(character, hitPt, hitNormal)
if thisPather:IsValidPath() then
FailCount = 0
thisPather:Start()
if BindableEvent_OnFailStateChanged then
BindableEvent_OnFailStateChanged:Fire(false)
end
CleanupPath()
local destinationPopup = createNewPopup("DestinationPopup")
destinationPopup:Place(hitPt, Vector3_new(0,hitPt.y,0))
local failurePopup = createNewPopup("FailurePopup")
local currentTween = destinationPopup:TweenIn()
ExistingPather = thisPather
ExistingIndicator = destinationPopup
PathCompleteListener = thisPather.Finished:connect(function()
if destinationPopup then
if ExistingIndicator == destinationPopup then
ExistingIndicator = nil
end
local tween = destinationPopup:TweenOut()
local tweenCompleteEvent = nil
tweenCompleteEvent = tween.Completed:connect(function()
tweenCompleteEvent:disconnect()
destinationPopup.Model:Destroy()
destinationPopup = nil
end)
end
if hitChar then
local humanoid = findPlayerHumanoid(Player)
local currentWeapon = GetEquippedTool(character)
if currentWeapon then
currentWeapon:Activate()
LastFired = tick()
end
if humanoid then
humanoid:MoveTo(hitPt)
end
end
end)
PathFailedListener = thisPather.PathFailed:connect(function()
if failurePopup then
failurePopup:Place(hitPt, Vector3_new(0,hitPt.y,0))
local failTweenIn = failurePopup:TweenIn()
failTweenIn.Completed:wait()
local failTweenOut = failurePopup:TweenOut()
failTweenOut.Completed:wait()
failurePopup.Model:Destroy()
failurePopup = nil
end
end)
else
if hitPt then
-- Feedback here for when we don't have a good path
local foundDirectPath = false
if (hitPt-startPos).Magnitude < 25 and (startPos.y-hitPt.y > -3) then
-- move directly here
if myHumanoid then
if myHumanoid.Sit then
myHumanoid.Jump = true
end
local currentPosition
myHumanoid:MoveTo(hitPt)
foundDirectPath = true
end
end
spawn(function()
local directPopup = createNewPopup(foundDirectPath and "DirectWalkPopup" or "FailurePopup")
directPopup:Place(hitPt, Vector3_new(0,hitPt.y,0))
local directTweenIn = directPopup:TweenIn()
directTweenIn.Completed:wait()
local directTweenOut = directPopup:TweenOut()
directTweenOut.Completed:wait()
directPopup.Model:Destroy()
directPopup = nil
end)
end
end
elseif hitPt and character and CurrentSeatPart then
local destinationPopup = createNewPopup("DestinationPopup")
ExistingIndicator = destinationPopup
destinationPopup:Place(hitPt, Vector3_new(0,hitPt.y,0))
destinationPopup:TweenIn()
DrivingTo = hitPt
local ConnectedParts = CurrentSeatPart:GetConnectedParts(true)
while wait() do
if CurrentSeatPart and ExistingIndicator == destinationPopup then
local ExtentsSize = getExtentsSize(ConnectedParts)
if inExtents(ExtentsSize, hitPt) then
local popup = destinationPopup
spawn(function()
local tweenOut = popup:TweenOut()
tweenOut.Completed:wait()
popup.Model:Destroy()
end)
destinationPopup = nil
DrivingTo = nil
break
end
else
if CurrentSeatPart == nil and destinationPopup == ExistingIndicator then
DrivingTo = nil
OnTap(tapPositions, hitPt)
end
local popup = destinationPopup
spawn(function()
local tweenOut = popup:TweenOut()
tweenOut.Completed:wait()
popup.Model:Destroy()
end)
destinationPopup = nil
break
end
end
end
end
elseif #tapPositions >= 2 then
if camera then
-- Do shoot
local avgPoint = Utility.AveragePoints(tapPositions)
local unitRay = camera:ScreenPointToRay(avgPoint.x, avgPoint.y)
local currentWeapon = GetEquippedTool(character)
if currentWeapon then
currentWeapon:Activate()
LastFired = tick()
end
end
end
end
local function CreateClickToMoveModule()
local this = {}
local LastStateChange = 0
local LastState = Enum.HumanoidStateType.Running
local FingerTouches = {}
local NumUnsunkTouches = 0
-- PC simulation
local mouse1Down = tick()
local mouse1DownPos = Vector2_new()
local mouse2Down = tick()
local mouse2DownPos = Vector2_new()
local mouse2Up = tick()
local movementKeys = {
[Enum.KeyCode.W] = true;
[Enum.KeyCode.A] = true;
[Enum.KeyCode.S] = true;
[Enum.KeyCode.D] = true;
[Enum.KeyCode.Up] = true;
[Enum.KeyCode.Down] = true;
}
local TapConn = nil
local InputBeganConn = nil
local InputChangedConn = nil
local InputEndedConn = nil
local HumanoidDiedConn = nil
local CharacterChildAddedConn = nil
local OnCharacterAddedConn = nil
local CharacterChildRemovedConn = nil
local RenderSteppedConn = nil
local HumanoidSeatedConn = nil
local function disconnectEvent(event)
if event then
event:disconnect()
end
end
local function DisconnectEvents()
disconnectEvent(TapConn)
disconnectEvent(InputBeganConn)
disconnectEvent(InputChangedConn)
disconnectEvent(InputEndedConn)
disconnectEvent(HumanoidDiedConn)
disconnectEvent(CharacterChildAddedConn)
disconnectEvent(OnCharacterAddedConn)
disconnectEvent(RenderSteppedConn)
disconnectEvent(CharacterChildRemovedConn)
pcall(function() RunService:UnbindFromRenderStep("ClickToMoveRenderUpdate") end)
disconnectEvent(HumanoidSeatedConn)
end
local function IsFinite(num)
return num == num and num ~= 1/0 and num ~= -1/0
end
local function findAngleBetweenXZVectors(vec2, vec1)
return math_atan2(vec1.X*vec2.Z-vec1.Z*vec2.X, vec1.X*vec2.X + vec1.Z*vec2.Z)
end
-- Setup the camera
CameraModule = OrbitalCamModule()
local function OnTouchBegan(input, processed)
if FingerTouches[input] == nil and not processed then
NumUnsunkTouches = NumUnsunkTouches + 1
end
FingerTouches[input] = processed
end
local function OnTouchChanged(input, processed)
if FingerTouches[input] == nil then
FingerTouches[input] = processed
if not processed then
NumUnsunkTouches = NumUnsunkTouches + 1
end
end
end
local function OnTouchEnded(input, processed)
if FingerTouches[input] ~= nil and FingerTouches[input] == false then
NumUnsunkTouches = NumUnsunkTouches - 1
end
FingerTouches[input] = nil
end
local function OnCharacterAdded(character)
DisconnectEvents()
InputBeganConn = UIS.InputBegan:connect(function(input, processed)
if input.UserInputType == Enum.UserInputType.Touch then
OnTouchBegan(input, processed)
-- Give back controls when they tap both sticks
local wasInBottomLeft = IsInBottomLeft(input.Position)
local wasInBottomRight = IsInBottomRight(input.Position)
if wasInBottomRight or wasInBottomLeft then
for otherInput, _ in pairs(FingerTouches) do
if otherInput ~= input then
local otherInputInLeft = IsInBottomLeft(otherInput.Position)
local otherInputInRight = IsInBottomRight(otherInput.Position)
if otherInput.UserInputState ~= Enum.UserInputState.End and ((wasInBottomLeft and otherInputInRight) or (wasInBottomRight and otherInputInLeft)) then
if BindableEvent_OnFailStateChanged then
BindableEvent_OnFailStateChanged:Fire(true)
end
return
end
end
end
end
end
-- Cancel path when you use the keyboard controls.
if processed == false and input.UserInputType == Enum.UserInputType.Keyboard and movementKeys[input.KeyCode] then
CleanupPath()
end
if input.UserInputType == Enum.UserInputType.MouseButton1 then
mouse1Down = tick()
mouse1DownPos = input.Position
end
if input.UserInputType == Enum.UserInputType.MouseButton2 then
mouse2Down = tick()
mouse2DownPos = input.Position
end
end)
InputChangedConn = UIS.InputChanged:connect(function(input, processed)
if input.UserInputType == Enum.UserInputType.Touch then
OnTouchChanged(input, processed)
end
end)
InputEndedConn = UIS.InputEnded:connect(function(input, processed)
if input.UserInputType == Enum.UserInputType.Touch then
OnTouchEnded(input, processed)
end
if input.UserInputType == Enum.UserInputType.MouseButton2 then
mouse2Up = tick()
local currPos = input.Position
if mouse2Up - mouse2Down < 0.25 and (currPos - mouse2DownPos).magnitude < 5 then
local positions = {currPos}
OnTap(positions)
end
end
end)
TapConn = UIS.TouchTap:connect(function(touchPositions, processed)
if not processed then
OnTap(touchPositions)
end
end)
local function computeThrottle(dist)
if dist > .2 then
return 0.5+(dist^2)/2
else
return 0
end
end
local function getThrottleAndSteer(object, point)
local throttle, steer = 0, 0
local oCF = object.CFrame
local relativePosition = oCF:pointToObjectSpace(point)
local relativeZDirection = -relativePosition.z
local relativeDistance = relativePosition.magnitude
-- throttle quadratically increases from 0-1 as distance from the selected point goes from 0-50, after 50, throttle is 1.
-- this allows shorter distance travel to have more fine-tuned control.
throttle = computeThrottle(math_min(1,relativeDistance/50))*math.sign(relativeZDirection)
local steerAngle = -math_atan2(-relativePosition.x, -relativePosition.z)
steer = steerAngle/(math_pi/4)
return throttle, steer
end
local function Update()
if CameraModule then
CameraModule.UserPanningTheCamera = true
if CameraModule.UserPanningTheCamera then
CameraModule.UpdateTweenFunction = nil
else
if CameraModule.UpdateTweenFunction then
local done = CameraModule.UpdateTweenFunction()
if done then
CameraModule.UpdateTweenFunction = nil
end
end
end
CameraModule:Update()
end
if CurrentSeatPart then
if DrivingTo then
local throttle, steer = getThrottleAndSteer(CurrentSeatPart, DrivingTo)
CurrentSeatPart.ThrottleFloat = throttle
CurrentSeatPart.SteerFloat = steer
else
CurrentSeatPart.ThrottleFloat = 0
CurrentSeatPart.SteerFloat = 0
end
end
end
RunService:BindToRenderStep("ClickToMoveRenderUpdate",Enum.RenderPriority.Camera.Value - 1,Update)
local function onSeated(child, active, currentSeatPart)
if active then
if TouchJump and UIS.TouchEnabled then
TouchJump:Enable()
end
if currentSeatPart and currentSeatPart.ClassName == "VehicleSeat" then
CurrentSeatPart = currentSeatPart
end
else
CurrentSeatPart = nil
if TouchJump and UIS.TouchEnabled then
TouchJump:Disable()
end
end
end
local function OnCharacterChildAdded(child)
if UIS.TouchEnabled then
if child:IsA('Tool') then
child.ManualActivationOnly = true
end
end
if child:IsA('Humanoid') then
disconnectEvent(HumanoidDiedConn)
HumanoidDiedConn = child.Died:connect(function()
if ExistingIndicator then
DebrisService:AddItem(ExistingIndicator.Model, 1)
end
end)
HumanoidSeatedConn = child.Seated:connect(function(active, seat) onSeated(child, active, seat) end)
if child.SeatPart then
onSeated(child, true, child.SeatPart)
end
end
end
CharacterChildAddedConn = character.ChildAdded:connect(function(child)
OnCharacterChildAdded(child)
end)
CharacterChildRemovedConn = character.ChildRemoved:connect(function(child)
if UIS.TouchEnabled then
if child:IsA('Tool') then
child.ManualActivationOnly = false
end
end
end)
for _, child in pairs(character:GetChildren()) do
OnCharacterChildAdded(child)
end
end
local Running = false
function this:Stop()
if Running then
DisconnectEvents()
CleanupPath()
if CameraModule then
CameraModule.UpdateTweenFunction = nil
CameraModule:SetEnabled(false)
end
-- Restore tool activation on shutdown
if UIS.TouchEnabled then
local character = Player.Character
if character then
for _, child in pairs(character:GetChildren()) do
if child:IsA('Tool') then
child.ManualActivationOnly = false
end
end
end
end
DrivingTo = nil
Running = false
end
end
function this:Start()
if not Running then
if Player.Character then -- retro-listen
OnCharacterAdded(Player.Character)
end
OnCharacterAddedConn = Player.CharacterAdded:connect(OnCharacterAdded)
if CameraModule then
CameraModule:SetEnabled(true)
end
Running = true
end
end
return this
end
return CreateClickToMoveModule
--[[
Orbital Camera 1.0.0
AllYourBlox
Derived from ClassicCamera, adds camera angle constraints, and represents position values in spherical
coordinates (azimuth, elevation, radius), instead of Cartesian coordinates (x, y, z). Azimuth is the
angle of rotation about the Y axis, with the zero-angle reference point corresponding to offsetting
the camera in the +Z direction, from where it will be looking in the -Z direction by default. Elevation
is the angle up from the XZ plane, where zero degrees is on the plane, and +90 degrees is on the +Y
axis. Distance is the camera-to-subject distance, the spherical coordinates radius, specified in studs.
--]]
-- Do not edit these values, they are not the developer-set limits, they are limits
-- to the values the camera system equations can correctly handle
local MIN_ALLOWED_ELEVATION_DEG = -80
local MAX_ALLOWED_ELEVATION_DEG = 80
local externalProperties = {}
externalProperties["InitialDistance"] = 25
externalProperties["MinDistance"] = 10
externalProperties["MaxDistance"] = 100
externalProperties["InitialElevation"] = 35
externalProperties["MinElevation"] = 35
externalProperties["MaxElevation"] = 35
externalProperties["ReferenceAzimuth"] = -45 -- Angle around the Y axis where the camera starts. -45 offsets the camera in the -X and +Z directions equally
externalProperties["CWAzimuthTravel"] = 90 -- How many degrees the camera is allowed to rotate from the reference position, CW as seen from above
externalProperties["CCWAzimuthTravel"] = 90 -- How many degrees the camera is allowed to rotate from the reference position, CCW as seen from above
externalProperties["UseAzimuthLimits"] = false -- Full rotation around Y axis available by default
local refAzimuthRad
local curAzimuthRad
local minAzimuthAbsoluteRad
local maxAzimuthAbsoluteRad
local useAzimuthLimits
local curElevationRad
local minElevationRad
local maxElevationRad
local curDistance
local minDistance
local maxDistance
local UNIT_Z = Vector3.new(0,0,1)
local TAU = 2 * math.pi
local changedSignalConnections = {}
-- End of OrbitalCamera additions
local PlayersService = game:GetService('Players')
local VRService = game:GetService("VRService")
local RootCameraCreator = require(script.Parent)
local UP_VECTOR = Vector3.new(0, 1, 0)
local XZ_VECTOR = Vector3.new(1, 0, 1)
local ZERO_VECTOR2 = Vector2.new(0, 0)
local VR_PITCH_FRACTION = 0.25
local Vector3_new = Vector3.new
local CFrame_new = CFrame.new
local math_min = math.min
local math_max = math.max
local math_atan2 = math.atan2
local math_rad = math.rad
local math_abs = math.abs
--[[ Gamepad Support ]]--
local THUMBSTICK_DEADZONE = 0.2
local r3ButtonDown = false
local l3ButtonDown = false
local currentZoomSpeed = 1 -- Multiplier, so 1 == no zooming
local function clamp(value, minValue, maxValue)
if maxValue < minValue then
maxValue = minValue
end
return math.clamp(value, minValue, maxValue)
end
local function IsFinite(num)
return num == num and num ~= 1/0 and num ~= -1/0
end
local function IsFiniteVector3(vec3)
return IsFinite(vec3.x) and IsFinite(vec3.y) and IsFinite(vec3.z)
end
-- May return NaN or inf or -inf
-- This is a way of finding the angle between the two vectors:
local function findAngleBetweenXZVectors(vec2, vec1)
return math_atan2(vec1.X*vec2.Z-vec1.Z*vec2.X, vec1.X*vec2.X + vec1.Z*vec2.Z)
end
local function GetValueObject(name, defaultValue)
local valueObj = script:FindFirstChild(name)
if valueObj then
return valueObj.Value
end
return defaultValue
end
local function LoadOrCreateNumberValueParameter(name, valueType, updateFunction)
local valueObj = script:FindFirstChild(name)
if valueObj and valueObj:isA(valueType) then
-- Value object exists and is the correct type, use its value
externalProperties[name] = valueObj.Value
elseif externalProperties[name] ~= nil then
-- Create missing (or replace incorrectly-typed) valueObject with default value
valueObj = Instance.new(valueType)
valueObj.Name = name
valueObj.Parent = script
valueObj.Value = externalProperties[name]
else
print("externalProperties table has no entry for ",name)
return
end
if updateFunction then
if changedSignalConnections[name] then
changedSignalConnections[name]:disconnect()
end
changedSignalConnections[name] = valueObj.Changed:connect(function(newValue)
externalProperties[name] = newValue
updateFunction()
end)
end
end
local function SetAndBoundsCheckAzimuthValues()
minAzimuthAbsoluteRad = math.rad(externalProperties["ReferenceAzimuth"]) - math.abs(math.rad(externalProperties["CWAzimuthTravel"]))
maxAzimuthAbsoluteRad = math.rad(externalProperties["ReferenceAzimuth"]) + math.abs(math.rad(externalProperties["CCWAzimuthTravel"]))
useAzimuthLimits = externalProperties["UseAzimuthLimits"]
if useAzimuthLimits then
curAzimuthRad = math.max(curAzimuthRad, minAzimuthAbsoluteRad)
curAzimuthRad = math.min(curAzimuthRad, maxAzimuthAbsoluteRad)
end
end
local function SetAndBoundsCheckElevationValues()
-- These degree values are the direct user input values. It is deliberate that they are
-- ranged checked only against the extremes, and not against each other. Any time one
-- is changed, both of the internal values in radians are recalculated. This allows for
-- A developer to change the values in any order and for the end results to be that the
-- internal values adjust to match intent as best as possible.
local minElevationDeg = math.max(externalProperties["MinElevation"], MIN_ALLOWED_ELEVATION_DEG)
local maxElevationDeg = math.min(externalProperties["MaxElevation"], MAX_ALLOWED_ELEVATION_DEG)
-- Set internal values in radians
minElevationRad = math.rad(math.min(minElevationDeg, maxElevationDeg))
maxElevationRad = math.rad(math.max(minElevationDeg, maxElevationDeg))
curElevationRad = math.max(curElevationRad, minElevationRad)
curElevationRad = math.min(curElevationRad, maxElevationRad)
end
local function SetAndBoundsCheckDistanceValues()
minDistance = externalProperties["MinDistance"]
maxDistance = externalProperties["MaxDistance"]
curDistance = math.max(curDistance, minDistance)
curDistance = math.min(curDistance, maxDistance)
end
-- This loads from, or lazily creates, NumberValue objects for exposed parameters
local function LoadNumberValueParameters()
-- These initial values do not require change listeners since they are read only once
LoadOrCreateNumberValueParameter("InitialElevation", "NumberValue", nil)
LoadOrCreateNumberValueParameter("InitialDistance", "NumberValue", nil)
-- Note: ReferenceAzimuth is also used as an initial value, but needs a change listener because it is used in the calculation of the limits
LoadOrCreateNumberValueParameter("ReferenceAzimuth", "NumberValue", SetAndBoundsCheckAzimuthValues)
LoadOrCreateNumberValueParameter("CWAzimuthTravel", "NumberValue", SetAndBoundsCheckAzimuthValues)
LoadOrCreateNumberValueParameter("CCWAzimuthTravel", "NumberValue", SetAndBoundsCheckAzimuthValues)
LoadOrCreateNumberValueParameter("MinElevation", "NumberValue", SetAndBoundsCheckElevationValues)
LoadOrCreateNumberValueParameter("MaxElevation", "NumberValue", SetAndBoundsCheckElevationValues)
LoadOrCreateNumberValueParameter("MinDistance", "NumberValue", SetAndBoundsCheckDistanceValues)
LoadOrCreateNumberValueParameter("MaxDistance", "NumberValue", SetAndBoundsCheckDistanceValues)
LoadOrCreateNumberValueParameter("UseAzimuthLimits", "BoolValue", SetAndBoundsCheckAzimuthValues)
-- Internal values set (in radians, from degrees), plus sanitization
curAzimuthRad = math.rad(externalProperties["ReferenceAzimuth"])
curElevationRad = math.rad(externalProperties["InitialElevation"])
curDistance = externalProperties["InitialDistance"]
SetAndBoundsCheckAzimuthValues()
SetAndBoundsCheckElevationValues()
SetAndBoundsCheckDistanceValues()
end
local function CreateOrbitalCamera()
local module = RootCameraCreator()
LoadNumberValueParameters()
module.DefaultZoom = curDistance
local tweenAcceleration = math_rad(220)
local tweenSpeed = math_rad(0)
local tweenMaxSpeed = math_rad(250)
local timeBeforeAutoRotate = 2
local lastUpdate = tick()
module.LastUserPanCamera = tick()
function module:Update()
module:ProcessTweens()
local now = tick()
local timeDelta = (now - lastUpdate)
local userPanningTheCamera = (self.UserPanningTheCamera == true)
local camera = workspace.CurrentCamera
local player = PlayersService.LocalPlayer
local humanoid = self:GetHumanoid()
local cameraSubject = camera and camera.CameraSubject
local isInVehicle = cameraSubject and cameraSubject:IsA('VehicleSeat')
local isOnASkateboard = cameraSubject and cameraSubject:IsA('SkateboardPlatform')
if lastUpdate == nil or now - lastUpdate > 1 then
module:ResetCameraLook()
self.LastCameraTransform = nil
end
if lastUpdate then
local gamepadRotation = self:UpdateGamepad()
if self:ShouldUseVRRotation() then
self.RotateInput = self.RotateInput + self:GetVRRotationInput()
else
-- Cap out the delta to 0.1 so we don't get some crazy things when we re-resume from
local delta = math_min(0.1, now - lastUpdate)
if gamepadRotation ~= ZERO_VECTOR2 then
userPanningTheCamera = true
self.RotateInput = self.RotateInput + (gamepadRotation * delta)
end
local angle = 0
if not (isInVehicle or isOnASkateboard) then
angle = angle + (self.TurningLeft and -120 or 0)
angle = angle + (self.TurningRight and 120 or 0)
end
if angle ~= 0 then
self.RotateInput = self.RotateInput + Vector2.new(math_rad(angle * delta), 0)
userPanningTheCamera = true
end
end
end
-- Reset tween speed if user is panning
if userPanningTheCamera then
tweenSpeed = 0
module.LastUserPanCamera = tick()
end
local userRecentlyPannedCamera = now - module.LastUserPanCamera < timeBeforeAutoRotate
local subjectPosition = self:GetSubjectPosition()
if subjectPosition and player and camera then
local zoom = self:ZoomCamera(curDistance * currentZoomSpeed)
if not userPanningTheCamera and self.LastCameraTransform then
local isInFirstPerson = self:IsInFirstPerson()
if (isInVehicle or isOnASkateboard) and lastUpdate and humanoid and humanoid.Torso then
if isInFirstPerson then
if self.LastSubjectCFrame and (isInVehicle or isOnASkateboard) and cameraSubject:IsA('BasePart') then
local y = -findAngleBetweenXZVectors(self.LastSubjectCFrame.lookVector, cameraSubject.CFrame.lookVector)
if IsFinite(y) then
self.RotateInput = self.RotateInput + Vector2.new(y, 0)
end
tweenSpeed = 0
end
elseif not userRecentlyPannedCamera then
local forwardVector = humanoid.Torso.CFrame.lookVector
if isOnASkateboard then
forwardVector = cameraSubject.CFrame.lookVector
end
tweenSpeed = clamp(0, tweenMaxSpeed, tweenSpeed + tweenAcceleration * timeDelta)
local percent = clamp(0, 1, tweenSpeed * timeDelta)
if self:IsInFirstPerson() then
percent = 1
end
local y = findAngleBetweenXZVectors(forwardVector, self:GetCameraLook())
if IsFinite(y) and math_abs(y) > 0.0001 then
self.RotateInput = self.RotateInput + Vector2.new(y * percent, 0)
end
end
end
end
local VREnabled = VRService.VREnabled
camera.Focus = VREnabled and self:GetVRFocus(subjectPosition, timeDelta) or CFrame_new(subjectPosition)
local cameraFocusP = camera.Focus.p
if VREnabled and not self:IsInFirstPerson() then
local cameraHeight = self:GetCameraHeight()
local vecToSubject = (subjectPosition - camera.CFrame.p)
local distToSubject = vecToSubject.magnitude
-- Only move the camera if it exceeded a maximum distance to the subject in VR
if distToSubject > zoom or self.RotateInput.x ~= 0 then
local desiredDist = math_min(distToSubject, zoom)
vecToSubject = self:RotateCamera(vecToSubject.unit * XZ_VECTOR, Vector2.new(self.RotateInput.x, 0)) * desiredDist
local newPos = cameraFocusP - vecToSubject
local desiredLookDir = camera.CFrame.lookVector
if self.RotateInput.x ~= 0 then
desiredLookDir = vecToSubject
end
local lookAt = Vector3.new(newPos.x + desiredLookDir.x, newPos.y, newPos.z + desiredLookDir.z)
self.RotateInput = ZERO_VECTOR2
camera.CFrame = CFrame_new(newPos, lookAt) + Vector3_new(0, cameraHeight, 0)
end
else
-- self.RotateInput is a Vector2 of mouse movement deltas since last update
curAzimuthRad = curAzimuthRad - self.RotateInput.x
if useAzimuthLimits then
curAzimuthRad = clamp(curAzimuthRad, minAzimuthAbsoluteRad, maxAzimuthAbsoluteRad)
else
curAzimuthRad = (curAzimuthRad ~= 0) and (math.sign(curAzimuthRad) * (math.abs(curAzimuthRad) % TAU)) or 0
end
curDistance = clamp(zoom, minDistance, maxDistance)
curElevationRad = clamp(curElevationRad + self.RotateInput.y, minElevationRad,maxElevationRad)
local cameraPosVector = curDistance * ( CFrame.fromEulerAnglesYXZ( -curElevationRad, curAzimuthRad, 0 ) * UNIT_Z )
local camPos = subjectPosition + cameraPosVector
camera.CFrame = CFrame.new(camPos, subjectPosition)
self.RotateInput = ZERO_VECTOR2
end
self.LastCameraTransform = camera.CFrame
self.LastCameraFocus = camera.Focus
if (isInVehicle or isOnASkateboard) and cameraSubject:IsA('BasePart') then
self.LastSubjectCFrame = cameraSubject.CFrame
else
self.LastSubjectCFrame = nil
end
end
lastUpdate = now
end
function module:GetCameraZoom()
return curDistance
end
function module:ZoomCamera(desiredZoom)
local player = PlayersService.LocalPlayer
if player then
curDistance = clamp(desiredZoom, minDistance, maxDistance)
end
local isFirstPerson = self:GetCameraZoom() < 2
--ShiftLockController:SetIsInFirstPerson(isFirstPerson)
-- set mouse behavior
self:UpdateMouseBehavior()
return self:GetCameraZoom()
end
function module:ZoomCameraBy(zoomScale)
local newDist = curDistance
-- Can break into more steps to get more accurate integration
newDist = self:rk4Integrator(curDistance, zoomScale, 1)
self:ZoomCamera(newDist)
return self:GetCameraZoom()
end
function module:ZoomCameraFixedBy(zoomIncrement)
return self:ZoomCamera(self:GetCameraZoom() + zoomIncrement)
end
function module:SetInitialOrientation(humanoid)
local newDesiredLook = (humanoid.Torso.CFrame.lookVector - Vector3.new(0,0.23,0)).unit
local horizontalShift = findAngleBetweenXZVectors(newDesiredLook, self:GetCameraLook())
local vertShift = math.asin(self:GetCameraLook().y) - math.asin(newDesiredLook.y)
if not IsFinite(horizontalShift) then
horizontalShift = 0
end
if not IsFinite(vertShift) then
vertShift = 0
end
self.RotateInput = Vector2.new(horizontalShift, vertShift)
end
function module.getGamepadPan(name, state, input)
if input.UserInputType == module.activeGamepad and input.KeyCode == Enum.KeyCode.Thumbstick2 then
if r3ButtonDown or l3ButtonDown then
-- R3 or L3 Thumbstick is depressed, right stick controls dolly in/out
if (input.Position.Y > THUMBSTICK_DEADZONE) then
currentZoomSpeed = 0.96
elseif (input.Position.Y < -THUMBSTICK_DEADZONE) then
currentZoomSpeed = 1.04
else
currentZoomSpeed = 1.00
end
else
if state == Enum.UserInputState.Cancel then
module.GamepadPanningCamera = ZERO_VECTOR2
return
end
local inputVector = Vector2.new(input.Position.X, -input.Position.Y)
if inputVector.magnitude > THUMBSTICK_DEADZONE then
module.GamepadPanningCamera = Vector2.new(input.Position.X, -input.Position.Y)
else
module.GamepadPanningCamera = ZERO_VECTOR2
end
end
end
end
function module.doGamepadZoom(name, state, input)
if input.UserInputType == module.activeGamepad and (input.KeyCode == Enum.KeyCode.ButtonR3 or input.KeyCode == Enum.KeyCode.ButtonL3) then
if (state == Enum.UserInputState.Begin) then
r3ButtonDown = input.KeyCode == Enum.KeyCode.ButtonR3
l3ButtonDown = input.KeyCode == Enum.KeyCode.ButtonL3
elseif (state == Enum.UserInputState.End) then
if (input.KeyCode == Enum.KeyCode.ButtonR3) then
r3ButtonDown = false
elseif (input.KeyCode == Enum.KeyCode.ButtonL3) then
l3ButtonDown = false
end
if (not r3ButtonDown) and (not l3ButtonDown) then
currentZoomSpeed = 1.00
end
end
end
end
function module:BindGamepadInputActions()
local ContextActionService = game:GetService('ContextActionService')
ContextActionService:BindAction("OrbitalCamGamepadPan", module.getGamepadPan, false, Enum.KeyCode.Thumbstick2)
ContextActionService:BindAction("OrbitalCamGamepadZoom", module.doGamepadZoom, false, Enum.KeyCode.ButtonR3)
ContextActionService:BindAction("OrbitalCamGamepadZoomAlt", module.doGamepadZoom, false, Enum.KeyCode.ButtonL3)
end
return module
end
return CreateOrbitalCamera
-- PopperCam Version 16
-- OnlyTwentyCharacters
local PopperCam = {} -- Guarantees your players won't see outside the bounds of your map!
-----------------
--| Constants |--
-----------------
local POP_RESTORE_RATE = 0.3
local MIN_CAMERA_ZOOM = 0.5
local VALID_SUBJECTS = {
'Humanoid',
'VehicleSeat',
'SkateboardPlatform',
}
local portraitPopperFixFlagExists, portraitPopperFixFlagEnabled = pcall(function()
return UserSettings():IsUserFeatureEnabled("UserPortraitPopperFix")
end)
local FFlagUserPortraitPopperFix = portraitPopperFixFlagExists and portraitPopperFixFlagEnabled
-----------------
--| Variables |--
-----------------
local PlayersService = game:GetService('Players')
local Camera = nil
local CameraSubjectChangeConn = nil
local SubjectPart = nil
local PlayerCharacters = {} -- For ignoring in raycasts
local VehicleParts = {} -- Also just for ignoring
local LastPopAmount = 0
local LastZoomLevel = 0
local PopperEnabled = false
local CFrame_new = CFrame.new
-----------------------
--| Local Functions |--
-----------------------
local math_abs = math.abs
local function OnCameraSubjectChanged()
VehicleParts = {}
local newSubject = Camera.CameraSubject
if newSubject then
-- Determine if we should be popping at all
PopperEnabled = false
for _, subjectType in pairs(VALID_SUBJECTS) do
if newSubject:IsA(subjectType) then
PopperEnabled = true
break
end
end
-- Get all parts of the vehicle the player is controlling
if newSubject:IsA('VehicleSeat') then
VehicleParts = newSubject:GetConnectedParts(true)
end
if FFlagUserPortraitPopperFix then
if newSubject:IsA("BasePart") then
SubjectPart = newSubject
elseif newSubject:IsA("Model") then
SubjectPart = newSubject.PrimaryPart
elseif newSubject:IsA("Humanoid") then
SubjectPart = newSubject.Torso
end
end
end
end
local function OnCharacterAdded(player, character)
PlayerCharacters[player] = character
end
local function OnPlayersChildAdded(child)
if child:IsA('Player') then
child.CharacterAdded:connect(function(character)
OnCharacterAdded(child, character)
end)
if child.Character then
OnCharacterAdded(child, child.Character)
end
end
end
local function OnPlayersChildRemoved(child)
if child:IsA('Player') then
PlayerCharacters[child] = nil
end
end
local function OnWorkspaceChanged(property)
if property == 'CurrentCamera' then
local newCamera = workspace.CurrentCamera
if newCamera then
Camera = newCamera
if CameraSubjectChangeConn then
CameraSubjectChangeConn:disconnect()
end
CameraSubjectChangeConn = Camera:GetPropertyChangedSignal("CameraSubject"):connect(OnCameraSubjectChanged)
OnCameraSubjectChanged()
end
end
end
-------------------------
--| Exposed Functions |--
-------------------------
function PopperCam:Update(EnabledCamera)
if PopperEnabled then
-- First, prep some intermediate vars
local cameraCFrame = Camera.CFrame
local focusPoint = Camera.Focus.p
if FFlagUserPortraitPopperFix and SubjectPart then
focusPoint = SubjectPart.CFrame.p
end
local ignoreList = {}
for _, character in pairs(PlayerCharacters) do
ignoreList[#ignoreList + 1] = character
end
for i = 1, #VehicleParts do
ignoreList[#ignoreList + 1] = VehicleParts[i]
end
-- Get largest cutoff distance
local largest = Camera:GetLargestCutoffDistance(ignoreList)
-- Then check if the player zoomed since the last frame,
-- and if so, reset our pop history so we stop tweening
local zoomLevel = (cameraCFrame.p - focusPoint).Magnitude
if math_abs(zoomLevel - LastZoomLevel) > 0.001 then
LastPopAmount = 0
end
-- Finally, zoom the camera in (pop) by that most-cut-off amount, or the last pop amount if that's more
local popAmount = largest
if LastPopAmount > popAmount then
popAmount = LastPopAmount
end
if popAmount > 0 then
Camera.CFrame = cameraCFrame + (cameraCFrame.lookVector * popAmount)
LastPopAmount = popAmount - POP_RESTORE_RATE -- Shrink it for the next frame
if LastPopAmount < 0 then
LastPopAmount = 0
end
end
LastZoomLevel = zoomLevel
-- Stop shift lock being able to see through walls by manipulating Camera focus inside the wall
if EnabledCamera and EnabledCamera:GetShiftLock() and not EnabledCamera:IsInFirstPerson() then
if EnabledCamera:GetCameraActualZoom() < 1 then
local subjectPosition = EnabledCamera.lastSubjectPosition
if subjectPosition then
Camera.Focus = CFrame_new(subjectPosition)
Camera.CFrame = CFrame_new(subjectPosition - MIN_CAMERA_ZOOM*EnabledCamera:GetCameraLook(), subjectPosition)
end
end
end
end
end
--------------------
--| Script Logic |--
--------------------
-- Connect to the current and all future cameras
workspace.Changed:connect(OnWorkspaceChanged)
OnWorkspaceChanged('CurrentCamera')
-- Connect to all Players so we can ignore their Characters
PlayersService.ChildRemoved:connect(OnPlayersChildRemoved)
PlayersService.ChildAdded:connect(OnPlayersChildAdded)
for _, player in pairs(PlayersService:GetPlayers()) do
OnPlayersChildAdded(player)
end
return PopperCam
local PlayersService = game:GetService('Players')
local UserInputService = game:GetService('UserInputService')
local StarterGui = game:GetService('StarterGui')
local GuiService = game:GetService('GuiService')
local ContextActionService = game:GetService('ContextActionService')
local VRService = game:GetService("VRService")
local LocalPlayer = PlayersService.LocalPlayer
local PlayerGui = nil
if LocalPlayer then
PlayerGui = PlayersService.LocalPlayer:WaitForChild("PlayerGui")
end
local PortraitMode = false
local CameraScript = script.Parent
local ShiftLockController = require(CameraScript:WaitForChild('ShiftLockController'))
local Settings = UserSettings()
local GameSettings = Settings.GameSettings
local function clamp(low, high, num)
return (num > high and high or num < low and low or num)
end
local math_atan2 = math.atan2
local function findAngleBetweenXZVectors(vec2, vec1)
return math_atan2(vec1.X*vec2.Z-vec1.Z*vec2.X, vec1.X*vec2.X + vec1.Z*vec2.Z)
end
local function IsFinite(num)
return num == num and num ~= 1/0 and num ~= -1/0
end
local THUMBSTICK_DEADZONE = 0.2
local LANDSCAPE_DEFAULT_ZOOM = 12.5
local PORTRAIT_DEFAULT_ZOOM = 25
local humanoidCache = {}
local function findPlayerHumanoid(player)
local character = player and player.Character
if character then
local resultHumanoid = humanoidCache[player]
if resultHumanoid and resultHumanoid.Parent == character then
return resultHumanoid
else
humanoidCache[player] = nil -- Bust Old Cache
local humanoid = character:FindFirstChildOfClass("Humanoid")
if humanoid then
humanoidCache[player] = humanoid
end
return humanoid
end
end
end
local MIN_Y = math.rad(-80)
local MAX_Y = math.rad(80)
local VR_ANGLE = math.rad(15)
local VR_LOW_INTENSITY_ROTATION = Vector2.new(math.rad(15), 0)
local VR_HIGH_INTENSITY_ROTATION = Vector2.new(math.rad(45), 0)
local VR_LOW_INTENSITY_REPEAT = 0.1
local VR_HIGH_INTENSITY_REPEAT = 0.4
local ZERO_VECTOR2 = Vector2.new(0, 0)
local ZERO_VECTOR3 = Vector3.new(0, 0, 0)
local TOUCH_SENSITIVTY = Vector2.new(math.pi*2.25, math.pi*2)
local MOUSE_SENSITIVITY = Vector2.new(math.pi*4, math.pi*1.9)
local MAX_TIME_FOR_DOUBLE_TAP = 1.5
local MAX_TAP_POS_DELTA = 15
local MAX_TAP_TIME_DELTA = 0.75
local SEAT_OFFSET = Vector3.new(0,5,0)
local VR_SEAT_OFFSET = Vector3.new(0, 4, 0)
local HEAD_OFFSET = Vector3.new(0, 1.5, 0)
-- Reset the camera look vector when the camera is enabled for the first time
local SetCameraOnSpawn = true
local hasGameLoaded = false
local GestureArea = nil
--todo: remove this once TouchTapInWorld is on all platforms
local touchWorkspaceEventEnabled = pcall(function() local test = UserInputService.TouchTapInWorld end)
local function layoutGestureArea(portraitMode)
if GestureArea then
if portraitMode then
GestureArea.Size = UDim2.new(1, 0, .6, 0)
GestureArea.Position = UDim2.new(0, 0, 0, 0)
else
GestureArea.Size = UDim2.new(1, 0, .5, -18)
GestureArea.Position = UDim2.new(0, 0, 0, 0)
end
end
end
-- Setup gesture area that camera uses while DynamicThumbstick is enabled
local function OnCharacterAdded(character)
if UserInputService.TouchEnabled then
if PlayerGui then
local ScreenGui = Instance.new("ScreenGui")
ScreenGui.Name = "GestureArea"
ScreenGui.Parent = PlayerGui
GestureArea = Instance.new("Frame")
GestureArea.BackgroundTransparency = 1.0
GestureArea.Visible = true
GestureArea.BackgroundColor3 = Color3.fromRGB(0, 0, 0)
layoutGestureArea(PortraitMode)
GestureArea.Parent = ScreenGui
end
for _, child in ipairs(LocalPlayer.Character:GetChildren()) do
if child:IsA("Tool") then
IsAToolEquipped = true
end
end
character.ChildAdded:Connect(function(child)
if child:IsA("Tool") then
IsAToolEquipped = true
end
end)
character.ChildRemoved:Connect(function(child)
if child:IsA("Tool") then
IsAToolEquipped = false
end
end)
end
end
if LocalPlayer then
if LocalPlayer.Character ~= nil then
OnCharacterAdded(LocalPlayer.Character)
end
LocalPlayer.CharacterAdded:connect(function(character)
OnCharacterAdded(character)
end)
end
local function positionIntersectsGuiObject(position, guiObject)
if position.X < guiObject.AbsolutePosition.X + guiObject.AbsoluteSize.X
and position.X > guiObject.AbsolutePosition.X
and position.Y < guiObject.AbsolutePosition.Y + guiObject.AbsoluteSize.Y
and position.Y > guiObject.AbsolutePosition.Y then
return true
end
return false
end
local function GetRenderCFrame(part)
return part:GetRenderCFrame()
end
local function CreateCamera()
local this = {}
function this:GetActivateValue()
return 0.7
end
function this:IsPortraitMode()
return PortraitMode
end
function this:GetRotateAmountValue(vrRotationIntensity)
vrRotationIntensity = vrRotationIntensity or StarterGui:GetCore("VRRotationIntensity")
if vrRotationIntensity then
if vrRotationIntensity == "Low" then
return VR_LOW_INTENSITY_ROTATION
elseif vrRotationIntensity == "High" then
return VR_HIGH_INTENSITY_ROTATION
end
end
return ZERO_VECTOR2
end
function this:GetRepeatDelayValue(vrRotationIntensity)
vrRotationIntensity = vrRotationIntensity or StarterGui:GetCore("VRRotationIntensity")
if vrRotationIntensity then
if vrRotationIntensity == "Low" then
return VR_LOW_INTENSITY_REPEAT
elseif vrRotationIntensity == "High" then
return VR_HIGH_INTENSITY_REPEAT
end
end
return 0
end
this.ShiftLock = false
this.Enabled = false
local isFirstPerson = false
local isRightMouseDown = false
local isMiddleMouseDown = false
this.RotateInput = ZERO_VECTOR2
this.DefaultZoom = LANDSCAPE_DEFAULT_ZOOM
this.activeGamepad = nil
local tweens = {}
this.lastSubject = nil
this.lastSubjectPosition = Vector3.new(0, 5, 0)
local lastVRRotation = 0
local vrRotateKeyCooldown = {}
local isDynamicThumbstickEnabled = false
-- Check for changes in ViewportSize to decide if PortraitMode
local CameraChangedConn = nil
local workspaceCameraChangedConn = nil
local function onWorkspaceCameraChanged()
if UserInputService.TouchEnabled then
if CameraChangedConn then
CameraChangedConn:Disconnect()
CameraChangedConn = nil
end
local newCamera = workspace.CurrentCamera
if newCamera then
local size = newCamera.ViewportSize
PortraitMode = size.X < size.Y
layoutGestureArea(PortraitMode)
DefaultZoom = PortraitMode and PORTRAIT_DEFAULT_ZOOM or LANDSCAPE_DEFAULT_ZOOM
CameraChangedConn = newCamera:GetPropertyChangedSignal("ViewportSize"):Connect(function()
size = newCamera.ViewportSize
PortraitMode = size.X < size.Y
layoutGestureArea(PortraitMode)
DefaultZoom = PortraitMode and PORTRAIT_DEFAULT_ZOOM or LANDSCAPE_DEFAULT_ZOOM
end)
end
end
end
workspaceCameraChangedConn = workspace:GetPropertyChangedSignal("CurrentCamera"):Connect(onWorkspaceCameraChanged)
if workspace.CurrentCamera then
onWorkspaceCameraChanged()
end
function this:GetShiftLock()
return ShiftLockController:IsShiftLocked()
end
function this:GetHumanoid()
local player = PlayersService.LocalPlayer
return findPlayerHumanoid(player)
end
function this:GetHumanoidRootPart()
local humanoid = this:GetHumanoid()
return humanoid and humanoid.Torso
end
function this:GetRenderCFrame(part)
GetRenderCFrame(part)
end
local STATE_DEAD = Enum.HumanoidStateType.Dead
-- HumanoidRootPart when alive, Head part when dead
local function getHumanoidPartToFollow(humanoid, humanoidStateType)
if humanoidStateType == STATE_DEAD then
local character = humanoid.Parent
if character then
return character:FindFirstChild("Head") or humanoid.Torso
else
return humanoid.Torso
end
else
return humanoid.Torso
end
end
local HUMANOID_STATE_DEAD = Enum.HumanoidStateType.Dead
function this:GetSubjectPosition()
local result = nil
local camera = workspace.CurrentCamera
local cameraSubject = camera and camera.CameraSubject
if cameraSubject then
if cameraSubject:IsA('Humanoid') then
local humanoidStateType = cameraSubject:GetState()
if VRService.VREnabled and humanoidStateType == STATE_DEAD and cameraSubject == this.lastSubject then
result = this.lastSubjectPosition
else
local humanoidRootPart = getHumanoidPartToFollow(cameraSubject, humanoidStateType)
if humanoidRootPart and humanoidRootPart:IsA('BasePart') then
local subjectCFrame = GetRenderCFrame(humanoidRootPart)
local heightOffset = ZERO_VECTOR3
if humanoidStateType ~= STATE_DEAD then
heightOffset = cameraSubject.RigType == Enum.HumanoidRigType.R15 and R15HeadHeight or HEAD_OFFSET
end
result = subjectCFrame.p +
subjectCFrame:vectorToWorldSpace(heightOffset + cameraSubject.CameraOffset)
end
end
elseif cameraSubject:IsA('VehicleSeat') then
local subjectCFrame = GetRenderCFrame(cameraSubject)
local offset = SEAT_OFFSET
if VRService.VREnabled then
offset = VR_SEAT_OFFSET
end
result = subjectCFrame.p + subjectCFrame:vectorToWorldSpace(offset)
elseif cameraSubject:IsA('SkateboardPlatform') then
local subjectCFrame = GetRenderCFrame(cameraSubject)
result = subjectCFrame.p + SEAT_OFFSET
elseif cameraSubject:IsA('BasePart') then
local subjectCFrame = GetRenderCFrame(cameraSubject)
result = subjectCFrame.p
elseif cameraSubject:IsA('Model') then
result = cameraSubject:GetModelCFrame().p
end
end
this.lastSubject = cameraSubject
this.lastSubjectPosition = result
return result
end
function this:ResetCameraLook()
end
function this:GetCameraLook()
return workspace.CurrentCamera and workspace.CurrentCamera.CoordinateFrame.lookVector or Vector3.new(0,0,1)
end
function this:GetCameraZoom()
if this.currentZoom == nil then
local player = PlayersService.LocalPlayer
this.currentZoom = player and clamp(player.CameraMinZoomDistance, player.CameraMaxZoomDistance, this.DefaultZoom) or this.DefaultZoom
end
return this.currentZoom
end
function this:GetCameraActualZoom()
local camera = workspace.CurrentCamera
if camera then
return (camera.CoordinateFrame.p - camera.Focus.p).magnitude
end
end
function this:GetCameraHeight()
if VRService.VREnabled and not this:IsInFirstPerson() then
local zoom = this:GetCameraZoom()
return math.sin(VR_ANGLE) * zoom
end
return 0
end
function this:ViewSizeX()
local result = 1024
local camera = workspace.CurrentCamera
if camera then
result = camera.ViewportSize.X
end
return result
end
function this:ViewSizeY()
local result = 768
local camera = workspace.CurrentCamera
if camera then
result = camera.ViewportSize.Y
end
return result
end
local math_asin = math.asin
local math_atan2 = math.atan2
local math_floor = math.floor
local math_max = math.max
local math_pi = math.pi
local Vector2_new = Vector2.new
local Vector3_new = Vector3.new
local CFrame_Angles = CFrame.Angles
local CFrame_new = CFrame.new
function this:ScreenTranslationToAngle(translationVector)
local screenX = this:ViewSizeX()
local screenY = this:ViewSizeY()
local xTheta = (translationVector.x / screenX)
local yTheta = (translationVector.y / screenY)
return Vector2_new(xTheta, yTheta)
end
function this:MouseTranslationToAngle(translationVector)
local xTheta = (translationVector.x / 1920)
local yTheta = (translationVector.y / 1200)
return Vector2_new(xTheta, yTheta)
end
function this:RotateVector(startVector, xyRotateVector)
local startCFrame = CFrame_new(ZERO_VECTOR3, startVector)
local resultLookVector = (CFrame_Angles(0, -xyRotateVector.x, 0) * startCFrame * CFrame_Angles(-xyRotateVector.y,0,0)).lookVector
return resultLookVector, Vector2_new(xyRotateVector.x, xyRotateVector.y)
end
function this:RotateCamera(startLook, xyRotateVector)
if VRService.VREnabled then
local yawRotatedVector, xyRotateVector = self:RotateVector(startLook, Vector2.new(xyRotateVector.x, 0))
return Vector3_new(yawRotatedVector.x, 0, yawRotatedVector.z).unit, xyRotateVector
else
local startVertical = math_asin(startLook.y)
local yTheta = clamp(-MAX_Y + startVertical, -MIN_Y + startVertical, xyRotateVector.y)
return self:RotateVector(startLook, Vector2_new(xyRotateVector.x, yTheta))
end
end
function this:IsInFirstPerson()
return isFirstPerson
end
-- there are several cases to consider based on the state of input and camera rotation mode
function this:UpdateMouseBehavior()
-- first time transition to first person mode or shiftlock
if isFirstPerson or self:GetShiftLock() then
pcall(function() GameSettings.RotationType = Enum.RotationType.CameraRelative end)
if UserInputService.MouseBehavior ~= Enum.MouseBehavior.LockCenter then
UserInputService.MouseBehavior = Enum.MouseBehavior.LockCenter
end
else
pcall(function() GameSettings.RotationType = Enum.RotationType.MovementRelative end)
if isRightMouseDown or isMiddleMouseDown then
UserInputService.MouseBehavior = Enum.MouseBehavior.LockCurrentPosition
else
UserInputService.MouseBehavior = Enum.MouseBehavior.Default
end
end
end
function this:ZoomCamera(desiredZoom)
local player = PlayersService.LocalPlayer
if player then
if player.CameraMode == Enum.CameraMode.LockFirstPerson then
this.currentZoom = 0
else
this.currentZoom = clamp(player.CameraMinZoomDistance, player.CameraMaxZoomDistance, desiredZoom)
end
end
isFirstPerson = self:GetCameraZoom() < 2
ShiftLockController:SetIsInFirstPerson(isFirstPerson)
-- set mouse behavior
self:UpdateMouseBehavior()
return self:GetCameraZoom()
end
function this:rk4Integrator(position, velocity, t)
local direction = velocity < 0 and -1 or 1
local function acceleration(p, v)
local accel = direction * math_max(1, (p / 3.3) + 0.5)
return accel
end
local p1 = position
local v1 = velocity
local a1 = acceleration(p1, v1)
local p2 = p1 + v1 * (t / 2)
local v2 = v1 + a1 * (t / 2)
local a2 = acceleration(p2, v2)
local p3 = p1 + v2 * (t / 2)
local v3 = v1 + a2 * (t / 2)
local a3 = acceleration(p3, v3)
local p4 = p1 + v3 * t
local v4 = v1 + a3 * t
local a4 = acceleration(p4, v4)
local positionResult = position + (v1 + 2 * v2 + 2 * v3 + v4) * (t / 6)
local velocityResult = velocity + (a1 + 2 * a2 + 2 * a3 + a4) * (t / 6)
return positionResult, velocityResult
end
function this:ZoomCameraBy(zoomScale)
local zoom = this:GetCameraActualZoom()
if zoom then
-- Can break into more steps to get more accurate integration
zoom = self:rk4Integrator(zoom, zoomScale, 1)
self:ZoomCamera(zoom)
end
return self:GetCameraZoom()
end
function this:ZoomCameraFixedBy(zoomIncrement)
return self:ZoomCamera(self:GetCameraZoom() + zoomIncrement)
end
function this:Update()
end
----- VR STUFF ------
function this:ApplyVRTransform()
if not VRService.VREnabled then
return
end
--we only want this to happen in first person VR
local player = PlayersService.LocalPlayer
if not (player and player.Character
and player.Character:FindFirstChild("HumanoidRootPart")
and player.Character.HumanoidRootPart:FindFirstChild("RootJoint")) then
return
end
local camera = workspace.CurrentCamera
local cameraSubject = camera.CameraSubject
local isInVehicle = cameraSubject and cameraSubject:IsA('VehicleSeat')
if this:IsInFirstPerson() and not isInVehicle then
local vrFrame = VRService:GetUserCFrame(Enum.UserCFrame.Head)
local vrRotation = vrFrame - vrFrame.p
local rootJoint = player.Character.HumanoidRootPart.RootJoint
rootJoint.C0 = CFrame.new(vrRotation:vectorToObjectSpace(vrFrame.p)) * CFrame.new(0, 0, 0, -1, 0, 0, 0, 0, 1, 0, 1, 0)
else
local rootJoint = player.Character.HumanoidRootPart.RootJoint
rootJoint.C0 = CFrame.new(0, 0, 0, -1, 0, 0, 0, 0, 1, 0, 1, 0)
end
end
local vrRotationIntensityExists = true
local lastVrRotationCheck = 0
function this:ShouldUseVRRotation()
if not VRService.VREnabled then
return false
end
if not vrRotationIntensityExists and tick() - lastVrRotationCheck < 1 then return false end
local success, vrRotationIntensity = pcall(function() return StarterGui:GetCore("VRRotationIntensity") end)
vrRotationIntensityExists = success and vrRotationIntensity ~= nil
lastVrRotationCheck = tick()
return success and vrRotationIntensity ~= nil and vrRotationIntensity ~= "Smooth"
end
function this:GetVRRotationInput()
local vrRotateSum = ZERO_VECTOR2
local vrRotationIntensity = StarterGui:GetCore("VRRotationIntensity")
local vrGamepadRotation = self.GamepadPanningCamera or ZERO_VECTOR2
local delayExpired = (tick() - lastVRRotation) >= self:GetRepeatDelayValue(vrRotationIntensity)
if math.abs(vrGamepadRotation.x) >= self:GetActivateValue() then
if (delayExpired or not vrRotateKeyCooldown[Enum.KeyCode.Thumbstick2]) then
local sign = 1
if vrGamepadRotation.x < 0 then
sign = -1
end
vrRotateSum = vrRotateSum + self:GetRotateAmountValue(vrRotationIntensity) * sign
vrRotateKeyCooldown[Enum.KeyCode.Thumbstick2] = true
end
elseif math.abs(vrGamepadRotation.x) < self:GetActivateValue() - 0.1 then
vrRotateKeyCooldown[Enum.KeyCode.Thumbstick2] = nil
end
if self.TurningLeft then
if delayExpired or not vrRotateKeyCooldown[Enum.KeyCode.Left] then
vrRotateSum = vrRotateSum - self:GetRotateAmountValue(vrRotationIntensity)
vrRotateKeyCooldown[Enum.KeyCode.Left] = true
end
else
vrRotateKeyCooldown[Enum.KeyCode.Left] = nil
end
if self.TurningRight then
if (delayExpired or not vrRotateKeyCooldown[Enum.KeyCode.Right]) then
vrRotateSum = vrRotateSum + self:GetRotateAmountValue(vrRotationIntensity)
vrRotateKeyCooldown[Enum.KeyCode.Right] = true
end
else
vrRotateKeyCooldown[Enum.KeyCode.Right] = nil
end
if vrRotateSum ~= ZERO_VECTOR2 then
lastVRRotation = tick()
end
return vrRotateSum
end
local cameraTranslationConstraints = Vector3.new(1, 1, 1)
local humanoidJumpOrigin = nil
local trackingHumanoid = nil
local cameraFrozen = false
local subjectStateChangedConn = nil
local cameraSubjectChangedConn = nil
local workspaceChangedConn = nil
local humanoidChildAddedConn = nil
local humanoidChildRemovedConn = nil
local function cancelCameraFreeze(keepConstraints)
if not keepConstraints then
cameraTranslationConstraints = Vector3.new(cameraTranslationConstraints.x, 1, cameraTranslationConstraints.z)
end
if cameraFrozen then
trackingHumanoid = nil
cameraFrozen = false
end
end
local function startCameraFreeze(subjectPosition, humanoidToTrack)
if not cameraFrozen then
humanoidJumpOrigin = subjectPosition
trackingHumanoid = humanoidToTrack
cameraTranslationConstraints = Vector3.new(cameraTranslationConstraints.x, 0, cameraTranslationConstraints.z)
cameraFrozen = true
end
end
local function rescaleCameraOffset(newScaleFactor)
R15HeadHeight = R15_HEAD_OFFSET*newScaleFactor
end
local function onHumanoidSubjectChildAdded(child)
if child.Name == "BodyHeightScale" and child:IsA("NumberValue") then
if heightScaleChangedConn then
heightScaleChangedConn:disconnect()
end
heightScaleChangedConn = child.Changed:connect(rescaleCameraOffset)
rescaleCameraOffset(child.Value)
end
end
local function onHumanoidSubjectChildRemoved(child)
if child.Name == "BodyHeightScale" then
rescaleCameraOffset(1)
if heightScaleChangedConn then
heightScaleChangedConn:disconnect()
heightScaleChangedConn = nil
end
end
end
local function onNewCameraSubject()
if subjectStateChangedConn then
subjectStateChangedConn:disconnect()
subjectStateChangedConn = nil
end
if humanoidChildAddedConn then
humanoidChildAddedConn:disconnect()
humanoidChildAddedConn = nil
end
if humanoidChildRemovedConn then
humanoidChildRemovedConn:disconnect()
humanoidChildRemovedConn = nil
end
if heightScaleChangedConn then
heightScaleChangedConn:disconnect()
heightScaleChangedConn = nil
end
local humanoid = workspace.CurrentCamera and workspace.CurrentCamera.CameraSubject
if trackingHumanoid ~= humanoid then
cancelCameraFreeze()
end
if humanoid and humanoid:IsA('Humanoid') then
humanoidChildAddedConn = humanoid.ChildAdded:connect(onHumanoidSubjectChildAdded)
humanoidChildRemovedConn = humanoid.ChildRemoved:connect(onHumanoidSubjectChildRemoved)
for _, child in pairs(humanoid:GetChildren()) do
onHumanoidSubjectChildAdded(child)
end
subjectStateChangedConn = humanoid.StateChanged:connect(function(oldState, newState)
if VRService.VREnabled and newState == Enum.HumanoidStateType.Jumping and not this:IsInFirstPerson() then
startCameraFreeze(this:GetSubjectPosition(), humanoid)
elseif newState ~= Enum.HumanoidStateType.Jumping and newState ~= Enum.HumanoidStateType.Freefall then
cancelCameraFreeze(true)
end
end)
end
end
local function onCurrentCameraChanged()
if cameraSubjectChangedConn then
cameraSubjectChangedConn:disconnect()
cameraSubjectChangedConn = nil
end
local camera = workspace.CurrentCamera
if camera then
cameraSubjectChangedConn = camera:GetPropertyChangedSignal("CameraSubject"):connect(onNewCameraSubject)
onNewCameraSubject()
end
end
function this:GetVRFocus(subjectPosition, timeDelta)
local newFocus = nil
local camera = workspace.CurrentCamera
local lastFocus = self.LastCameraFocus or subjectPosition
if not cameraFrozen then
cameraTranslationConstraints = Vector3.new(cameraTranslationConstraints.x, math.min(1, cameraTranslationConstraints.y + 0.42 * timeDelta), cameraTranslationConstraints.z)
end
if cameraFrozen and humanoidJumpOrigin and humanoidJumpOrigin.y > lastFocus.y then
newFocus = CFrame.new(Vector3.new(subjectPosition.x, math.min(humanoidJumpOrigin.y, lastFocus.y + 5 * timeDelta), subjectPosition.z))
else
newFocus = CFrame.new(Vector3.new(subjectPosition.x, lastFocus.y, subjectPosition.z):lerp(subjectPosition, cameraTranslationConstraints.y))
end
if cameraFrozen then
-- No longer in 3rd person
if self:IsInFirstPerson() then -- not VRService.VREnabled
cancelCameraFreeze()
end
-- This case you jumped off a cliff and want to keep your character in view
-- 0.5 is to fix floating point error when not jumping off cliffs
if humanoidJumpOrigin and subjectPosition.y < (humanoidJumpOrigin.y - 0.5) then
cancelCameraFreeze()
end
end
return newFocus
end
------------------------
---- Input Events ----
local startPos = nil
local lastPos = nil
local panBeginLook = nil
local lastTapTime = nil
local fingerTouches = {}
local NumUnsunkTouches = 0
local inputStartPositions = {}
local inputStartTimes = {}
local StartingDiff = nil
local pinchBeginZoom = nil
this.ZoomEnabled = true
this.PanEnabled = true
this.KeyPanEnabled = true
local function OnTouchBegan(input, processed)
--If isDynamicThumbstickEnabled, then only process TouchBegan event if it starts in GestureArea
if (not touchWorkspaceEventEnabled and not isDynamicThumbstickEnabled) or positionIntersectsGuiObject(input.Position, GestureArea) then
fingerTouches[input] = processed
if not processed then
inputStartPositions[input] = input.Position
inputStartTimes[input] = tick()
NumUnsunkTouches = NumUnsunkTouches + 1
end
end
end
local function OnTouchChanged(input, processed)
if fingerTouches[input] == nil then
if isDynamicThumbstickEnabled then
return
end
fingerTouches[input] = processed
if not processed then
NumUnsunkTouches = NumUnsunkTouches + 1
end
end
if NumUnsunkTouches == 1 then
if fingerTouches[input] == false then
panBeginLook = panBeginLook or this:GetCameraLook()
startPos = startPos or input.Position
lastPos = lastPos or startPos
this.UserPanningTheCamera = true
local delta = input.Position - lastPos
delta = Vector2.new(delta.X, delta.Y * GameSettings:GetCameraYInvertValue())
if this.PanEnabled then
local desiredXYVector = this:ScreenTranslationToAngle(delta) * TOUCH_SENSITIVTY
this.RotateInput = this.RotateInput + desiredXYVector
end
lastPos = input.Position
end
else
panBeginLook = nil
startPos = nil
lastPos = nil
this.UserPanningTheCamera = false
end
if NumUnsunkTouches == 2 then
local unsunkTouches = {}
for touch, wasSunk in pairs(fingerTouches) do
if not wasSunk then
table.insert(unsunkTouches, touch)
end
end
if #unsunkTouches == 2 then
local difference = (unsunkTouches[1].Position - unsunkTouches[2].Position).magnitude
if StartingDiff and pinchBeginZoom then
local scale = difference / math_max(0.01, StartingDiff)
local clampedScale = clamp(0.1, 10, scale)
if this.ZoomEnabled then
this:ZoomCamera(pinchBeginZoom / clampedScale)
end
else
StartingDiff = difference
pinchBeginZoom = this:GetCameraActualZoom()
end
end
else
StartingDiff = nil
pinchBeginZoom = nil
end
end
local function calcLookBehindRotateInput(torso)
if torso then
local newDesiredLook = (torso.CFrame.lookVector - Vector3.new(0,0.23,0)).unit
local horizontalShift = findAngleBetweenXZVectors(newDesiredLook, this:GetCameraLook())
local vertShift = math.asin(this:GetCameraLook().y) - math.asin(newDesiredLook.y)
if not IsFinite(horizontalShift) then
horizontalShift = 0
end
if not IsFinite(vertShift) then
vertShift = 0
end
return Vector2.new(horizontalShift, vertShift)
end
return nil
end
local OnTouchTap = nil
if not touchWorkspaceEventEnabled then
OnTouchTap = function(position)
if isDynamicThumbstickEnabled and not IsAToolEquipped then
if lastTapTime and tick() - lastTapTime < MAX_TIME_FOR_DOUBLE_TAP then
local tween = {
from = this:GetCameraZoom(),
to = DefaultZoom,
start = tick(),
duration = 0.2,
func = function(from, to, alpha)
this:ZoomCamera(from + (to - from)*alpha)
return to
end
}
tweens["Zoom"] = tween
else
local humanoid = this:GetHumanoid()
if humanoid then
local player = PlayersService.LocalPlayer
if player and player.Character then
if humanoid and humanoid.Torso then
local tween = {
from = this.RotateInput,
to = calcLookBehindRotateInput(humanoid.Torso),
start = tick(),
duration = 0.2,
func = function(from, to, alpha)
to = calcLookBehindRotateInput(humanoid.Torso)
if to then
this.RotateInput = from + (to - from)*alpha
end
return to
end
}
tweens["Rotate"] = tween
-- reset old camera info so follow cam doesn't rotate us
this.LastCameraTransform = nil
end
end
end
end
lastTapTime = tick()
end
end
end
local function IsTouchTap(input)
-- We can't make the assumption that the input exists in the inputStartPositions because we may have switched from a different camera type.
if inputStartPositions[input] then
local posDelta = (inputStartPositions[input] - input.Position).magnitude
if posDelta < MAX_TAP_POS_DELTA then
local timeDelta = inputStartTimes[input] - tick()
if timeDelta < MAX_TAP_TIME_DELTA then
return true
end
end
end
return false
end
local function OnTouchEnded(input, processed)
if fingerTouches[input] == false then
if NumUnsunkTouches == 1 then
panBeginLook = nil
startPos = nil
lastPos = nil
this.UserPanningTheCamera = false
if not touchWorkspaceEventEnabled and IsTouchTap(input) then
OnTouchTap(input.Position)
end
elseif NumUnsunkTouches == 2 then
StartingDiff = nil
pinchBeginZoom = nil
end
end
if fingerTouches[input] ~= nil and fingerTouches[input] == false then
NumUnsunkTouches = NumUnsunkTouches - 1
end
fingerTouches[input] = nil
inputStartPositions[input] = nil
inputStartTimes[input] = nil
end
local function OnMousePanButtonPressed(input, processed)
if processed then return end
this:UpdateMouseBehavior()
panBeginLook = panBeginLook or this:GetCameraLook()
startPos = startPos or input.Position
lastPos = lastPos or startPos
this.UserPanningTheCamera = true
end
local function OnMousePanButtonReleased(input, processed)
this:UpdateMouseBehavior()
if not (isRightMouseDown or isMiddleMouseDown) then
panBeginLook = nil
startPos = nil
lastPos = nil
this.UserPanningTheCamera = false
end
end
local function OnMouse2Down(input, processed)
if processed then return end
isRightMouseDown = true
OnMousePanButtonPressed(input, processed)
end
local function OnMouse2Up(input, processed)
isRightMouseDown = false
OnMousePanButtonReleased(input, processed)
end
local function OnMouse3Down(input, processed)
if processed then return end
isMiddleMouseDown = true
OnMousePanButtonPressed(input, processed)
end
local function OnMouse3Up(input, processed)
isMiddleMouseDown = false
OnMousePanButtonReleased(input, processed)
end
local function OnMouseMoved(input, processed)
if not hasGameLoaded and VRService.VREnabled then
return
end
local inputDelta = input.Delta
inputDelta = Vector2.new(inputDelta.X, inputDelta.Y * GameSettings:GetCameraYInvertValue())
if startPos and lastPos and panBeginLook then
local currPos = lastPos + input.Delta
local totalTrans = currPos - startPos
if this.PanEnabled then
local desiredXYVector = this:MouseTranslationToAngle(inputDelta) * MOUSE_SENSITIVITY
this.RotateInput = this.RotateInput + desiredXYVector
end
lastPos = currPos
elseif this:IsInFirstPerson() or this:GetShiftLock() then
if this.PanEnabled then
local desiredXYVector = this:MouseTranslationToAngle(inputDelta) * MOUSE_SENSITIVITY
this.RotateInput = this.RotateInput + desiredXYVector
end
end
end
local function OnMouseWheel(input, processed)
if not hasGameLoaded and VRService.VREnabled then
return
end
if not processed then
if this.ZoomEnabled then
this:ZoomCameraBy(clamp(-1, 1, -input.Position.Z) * 1.4)
end
end
end
local function round(num)
return math_floor(num + 0.5)
end
local eight2Pi = math_pi / 4
local function rotateVectorByAngleAndRound(camLook, rotateAngle, roundAmount)
if camLook ~= ZERO_VECTOR3 then
camLook = camLook.unit
local currAngle = math_atan2(camLook.z, camLook.x)
local newAngle = round((math_atan2(camLook.z, camLook.x) + rotateAngle) / roundAmount) * roundAmount
return newAngle - currAngle
end
return 0
end
local function OnKeyDown(input, processed)
if not hasGameLoaded and VRService.VREnabled then
return
end
if processed then return end
if this.ZoomEnabled then
if input.KeyCode == Enum.KeyCode.I then
this:ZoomCameraBy(-5)
elseif input.KeyCode == Enum.KeyCode.O then
this:ZoomCameraBy(5)
end
end
if panBeginLook == nil and this.KeyPanEnabled then
if input.KeyCode == Enum.KeyCode.Left then
this.TurningLeft = true
elseif input.KeyCode == Enum.KeyCode.Right then
this.TurningRight = true
elseif input.KeyCode == Enum.KeyCode.Comma then
local angle = rotateVectorByAngleAndRound(this:GetCameraLook() * Vector3.new(1,0,1), -eight2Pi * (3/4), eight2Pi)
if angle ~= 0 then
this.RotateInput = this.RotateInput + Vector2.new(angle, 0)
this.LastUserPanCamera = tick()
this.LastCameraTransform = nil
end
elseif input.KeyCode == Enum.KeyCode.Period then
local angle = rotateVectorByAngleAndRound(this:GetCameraLook() * Vector3.new(1,0,1), eight2Pi * (3/4), eight2Pi)
if angle ~= 0 then
this.RotateInput = this.RotateInput + Vector2.new(angle, 0)
this.LastUserPanCamera = tick()
this.LastCameraTransform = nil
end
elseif input.KeyCode == Enum.KeyCode.PageUp then
--elseif input.KeyCode == Enum.KeyCode.Home then
this.RotateInput = this.RotateInput + Vector2.new(0,math.rad(15))
this.LastCameraTransform = nil
elseif input.KeyCode == Enum.KeyCode.PageDown then
--elseif input.KeyCode == Enum.KeyCode.End then
this.RotateInput = this.RotateInput + Vector2.new(0,math.rad(-15))
this.LastCameraTransform = nil
end
end
end
local function OnKeyUp(input, processed)
if input.KeyCode == Enum.KeyCode.Left then
this.TurningLeft = false
elseif input.KeyCode == Enum.KeyCode.Right then
this.TurningRight = false
end
end
local lastThumbstickRotate = nil
local numOfSeconds = 0.7
local currentSpeed = 0
local maxSpeed = 6
local vrMaxSpeed = 4
local lastThumbstickPos = Vector2.new(0,0)
local ySensitivity = 0.65
local lastVelocity = nil
-- K is a tunable parameter that changes the shape of the S-curve
-- the larger K is the more straight/linear the curve gets
local k = 0.35
local lowerK = 0.8
local function SCurveTranform(t)
t = clamp(-1,1,t)
if t >= 0 then
return (k*t) / (k - t + 1)
end
return -((lowerK*-t) / (lowerK + t + 1))
end
-- DEADZONE
local DEADZONE = 0.1
local function toSCurveSpace(t)
return (1 + DEADZONE) * (2*math.abs(t) - 1) - DEADZONE
end
local function fromSCurveSpace(t)
return t/2 + 0.5
end
local function gamepadLinearToCurve(thumbstickPosition)
local function onAxis(axisValue)
local sign = 1
if axisValue < 0 then
sign = -1
end
local point = fromSCurveSpace(SCurveTranform(toSCurveSpace(math.abs(axisValue))))
point = point * sign
return clamp(-1, 1, point)
end
return Vector2_new(onAxis(thumbstickPosition.x), onAxis(thumbstickPosition.y))
end
function this:UpdateGamepad()
local gamepadPan = this.GamepadPanningCamera
if gamepadPan and (hasGameLoaded or not VRService.VREnabled) then
gamepadPan = gamepadLinearToCurve(gamepadPan)
local currentTime = tick()
if gamepadPan.X ~= 0 or gamepadPan.Y ~= 0 then
this.userPanningTheCamera = true
elseif gamepadPan == ZERO_VECTOR2 then
lastThumbstickRotate = nil
if lastThumbstickPos == ZERO_VECTOR2 then
currentSpeed = 0
end
end
local finalConstant = 0
if lastThumbstickRotate then
if VRService.VREnabled then
currentSpeed = vrMaxSpeed
else
local elapsedTime = (currentTime - lastThumbstickRotate) * 10
currentSpeed = currentSpeed + (maxSpeed * ((elapsedTime*elapsedTime)/numOfSeconds))
if currentSpeed > maxSpeed then currentSpeed = maxSpeed end
if lastVelocity then
local velocity = (gamepadPan - lastThumbstickPos)/(currentTime - lastThumbstickRotate)
local velocityDeltaMag = (velocity - lastVelocity).magnitude
if velocityDeltaMag > 12 then
currentSpeed = currentSpeed * (20/velocityDeltaMag)
if currentSpeed > maxSpeed then currentSpeed = maxSpeed end
end
end
end
local success, gamepadCameraSensitivity = pcall(function() return GameSettings.GamepadCameraSensitivity end)
finalConstant = success and (gamepadCameraSensitivity * currentSpeed) or currentSpeed
lastVelocity = (gamepadPan - lastThumbstickPos)/(currentTime - lastThumbstickRotate)
end
lastThumbstickPos = gamepadPan
lastThumbstickRotate = currentTime
return Vector2_new( gamepadPan.X * finalConstant, gamepadPan.Y * finalConstant * ySensitivity * GameSettings:GetCameraYInvertValue())
end
return ZERO_VECTOR2
end
local InputBeganConn, InputChangedConn, InputEndedConn, MenuOpenedConn, ShiftLockToggleConn, GamepadConnectedConn, GamepadDisconnectedConn, TouchActivateConn = nil, nil, nil, nil, nil, nil, nil, nil
function this:DisconnectInputEvents()
if InputBeganConn then
InputBeganConn:disconnect()
InputBeganConn = nil
end
if InputChangedConn then
InputChangedConn:disconnect()
InputChangedConn = nil
end
if InputEndedConn then
InputEndedConn:disconnect()
InputEndedConn = nil
end
if MenuOpenedConn then
MenuOpenedConn:disconnect()
MenuOpenedConn = nil
end
if ShiftLockToggleConn then
ShiftLockToggleConn:disconnect()
ShiftLockToggleConn = nil
end
if GamepadConnectedConn then
GamepadConnectedConn:disconnect()
GamepadConnectedConn = nil
end
if GamepadDisconnectedConn then
GamepadDisconnectedConn:disconnect()
GamepadDisconnectedConn = nil
end
if subjectStateChangedConn then
subjectStateChangedConn:disconnect()
subjectStateChangedConn = nil
end
if workspaceChangedConn then
workspaceChangedConn:disconnect()
workspaceChangedConn = nil
end
if TouchActivateConn then
TouchActivateConn:disconnect()
TouchActivateConn = nil
end
this.TurningLeft = false
this.TurningRight = false
this.LastCameraTransform = nil
self.LastSubjectCFrame = nil
this.UserPanningTheCamera = false
this.RotateInput = Vector2.new()
this.GamepadPanningCamera = Vector2.new(0,0)
-- Reset input states
startPos = nil
lastPos = nil
panBeginLook = nil
isRightMouseDown = false
isMiddleMouseDown = false
fingerTouches = {}
NumUnsunkTouches = 0
StartingDiff = nil
pinchBeginZoom = nil
-- Unlock mouse for example if right mouse button was being held down
if UserInputService.MouseBehavior ~= Enum.MouseBehavior.LockCenter then
UserInputService.MouseBehavior = Enum.MouseBehavior.Default
end
end
function this:ResetInputStates()
isRightMouseDown = false
isMiddleMouseDown = false
OnMousePanButtonReleased() -- this function doesn't seem to actually need parameters
if UserInputService.TouchEnabled then
--[[menu opening was causing serious touch issues
this should disable all active touch events if
they're active when menu opens.]]
for inputObject, value in pairs(fingerTouches) do
fingerTouches[inputObject] = nil
end
panBeginLook = nil
startPos = nil
lastPos = nil
this.UserPanningTheCamera = false
StartingDiff = nil
pinchBeginZoom = nil
NumUnsunkTouches = 0
end
end
function this.getGamepadPan(name, state, input)
if input.UserInputType == this.activeGamepad and input.KeyCode == Enum.KeyCode.Thumbstick2 then
if state == Enum.UserInputState.Cancel then
this.GamepadPanningCamera = ZERO_VECTOR2
return
end
local inputVector = Vector2.new(input.Position.X, -input.Position.Y)
if inputVector.magnitude > THUMBSTICK_DEADZONE then
this.GamepadPanningCamera = Vector2_new(input.Position.X, -input.Position.Y)
else
this.GamepadPanningCamera = ZERO_VECTOR2
end
end
end
function this.doGamepadZoom(name, state, input)
if input.UserInputType == this.activeGamepad and input.KeyCode == Enum.KeyCode.ButtonR3 and state == Enum.UserInputState.Begin then
if this.ZoomEnabled then
if this:GetCameraZoom() > 0.5 then
this:ZoomCamera(0)
else
this:ZoomCamera(10)
end
end
end
end
function this:BindGamepadInputActions()
ContextActionService:BindAction("RootCamGamepadPan", this.getGamepadPan, false, Enum.KeyCode.Thumbstick2)
ContextActionService:BindAction("RootCamGamepadZoom", this.doGamepadZoom, false, Enum.KeyCode.ButtonR3)
end
function this:ConnectInputEvents()
InputBeganConn = UserInputService.InputBegan:connect(function(input, processed)
if input.UserInputType == Enum.UserInputType.Touch then
OnTouchBegan(input, processed)
elseif input.UserInputType == Enum.UserInputType.MouseButton2 then
OnMouse2Down(input, processed)
elseif input.UserInputType == Enum.UserInputType.MouseButton3 then
OnMouse3Down(input, processed)
end
-- Keyboard
if input.UserInputType == Enum.UserInputType.Keyboard then
OnKeyDown(input, processed)
end
end)
InputChangedConn = UserInputService.InputChanged:connect(function(input, processed)
if input.UserInputType == Enum.UserInputType.Touch then
OnTouchChanged(input, processed)
elseif input.UserInputType == Enum.UserInputType.MouseMovement then
OnMouseMoved(input, processed)
elseif input.UserInputType == Enum.UserInputType.MouseWheel then
OnMouseWheel(input, processed)
end
end)
InputEndedConn = UserInputService.InputEnded:connect(function(input, processed)
if input.UserInputType == Enum.UserInputType.Touch then
OnTouchEnded(input, processed)
elseif input.UserInputType == Enum.UserInputType.MouseButton2 then
OnMouse2Up(input, processed)
elseif input.UserInputType == Enum.UserInputType.MouseButton3 then
OnMouse3Up(input, processed)
end
-- Keyboard
if input.UserInputType == Enum.UserInputType.Keyboard then
OnKeyUp(input, processed)
end
end)
if touchWorkspaceEventEnabled then
TouchActivateConn = UserInputService.TouchTapInWorld:connect(function(touchPos, processed)
if isDynamicThumbstickEnabled and not processed and not IsAToolEquipped and positionIntersectsGuiObject(touchPos, GestureArea) then
if lastTapTime and tick() - lastTapTime < MAX_TIME_FOR_DOUBLE_TAP then
local tween = {
from = this:GetCameraZoom(),
to = DefaultZoom,
start = tick(),
duration = 0.2,
func = function(from, to, alpha)
this:ZoomCamera(from + (to - from)*alpha)
return to
end
}
tweens["Zoom"] = tween
else
local humanoid = this:GetHumanoid()
if humanoid then
local player = PlayersService.LocalPlayer
if player and player.Character then
if humanoid and humanoid.Torso then
local tween = {
from = this.RotateInput,
to = calcLookBehindRotateInput(humanoid.Torso),
start = tick(),
duration = 0.2,
func = function(from, to, alpha)
to = calcLookBehindRotateInput(humanoid.Torso)
if to then
this.RotateInput = from + (to - from)*alpha
end
return to
end
}
tweens["Rotate"] = tween
-- reset old camera info so follow cam doesn't rotate us
this.LastCameraTransform = nil
end
end
end
end
lastTapTime = tick()
end
end)
end
MenuOpenedConn = GuiService.MenuOpened:connect(function()
this:ResetInputStates()
end)
workspaceChangedConn = workspace:GetPropertyChangedSignal("CurrentCamera"):Connect(onCurrentCameraChanged)
if workspace.CurrentCamera then
onCurrentCameraChanged()
end
ShiftLockToggleConn = ShiftLockController.OnShiftLockToggled.Event:connect(function()
this:UpdateMouseBehavior()
end)
this.RotateInput = Vector2.new()
this.activeGamepad = nil
local function assignActivateGamepad()
local connectedGamepads = UserInputService:GetConnectedGamepads()
if #connectedGamepads > 0 then
for i = 1, #connectedGamepads do
if this.activeGamepad == nil then
this.activeGamepad = connectedGamepads[i]
elseif connectedGamepads[i].Value < this.activeGamepad.Value then
this.activeGamepad = connectedGamepads[i]
end
end
end
if this.activeGamepad == nil then -- nothing is connected, at least set up for gamepad1
this.activeGamepad = Enum.UserInputType.Gamepad1
end
end
GamepadConnectedConn = UserInputService.GamepadDisconnected:connect(function(gamepadEnum)
if this.activeGamepad ~= gamepadEnum then return end
this.activeGamepad = nil
assignActivateGamepad()
end)
GamepadDisconnectedConn = UserInputService.GamepadConnected:connect(function(gamepadEnum)
if this.activeGamepad == nil then
assignActivateGamepad()
end
end)
self:BindGamepadInputActions()
assignActivateGamepad()
-- set mouse behavior
self:UpdateMouseBehavior()
end
--Process tweens related to tap-to-recenter and double-tap-to-zoom
--Needs to be called from specific cameras on each update
function this:ProcessTweens()
for name, tween in pairs(tweens) do
local alpha = math.min(1.0, (tick() - tween.start)/tween.duration)
tween.to = tween.func(tween.from, tween.to, alpha)
if math.abs(1 - alpha) < 0.0001 then
tweens[name] = nil
end
end
end
function this:SetEnabled(newState)
if newState ~= self.Enabled then
self.Enabled = newState
if self.Enabled then
self:ConnectInputEvents()
self.cframe = workspace.CurrentCamera.CFrame
else
self:DisconnectInputEvents()
end
end
end
local function OnPlayerAdded(player)
player.Changed:connect(function(prop)
if this.Enabled then
if prop == "CameraMode" or prop == "CameraMaxZoomDistance" or prop == "CameraMinZoomDistance" then
this:ZoomCameraFixedBy(0)
end
end
end)
local function OnCharacterAdded(newCharacter)
local humanoid = findPlayerHumanoid(player)
local start = tick()
while tick() - start < 0.3 and (humanoid == nil or humanoid.Torso == nil) do
wait()
humanoid = findPlayerHumanoid(player)
end
if humanoid and humanoid.Torso and player.Character == newCharacter then
local newDesiredLook = (humanoid.Torso.CFrame.lookVector - Vector3.new(0,0.23,0)).unit
local horizontalShift = findAngleBetweenXZVectors(newDesiredLook, this:GetCameraLook())
local vertShift = math.asin(this:GetCameraLook().y) - math.asin(newDesiredLook.y)
if not IsFinite(horizontalShift) then
horizontalShift = 0
end
if not IsFinite(vertShift) then
vertShift = 0
end
this.RotateInput = Vector2.new(horizontalShift, vertShift)
-- reset old camera info so follow cam doesn't rotate us
this.LastCameraTransform = nil
end
-- Need to wait for camera cframe to update before we zoom in
-- Not waiting will force camera to original cframe
wait()
this:ZoomCamera(this.DefaultZoom)
end
player.CharacterAdded:connect(function(character)
if this.Enabled or SetCameraOnSpawn then
OnCharacterAdded(character)
SetCameraOnSpawn = false
end
end)
if player.Character then
spawn(function() OnCharacterAdded(player.Character) end)
end
end
if PlayersService.LocalPlayer then
OnPlayerAdded(PlayersService.LocalPlayer)
end
PlayersService.ChildAdded:connect(function(child)
if child and PlayersService.LocalPlayer == child then
OnPlayerAdded(PlayersService.LocalPlayer)
end
end)
local function OnGameLoaded()
hasGameLoaded = true
end
spawn(function()
if game:IsLoaded() then
OnGameLoaded()
else
game.Loaded:wait()
OnGameLoaded()
end
end)
local function OnDynamicThumbstickEnabled()
if UserInputService.TouchEnabled then
isDynamicThumbstickEnabled = true
end
end
local function OnDynamicThumbstickDisabled()
isDynamicThumbstickEnabled = false
end
local function OnGameSettingsTouchMovementModeChanged()
if LocalPlayer.DevTouchMovementMode == Enum.DevTouchMovementMode.UserChoice then
if GameSettings.TouchMovementMode.Name == "DynamicThumbstick" then
OnDynamicThumbstickEnabled()
else
OnDynamicThumbstickDisabled()
end
end
end
local function OnDevTouchMovementModeChanged()
if LocalPlayer.DevTouchMovementMode.Name == "DynamicThumbstick" then
OnDynamicThumbstickEnabled()
else
OnGameSettingsTouchMovementModeChanged()
end
end
if PlayersService.LocalPlayer then
PlayersService.LocalPlayer.Changed:Connect(function(prop)
if prop == "DevTouchMovementMode" then
OnDevTouchMovementModeChanged()
end
end)
OnDevTouchMovementModeChanged()
end
GameSettings.Changed:Connect(function(prop)
if prop == "TouchMovementMode" then
OnGameSettingsTouchMovementModeChanged()
end
end)
OnGameSettingsTouchMovementModeChanged()
GameSettings:SetCameraYInvertVisible()
pcall(function() GameSettings:SetGamepadCameraSensitivityVisible() end)
return this
end
return CreateCamera
local RootCameraCreator = require(script.Parent)
local function CreateScriptableCamera()
local module = RootCameraCreator()
function module:Update()
end
return module
end
return CreateScriptableCamera
local PlayersService = game:GetService('Players')
local RootCameraCreator = require(script.Parent)
local ZERO_VECTOR2 = Vector2.new(0, 0)
local CFrame_new = CFrame.new
local math_min = math.min
local function CreateTrackCamera()
local module = RootCameraCreator()
local lastUpdate = tick()
function module:Update()
local now = tick()
local userPanningTheCamera = (self.UserPanningTheCamera == true)
local camera = workspace.CurrentCamera
local player = PlayersService.LocalPlayer
if lastUpdate == nil or now - lastUpdate > 1 then
module:ResetCameraLook()
self.LastCameraTransform = nil
end
if lastUpdate then
-- Cap out the delta to 0.1 so we don't get some crazy things when we re-resume from
local delta = math_min(0.1, now - lastUpdate)
local gamepadRotation = self:UpdateGamepad()
if gamepadRotation ~= ZERO_VECTOR2 then
userPanningTheCamera = true
self.RotateInput = self.RotateInput + (gamepadRotation * delta)
end
end
local subjectPosition = self:GetSubjectPosition()
if subjectPosition and player and camera then
local zoom = self:GetCameraZoom()
if zoom <= 0 then
zoom = 0.1
end
local newLookVector = self:RotateCamera(self:GetCameraLook(), self.RotateInput)
self.RotateInput = ZERO_VECTOR2
camera.Focus = CFrame_new(subjectPosition)
camera.CFrame = CFrame_new(subjectPosition - (zoom * newLookVector), subjectPosition)
self.LastCameraTransform = camera.CoordinateFrame
end
lastUpdate = now
end
return module
end
return CreateTrackCamera
-- SolarCrane
local MAX_TWEEN_RATE = 2.8 -- per second
local function clamp(low, high, num)
return (num > high and high or num < low and low or num)
end
local math_floor = math.floor
local function Round(num, places)
local decimalPivot = 10^places
return math_floor(num * decimalPivot + 0.5) / decimalPivot
end
local function CreateTransparencyController()
local module = {}
local LastUpdate = tick()
local TransparencyDirty = false
local Enabled = false
local LastTransparency = nil
local DescendantAddedConn, DescendantRemovingConn = nil, nil
local ToolDescendantAddedConns = {}
local ToolDescendantRemovingConns = {}
local CachedParts = {}
local function HasToolAncestor(object)
if object.Parent == nil then return false end
return object.Parent:IsA('Tool') or HasToolAncestor(object.Parent)
end
local function IsValidPartToModify(part)
if part:IsA('BasePart') or part:IsA('Decal') then
return not HasToolAncestor(part)
end
return false
end
local function CachePartsRecursive(object)
if object then
if IsValidPartToModify(object) then
CachedParts[object] = true
TransparencyDirty = true
end
for _, child in pairs(object:GetChildren()) do
CachePartsRecursive(child)
end
end
end
local function TeardownTransparency()
for child, _ in pairs(CachedParts) do
child.LocalTransparencyModifier = 0
end
CachedParts = {}
TransparencyDirty = true
LastTransparency = nil
if DescendantAddedConn then
DescendantAddedConn:disconnect()
DescendantAddedConn = nil
end
if DescendantRemovingConn then
DescendantRemovingConn:disconnect()
DescendantRemovingConn = nil
end
for object, conn in pairs(ToolDescendantAddedConns) do
conn:disconnect()
ToolDescendantAddedConns[object] = nil
end
for object, conn in pairs(ToolDescendantRemovingConns) do
conn:disconnect()
ToolDescendantRemovingConns[object] = nil
end
end
local function SetupTransparency(character)
TeardownTransparency()
if DescendantAddedConn then DescendantAddedConn:disconnect() end
DescendantAddedConn = character.DescendantAdded:connect(function(object)
-- This is a part we want to invisify
if IsValidPartToModify(object) then
CachedParts[object] = true
TransparencyDirty = true
-- There is now a tool under the character
elseif object:IsA('Tool') then
if ToolDescendantAddedConns[object] then ToolDescendantAddedConns[object]:disconnect() end
ToolDescendantAddedConns[object] = object.DescendantAdded:connect(function(toolChild)
CachedParts[toolChild] = nil
if toolChild:IsA('BasePart') or toolChild:IsA('Decal') then
-- Reset the transparency
toolChild.LocalTransparencyModifier = 0
end
end)
if ToolDescendantRemovingConns[object] then ToolDescendantRemovingConns[object]:disconnect() end
ToolDescendantRemovingConns[object] = object.DescendantRemoving:connect(function(formerToolChild)
wait() -- wait for new parent
if character and formerToolChild and formerToolChild:IsDescendantOf(character) then
if IsValidPartToModify(formerToolChild) then
CachedParts[formerToolChild] = true
TransparencyDirty = true
end
end
end)
end
end)
if DescendantRemovingConn then DescendantRemovingConn:disconnect() end
DescendantRemovingConn = character.DescendantRemoving:connect(function(object)
if CachedParts[object] then
CachedParts[object] = nil
-- Reset the transparency
object.LocalTransparencyModifier = 0
end
end)
CachePartsRecursive(character)
end
function module:SetEnabled(newState)
if Enabled ~= newState then
Enabled = newState
self:Update()
end
end
function module:SetSubject(subject)
local character = nil
if subject and subject:IsA("Humanoid") then
character = subject.Parent
end
if subject and subject:IsA("VehicleSeat") and subject.Occupant then
character = subject.Occupant.Parent
end
if character then
SetupTransparency(character)
else
TeardownTransparency()
end
end
function module:Update()
local instant = false
local now = tick()
local currentCamera = workspace.CurrentCamera
if currentCamera then
local transparency = 0
if not Enabled then
instant = true
else
local distance = (currentCamera.Focus.p - currentCamera.CoordinateFrame.p).magnitude
transparency = (7 - distance) / 5
if transparency < 0.5 then
transparency = 0
end
if LastTransparency then
local deltaTransparency = transparency - LastTransparency
-- Don't tween transparency if it is instant or your character was fully invisible last frame
if not instant and transparency < 1 and LastTransparency < 0.95 then
local maxDelta = MAX_TWEEN_RATE * (now - LastUpdate)
deltaTransparency = clamp(-maxDelta, maxDelta, deltaTransparency)
end
transparency = LastTransparency + deltaTransparency
else
TransparencyDirty = true
end
transparency = clamp(0, 1, Round(transparency, 2))
end
if TransparencyDirty or LastTransparency ~= transparency then
for child, _ in pairs(CachedParts) do
child.LocalTransparencyModifier = transparency
end
TransparencyDirty = false
LastTransparency = transparency
end
end
LastUpdate = now
end
return module
end
return CreateTransparencyController
local PlayersService = game:GetService('Players')
local UserInputService = game:GetService('UserInputService')
local VRService = game:GetService("VRService")
local ContextActionService = game:GetService("ContextActionService")
local LocalPlayer = PlayersService.LocalPlayer
local RootCameraCreator = require(script.Parent)
local XZ_VECTOR = Vector3.new(1, 0, 1)
local ZERO_VECTOR2 = Vector2.new(0, 0)
local PRESNAP_TIME_OFFSET = 0.5 --seconds
local PRESNAP_TIME_LATCH = PRESNAP_TIME_OFFSET * 10
local HEIGHT_OFFSET = 3
local HORZ_OFFSET = 4
local function CreateVRCamera()
local module = RootCameraCreator()
module.AllowOcclusion = false
local lastUpdate = tick()
local forceSnap = true
local forceSnapPoint = nil
local lastLook = Vector3.new(0, 0, -1)
local movementUpdateEventConn = nil
local lastSnapTimeEstimate = math.huge
local snapId = 0
local waitingForSnap = false
local snappedHeadOffset = VRService:GetUserCFrame(Enum.UserCFrame.Head).p
local snapPendingWhileJumping = false
local isJumping = false
local autoSnapsPaused = false
local currentDestination = nil
local function forceSnapTo(snapPoint)
forceSnap = true
forceSnapPoint = snapPoint
snapId = snapId + 1
waitingForSnap = false
end
local function onMovementUpdateEvent(updateType, arg1, arg2)
if updateType == "targetPoint" then
currentDestination = arg1
end
if updateType == "timing" then
local estimatedTimeRemaining = arg1
local snapPoint = arg2
if waitingForSnap and estimatedTimeRemaining > lastSnapTimeEstimate then
--our estimate grew, so cancel this snap and potentially re-evaluate it
waitingForSnap = false
snapId = snapId + 1
end
if estimatedTimeRemaining < PRESNAP_TIME_LATCH and estimatedTimeRemaining > PRESNAP_TIME_OFFSET then
waitingForSnap = true
snapId = snapId + 1
local thisSnapId = snapId
local timeToWait = estimatedTimeRemaining - PRESNAP_TIME_OFFSET
coroutine.wrap(function()
wait(timeToWait)
if waitingForSnap and snapId == thisSnapId then
waitingForSnap = false
forceSnap = true
forceSnapPoint = snapPoint
end
end)()
end
elseif updateType == "shortPath" or updateType == "pathFailure" or updateType == "offtrack" and not autoSnapsPaused then
if isJumping then
snapPendingWhileJumping = true
return
end
local snapPoint = arg1
forceSnapTo(snapPoint)
elseif updateType == "force" then
snapPendingWhileJumping = false
isJumping = false
forceSnapTo(nil)
end
end
local function onResetCameraAction(actionName, inputState, inputObj)
if inputState == Enum.UserInputState.Begin then
autoSnapsPaused = true
onMovementUpdateEvent("force", nil, nil)
else
autoSnapsPaused = false
end
end
local function bindActions(bind)
if bind then
ContextActionService:BindActionAtPriority("ResetCameraVR", onResetCameraAction, false, Enum.ContextActionPriority.Low.Value, Enum.KeyCode.ButtonL2)
else
autoSnapsPaused = false
ContextActionService:UnbindAction("ResetCameraVR")
end
end
local lastKnownTorsoPosition = Vector3.new()
local function onCharacterAdded(character)
local humanoid = character:WaitForChild("Humanoid")
humanoid.StateChanged:connect(function(oldState, newState)
local camera = workspace.CurrentCamera
if not camera or camera.CameraSubject ~= humanoid or not VRService.VREnabled then
return
end
if newState == Enum.HumanoidStateType.Jumping then
isJumping = true
elseif newState == Enum.HumanoidStateType.Landed then
if snapPendingWhileJumping then
forceSnapTo(nil)
snapPendingWhileJumping = false
end
isJumping = false
elseif newState == Enum.HumanoidStateType.Dead then
forceSnapTo(nil)
elseif newState == Enum.HumanoidStateType.Swimming then
if isJumping and snapPendingWhileJumping then
--Jumped into water and let go of controls during flight, treat as a normal landing
forceSnapTo(nil)
snapPendingWhileJumping = false
end
isJumping = false
end
end)
local humanoidRootPart = humanoid.Torso
humanoidRootPart.Changed:connect(function(property)
local camera = workspace.CurrentCamera
local cameraSubject = camera.CameraSubject
if camera and cameraSubject == humanoid and property == "CFrame" or property == "Position" then
if (humanoidRootPart.Position - lastKnownTorsoPosition).magnitude > 5 then
forceSnapTo(nil)
end
end
end)
end
if LocalPlayer.Character then
onCharacterAdded(LocalPlayer.Character)
end
LocalPlayer.CharacterAdded:connect(onCharacterAdded)
spawn(function()
local rootCamera = script.Parent
if not rootCamera then return end
local cameraScript = rootCamera.Parent
if not cameraScript then return end
local playerScripts = cameraScript.Parent
if not playerScripts then return end
local controlScript = playerScripts:WaitForChild("ControlScript")
local masterControlModule = controlScript:WaitForChild("MasterControl")
local vrNavigationModule = masterControlModule:WaitForChild("VRNavigation")
local movementUpdateEvent = vrNavigationModule:WaitForChild("MovementUpdate")
movementUpdateEventConn = movementUpdateEvent.Event:connect(onMovementUpdateEvent)
end)
local onCameraSubjectChangedConn = nil
local function onCameraSubjectChanged()
local camera = workspace.CurrentCamera
if camera and camera.CameraSubject then
wait(1)
forceSnap = true
end
end
local function onCurrentCameraChanged()
if onCameraSubjectChangedConn then
onCameraSubjectChangedConn:disconnect()
onCameraSubjectChangedConn = nil
end
if workspace.CurrentCamera then
onCameraSubjectChangedConn = workspace.CurrentCamera:GetPropertyChangedSignal("CameraSubject"):connect(onCameraSubjectChanged)
onCameraSubjectChanged()
end
end
workspace:GetPropertyChangedSignal("CurrentCamera"):connect(onCurrentCameraChanged)
onCurrentCameraChanged()
local function onVREnabled()
bindActions(VRService.VREnabled)
end
VRService:GetPropertyChangedSignal("VREnabled"):connect(onVREnabled)
function module:Update()
local now = tick()
local timeDelta = now - lastUpdate
local camera = workspace.CurrentCamera
local cameraSubject = camera and camera.CameraSubject
local player = PlayersService.LocalPlayer
local subjectPosition = self:GetSubjectPosition()
local zoom = self:GetCameraZoom()
local subjectPosition = self:GetSubjectPosition()
local gamepadRotation = self:UpdateGamepad()
if subjectPosition and currentDestination then
local dist = (currentDestination - subjectPosition).magnitude
if dist < 10 then
forceSnap = true
forceSnapPoint = currentDestination
currentDestination = nil
end
end
if cameraSubject and cameraSubject:IsA("Humanoid") then
local rootPart = cameraSubject.RootPart
if rootPart then
lastKnownTorsoPosition = rootPart.Position
end
end
local look = lastLook
if subjectPosition and forceSnap then
forceSnap = false
local newFocusPoint = subjectPosition
if forceSnapPoint then
newFocusPoint = Vector3.new(forceSnapPoint.X, subjectPosition.Y, forceSnapPoint.Z)
end
camera.Focus = CFrame.new(newFocusPoint)
forceSnapPoint = nil
look = camera:GetRenderCFrame().lookVector
snappedHeadOffset = VRService:GetUserCFrame(Enum.UserCFrame.Head).p
end
if subjectPosition and player and camera then
local cameraFocusP = camera.Focus.p
self.RotateInput = ZERO_VECTOR2
look = (look * XZ_VECTOR).unit
camera.CFrame = CFrame.new(cameraFocusP - (HORZ_OFFSET * look)) + Vector3.new(0, HEIGHT_OFFSET, 0) - snappedHeadOffset
self.LastCameraTransform = camera.CFrame
self.LastCameraFocus = camera.Focus
end
lastLook = look
lastUpdate = now
end
return module
end
return CreateVRCamera
local PlayersService = game:GetService('Players')
local RootCameraCreator = require(script.Parent)
local ZERO_VECTOR2 = Vector2.new(0, 0)
local CFrame_new = CFrame.new
local function CreateWatchCamera()
local module = RootCameraCreator()
module.PanEnabled = false
local lastUpdate = tick()
function module:Update()
local now = tick()
local camera = workspace.CurrentCamera
local player = PlayersService.LocalPlayer
if lastUpdate == nil or now - lastUpdate > 1 then
module:ResetCameraLook()
self.LastCameraTransform = nil
self.LastZoom = nil
end
local subjectPosition = self:GetSubjectPosition()
if subjectPosition and player and camera then
local cameraLook = nil
if self.LastCameraTransform then
local humanoid = self:GetHumanoid()
if humanoid and humanoid.Torso then
-- TODO: let the paging buttons move the camera but not the mouse/touch
-- currently neither do
local diffVector = subjectPosition - self.LastCameraTransform.p
cameraLook = diffVector.unit
if self.LastZoom and self.LastZoom == self:GetCameraZoom() then
-- Don't clobber the zoom if they zoomed the camera
local zoom = diffVector.magnitude
self:ZoomCamera(zoom)
end
end
end
local zoom = self:GetCameraZoom()
if zoom <= 0 then
zoom = 0.1
end
local newLookVector = self:RotateVector(cameraLook or self:GetCameraLook(), self.RotateInput)
self.RotateInput = ZERO_VECTOR2
local newFocus = CFrame_new(subjectPosition)
local newCamCFrame = CFrame_new(newFocus.p - (zoom * newLookVector), subjectPosition)
camera.Focus = newFocus
camera.CFrame = newCamCFrame
self.LastCameraTransform = newCamCFrame
self.LastZoom = zoom
end
lastUpdate = now
end
return module
end
return CreateWatchCamera
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment