Professional Documents
Culture Documents
Wall Climb
Wall Climb
local _p = game:WaitForChild("Players")
local _plr = _p.ChildAdded:Wait()
if _plr == _p.LocalPlayer then
_plr.ChildAdded:Connect(function(cccc)
if c.Name == "PlayerScriptsLoader" then
c.Disabled = true
end
end)
end
]]
repeat wait()
a = pcall(function()
game:WaitForChild("Players").LocalPlayer:WaitForChild("PlayerScripts").ChildAdded:C
onnect(function(c)
if c.Name == "PlayerScriptsLoader"then
c.Disabled = true
end
end)
end)
if a == true then break end
until true == false
game:WaitForChild("Players").LocalPlayer:WaitForChild("PlayerScripts").ChildAdded:C
onnect(function(c)
if c.Name == "PlayerScriptsLoader"then
c.Disabled = true
end
end)
function _CameraUI()
local Players = game:GetService("Players")
local TweenService = game:GetService("TweenService")
local uiRoot
local toast
local toastIcon
local toastUpperText
local toastLowerText
uiRoot = create("ScreenGui"){
Name = "RbxCameraUI",
AutoLocalize = false,
Enabled = true,
DisplayOrder = -1, -- Appears behind default developer UI
IgnoreGuiInset = false,
ResetOnSpawn = false,
ZIndexBehavior = Enum.ZIndexBehavior.Sibling,
create("ImageLabel"){
Name = "Toast",
Visible = false,
AnchorPoint = Vector2.new(0.5, 0),
BackgroundTransparency = 1,
BorderSizePixel = 0,
Position = UDim2.new(0.5, 0, 0, 8),
Size = TOAST_CLOSED_SIZE,
Image =
"rbxasset://textures/ui/Camera/CameraToast9Slice.png",
ImageColor3 = TOAST_BACKGROUND_COLOR,
ImageRectSize = Vector2.new(6, 6),
ImageTransparency = 1,
ScaleType = Enum.ScaleType.Slice,
SliceCenter = Rect.new(3, 3, 3, 3),
ClipsDescendants = true,
create("Frame"){
Name = "IconBuffer",
BackgroundTransparency = 1,
BorderSizePixel = 0,
Position = UDim2.new(0, 0, 0, 0),
Size = UDim2.new(0, 80, 1, 0),
create("ImageLabel"){
Name = "Icon",
AnchorPoint = Vector2.new(0.5, 0.5),
BackgroundTransparency = 1,
Position = UDim2.new(0.5, 0, 0.5, 0),
Size = UDim2.new(0, 48, 0, 48),
ZIndex = 2,
Image =
"rbxasset://textures/ui/Camera/CameraToastIcon.png",
ImageColor3 = TOAST_FOREGROUND_COLOR,
ImageTransparency = 1,
}
},
create("Frame"){
Name = "TextBuffer",
BackgroundTransparency = 1,
BorderSizePixel = 0,
Position = UDim2.new(0, 80, 0, 0),
Size = UDim2.new(1, -80, 1, 0),
ClipsDescendants = true,
create("TextLabel"){
Name = "Upper",
AnchorPoint = Vector2.new(0, 1),
BackgroundTransparency = 1,
Position = UDim2.new(0, 0, 0.5, 0),
Size = UDim2.new(1, 0, 0, 19),
Font = Enum.Font.GothamSemibold,
Text = "Camera control enabled",
TextColor3 = TOAST_FOREGROUND_COLOR,
TextTransparency = 1,
TextSize = 19,
TextXAlignment = Enum.TextXAlignment.Left,
TextYAlignment = Enum.TextYAlignment.Center,
},
create("TextLabel"){
Name = "Lower",
AnchorPoint = Vector2.new(0, 0),
BackgroundTransparency = 1,
Position = UDim2.new(0, 0, 0.5, 3),
Size = UDim2.new(1, 0, 0, 15),
Font = Enum.Font.Gotham,
Text = "Right mouse button to toggle",
TextColor3 = TOAST_FOREGROUND_COLOR,
TextTransparency = 1,
TextSize = 15,
TextXAlignment = Enum.TextXAlignment.Left,
TextYAlignment = Enum.TextYAlignment.Center,
},
},
},
Parent = PlayerGui,
}
toast = uiRoot.Toast
toastIcon = toast.IconBuffer.Icon
toastUpperText = toast.TextBuffer.Upper
toastLowerText = toast.TextBuffer.Lower
initialized = true
end
local CameraUI = {}
do
-- Instantaneously disable the toast or enable for opening later on.
Used when switching camera modes.
function CameraUI.setCameraModeToastEnabled(enabled)
if not enabled and not initialized then
return
end
toast.Visible = enabled
if not enabled then
CameraUI.setCameraModeToastOpen(false)
end
end
TweenService:Create(toast, tweenInfo, {
Size = open and TOAST_OPEN_SIZE or TOAST_CLOSED_SIZE,
ImageTransparency = open and TOAST_BACKGROUND_TRANS or 1,
}):Play()
TweenService:Create(toastIcon, tweenInfo, {
ImageTransparency = open and TOAST_FOREGROUND_TRANS or 1,
}):Play()
TweenService:Create(toastUpperText, tweenInfo, {
TextTransparency = open and TOAST_FOREGROUND_TRANS or 1,
}):Play()
TweenService:Create(toastLowerText, tweenInfo, {
TextTransparency = open and TOAST_FOREGROUND_TRANS or 1,
}):Play()
end
end
return CameraUI
end
function _CameraToggleStateController()
local Players = game:GetService("Players")
local UserInputService = game:GetService("UserInputService")
local GameSettings = UserSettings():GetService("UserGameSettings")
CameraUI.setCameraModeToastEnabled(false)
return function(isFirstPerson)
local togglePan = Input.getTogglePan()
local toastTimeout = 3
CameraUI.setCameraModeToastOpen(doShow)
if togglePan then
lockStateDirty = false
end
lastTogglePanChange = tick()
lastTogglePan = togglePan
end
Input.setTogglePan(wasTogglePanOnTheLastTimeYouWentIntoFirstPerson)
end
end
if isFirstPerson then
if Input.getTogglePan() then
Mouse.Icon = CROSS_MOUSE_ICON
UserInputService.MouseBehavior =
Enum.MouseBehavior.LockCenter
--GameSettings.RotationType =
Enum.RotationType.CameraRelative
else
Mouse.Icon = ""
UserInputService.MouseBehavior = Enum.MouseBehavior.Default
--GameSettings.RotationType =
Enum.RotationType.CameraRelative
end
else
Mouse.Icon = ""
UserInputService.MouseBehavior = Enum.MouseBehavior.Default
GameSettings.RotationType = Enum.RotationType.MovementRelative
end
lastFirstPerson = isFirstPerson
end
end
function _CameraInput()
local UserInputService = game:GetService("UserInputService")
local MB_TAP_LENGTH = 0.3 -- length of time for a short mouse button tap to
be registered
rmbDown = rmbDownBindable.Event
rmbUp = rmbUpBindable.Event
UserInputService.InputBegan:Connect(function(input, gpe)
if not gpe and input.UserInputType ==
Enum.UserInputType.MouseButton2 then
rmbDownBindable:Fire()
end
end)
UserInputService.InputEnded:Connect(function(input, gpe)
if input.UserInputType == Enum.UserInputType.MouseButton2 then
rmbUpBindable:Fire()
end
end)
end
local CameraInput = {}
function CameraInput.getHoldPan()
return holdPan
end
function CameraInput.getTogglePan()
return togglePan
end
function CameraInput.getPanning()
return togglePan or holdPan
end
function CameraInput.setTogglePan(value)
togglePan = value
end
function CameraInput.enableCameraToggleInput()
if cameraToggleInputEnabled then
return
end
cameraToggleInputEnabled = true
holdPan = false
togglePan = false
if rmbDownConnection then
rmbDownConnection:Disconnect()
end
if rmbUpConnection then
rmbUpConnection:Disconnect()
end
rmbDownConnection = rmbDown:Connect(function()
holdPan = true
lastRmbDown = tick()
end)
rmbUpConnection = rmbUp:Connect(function()
holdPan = false
if tick() - lastRmbDown < MB_TAP_LENGTH and (togglePan or
UserInputService:GetMouseDelta().Magnitude < 2) then
togglePan = not togglePan
end
end)
end
function CameraInput.disableCameraToggleInput()
if not cameraToggleInputEnabled then
return
end
cameraToggleInputEnabled = false
if rmbDownConnection then
rmbDownConnection:Disconnect()
rmbDownConnection = nil
end
if rmbUpConnection then
rmbUpConnection:Disconnect()
rmbUpConnection = nil
end
end
return CameraInput
end
function _BaseCamera()
--[[
BaseCamera - Abstract base class for camera control modules
2018 Camera Update - AllYourBlox
--]]
local GAMEPAD_ZOOM_STEP_1 = 0
local GAMEPAD_ZOOM_STEP_2 = 10
local GAMEPAD_ZOOM_STEP_3 = 20
local PAN_SENSITIVITY = 20
local ZOOM_SENSITIVITY_CURVATURE = 0.5
local FFlagUserCameraToggle do
local success, result = pcall(function()
return UserSettings():IsUserFeatureEnabled("UserCameraToggle")
end)
FFlagUserCameraToggle = success and result
end
local FFlagUserDontAdjustSensitvityForPortrait do
local success, result = pcall(function()
return
UserSettings():IsUserFeatureEnabled("UserDontAdjustSensitvityForPortrait")
end)
FFlagUserDontAdjustSensitvityForPortrait = success and result
end
local FFlagUserFixZoomInZoomOutDiscrepancy do
local success, result = pcall(function()
return
UserSettings():IsUserFeatureEnabled("UserFixZoomInZoomOutDiscrepancy")
end)
FFlagUserFixZoomInZoomOutDiscrepancy = success and result
end
function BaseCamera.new()
local self = setmetatable({}, BaseCamera)
self.cameraType = nil
self.cameraMovementMode = nil
self.lastCameraTransform = nil
self.rotateInput = ZERO_VECTOR2
self.userPanningCamera = false
self.lastUserPanCamera = tick()
self.humanoidRootPart = nil
self.humanoidCache = {}
self.inFirstPerson = false
self.inMouseLockedMode = false
self.portraitMode = false
self.isSmallTouchScreen = false
self.enabled = false
self.startPos = nil
self.lastPos = nil
self.panBeginLook = nil
self.panEnabled = true
self.keyPanEnabled = true
self.distanceChangeEnabled = true
self.PlayerGui = nil
self.cameraChangedConn = nil
self.viewportSizeChangedConn = nil
self.boundContextActions = {}
-- VR Support
self.shouldUseVRRotation = false
self.VRRotationIntensityAvailable = false
self.lastVRRotationIntensityCheckTime = 0
self.lastVRRotationTime = 0
self.vrRotateKeyCooldown = {}
self.cameraTranslationConstraints = Vector3.new(1, 1, 1)
self.humanoidJumpOrigin = nil
self.trackingHumanoid = nil
self.cameraFrozen = false
self.subjectStateChangedConn = nil
-- Gamepad support
self.activeGamepad = nil
self.gamepadPanningCamera = false
self.lastThumbstickRotate = nil
self.numOfSeconds = 0.7
self.currentSpeed = 0
self.maxSpeed = 6
self.vrMaxSpeed = 4
self.lastThumbstickPos = Vector2.new(0,0)
self.ySensitivity = 0.65
self.lastVelocity = nil
self.gamepadConnectedConn = nil
self.gamepadDisconnectedConn = nil
self.currentZoomSpeed = 1.0
self.L3ButtonDown = false
self.dpadLeftDown = false
self.dpadRightDown = false
if player.Character then
self:OnCharacterAdded(player.Character)
end
player.CharacterAdded:Connect(function(char)
self:OnCharacterAdded(char)
end)
if self.playerCameraModeChangeConn then
self.playerCameraModeChangeConn:Disconnect() end
self.playerCameraModeChangeConn =
player:GetPropertyChangedSignal("CameraMode"):Connect(function()
self:OnPlayerCameraPropertyChange()
end)
if self.minDistanceChangeConn then
self.minDistanceChangeConn:Disconnect() end
self.minDistanceChangeConn =
player:GetPropertyChangedSignal("CameraMinZoomDistance"):Connect(function()
self:OnPlayerCameraPropertyChange()
end)
if self.maxDistanceChangeConn then
self.maxDistanceChangeConn:Disconnect() end
self.maxDistanceChangeConn =
player:GetPropertyChangedSignal("CameraMaxZoomDistance"):Connect(function()
self:OnPlayerCameraPropertyChange()
end)
if self.playerDevTouchMoveModeChangeConn then
self.playerDevTouchMoveModeChangeConn:Disconnect() end
self.playerDevTouchMoveModeChangeConn =
player:GetPropertyChangedSignal("DevTouchMovementMode"):Connect(function()
self:OnDevTouchMovementModeChanged()
end)
self:OnDevTouchMovementModeChanged() -- Init
if self.gameSettingsTouchMoveMoveChangeConn then
self.gameSettingsTouchMoveMoveChangeConn:Disconnect() end
self.gameSettingsTouchMoveMoveChangeConn =
UserGameSettings:GetPropertyChangedSignal("TouchMovementMode"):Connect(function()
self:OnGameSettingsTouchMovementModeChanged()
end)
self:OnGameSettingsTouchMovementModeChanged() -- Init
UserGameSettings:SetCameraYInvertVisible()
UserGameSettings:SetGamepadCameraSensitivityVisible()
self.hasGameLoaded = game:IsLoaded()
if not self.hasGameLoaded then
self.gameLoadedConn = game.Loaded:Connect(function()
self.hasGameLoaded = true
self.gameLoadedConn:Disconnect()
self.gameLoadedConn = nil
end)
end
self:OnPlayerCameraPropertyChange()
return self
end
function BaseCamera:GetModuleName()
return "BaseCamera"
end
function BaseCamera:OnCharacterAdded(char)
self.resetCameraAngle = self.resetCameraAngle or self:GetEnabled()
self.humanoidRootPart = nil
if UserInputService.TouchEnabled then
self.PlayerGui = player:WaitForChild("PlayerGui")
for _, child in ipairs(char:GetChildren()) do
if child:IsA("Tool") then
self.isAToolEquipped = true
end
end
char.ChildAdded:Connect(function(child)
if child:IsA("Tool") then
self.isAToolEquipped = true
end
end)
char.ChildRemoved:Connect(function(child)
if child:IsA("Tool") then
self.isAToolEquipped = false
end
end)
end
end
function BaseCamera:GetHumanoidRootPart()
if not self.humanoidRootPart then
if player.Character then
local humanoid =
player.Character:FindFirstChildOfClass("Humanoid")
if humanoid then
self.humanoidRootPart = humanoid.RootPart
end
end
end
return self.humanoidRootPart
end
return humanoid.RootPart
end
function BaseCamera:GetSubjectPosition()
local result = self.lastSubjectPosition
local camera = game.Workspace.CurrentCamera
local cameraSubject = camera and camera.CameraSubject
if cameraSubject then
if cameraSubject:IsA("Humanoid") then
local humanoid = cameraSubject
local humanoidIsDead = humanoid:GetState() ==
Enum.HumanoidStateType.Dead
if bodyPartToFollow and
bodyPartToFollow:IsA("BasePart") then
local heightOffset
if humanoid.RigType == Enum.HumanoidRigType.R15
then
if humanoid.AutomaticScalingEnabled then
heightOffset = R15_HEAD_OFFSET
if bodyPartToFollow ==
humanoid.RootPart then
local rootPartSizeOffset =
(humanoid.RootPart.Size.Y/2) - (HUMANOID_ROOT_PART_SIZE.Y/2)
heightOffset = heightOffset +
Vector3.new(0, rootPartSizeOffset, 0)
end
else
heightOffset =
R15_HEAD_OFFSET_NO_SCALING
end
else
heightOffset = HEAD_OFFSET
end
if humanoidIsDead then
heightOffset = ZERO_VECTOR3
end
result = bodyPartToFollow.CFrame.p +
bodyPartToFollow.CFrame:vectorToWorldSpace(heightOffset + humanoid.CameraOffset)
end
end
self.lastSubject = cameraSubject
self.lastSubjectPosition = result
return result
end
function BaseCamera:UpdateDefaultSubjectDistance()
if self.portraitMode then
self.defaultSubjectDistance =
math.clamp(PORTRAIT_DEFAULT_DISTANCE, player.CameraMinZoomDistance,
player.CameraMaxZoomDistance)
else
self.defaultSubjectDistance = math.clamp(DEFAULT_DISTANCE,
player.CameraMinZoomDistance, player.CameraMaxZoomDistance)
end
end
function BaseCamera:OnViewportSizeChanged()
local camera = game.Workspace.CurrentCamera
local size = camera.ViewportSize
self.portraitMode = size.X < size.Y
self.isSmallTouchScreen = UserInputService.TouchEnabled and (size.Y <
500 or size.X < 700)
self:UpdateDefaultSubjectDistance()
end
if newCamera then
self:OnViewportSizeChanged()
self.viewportSizeChangedConn =
newCamera:GetPropertyChangedSignal("ViewportSize"):Connect(function()
self:OnViewportSizeChanged()
end)
end
end
-- VR support additions
if self.cameraSubjectChangedConn then
self.cameraSubjectChangedConn:Disconnect()
self.cameraSubjectChangedConn = nil
end
function BaseCamera:OnDynamicThumbstickEnabled()
if UserInputService.TouchEnabled then
self.isDynamicThumbstickEnabled = true
end
end
function BaseCamera:OnDynamicThumbstickDisabled()
self.isDynamicThumbstickEnabled = false
end
function BaseCamera:OnGameSettingsTouchMovementModeChanged()
if player.DevTouchMovementMode == Enum.DevTouchMovementMode.UserChoice
then
if (UserGameSettings.TouchMovementMode ==
Enum.TouchMovementMode.DynamicThumbstick
or UserGameSettings.TouchMovementMode ==
Enum.TouchMovementMode.Default) then
self:OnDynamicThumbstickEnabled()
else
self:OnDynamicThumbstickDisabled()
end
end
end
function BaseCamera:OnDevTouchMovementModeChanged()
if player.DevTouchMovementMode.Name == "DynamicThumbstick" then
self:OnDynamicThumbstickEnabled()
else
self:OnGameSettingsTouchMovementModeChanged()
end
end
function BaseCamera:OnPlayerCameraPropertyChange()
-- This call forces re-evaluation of player.CameraMode and clamping to
min/max distance which may have changed
self:SetCameraToSubjectDistance(self.currentSubjectDistance)
end
function BaseCamera:GetCameraHeight()
if VRService.VREnabled and not self.inFirstPerson then
return math.sin(VR_ANGLE) * self.currentSubjectDistance
end
return 0
end
function BaseCamera:InputTranslationToCameraAngleChange(translationVector,
sensitivity)
if not FFlagUserDontAdjustSensitvityForPortrait then
local camera = game.Workspace.CurrentCamera
if camera and camera.ViewportSize.X > 0 and camera.ViewportSize.Y
> 0 and (camera.ViewportSize.Y > camera.ViewportSize.X) then
-- Screen has portrait orientation, swap X and Y
sensitivity
return translationVector * Vector2.new( sensitivity.Y,
sensitivity.X)
end
end
return translationVector * sensitivity
end
function BaseCamera:Enable(enable)
if self.enabled ~= enable then
self.enabled = enable
if self.enabled then
self:ConnectInputEvents()
self:BindContextActions()
if player.CameraMode == Enum.CameraMode.LockFirstPerson
then
self.currentSubjectDistance = 0.5
if not self.inFirstPerson then
self:EnterFirstPerson()
end
end
else
self:DisconnectInputEvents()
self:UnbindContextActions()
-- Clean up additional event listeners and reset a bunch of
properties
self:Cleanup()
end
end
end
function BaseCamera:GetEnabled()
return self.enabled
end
self:SetCameraToSubjectDistance(newZoom)
end
end
function BaseCamera:ConnectInputEvents()
self.pointerActionConn =
UserInputService.PointerAction:Connect(function(wheel, pan, pinch, processed)
self:OnPointerAction(wheel, pan, pinch, processed)
end)
self.inputBeganConn =
UserInputService.InputBegan:Connect(function(input, processed)
self:OnInputBegan(input, processed)
end)
self.inputChangedConn =
UserInputService.InputChanged:Connect(function(input, processed)
self:OnInputChanged(input, processed)
end)
self.inputEndedConn =
UserInputService.InputEnded:Connect(function(input, processed)
self:OnInputEnded(input, processed)
end)
self.menuOpenedConn = GuiService.MenuOpened:connect(function()
self:ResetInputStates()
end)
self.gamepadConnectedConn =
UserInputService.GamepadDisconnected:connect(function(gamepadEnum)
if self.activeGamepad ~= gamepadEnum then return end
self.activeGamepad = nil
self:AssignActivateGamepad()
end)
self.gamepadDisconnectedConn =
UserInputService.GamepadConnected:connect(function(gamepadEnum)
if self.activeGamepad == nil then
self:AssignActivateGamepad()
end
end)
self:AssignActivateGamepad()
if not FFlagUserCameraToggle then
self:UpdateMouseBehavior()
end
end
function BaseCamera:BindContextActions()
self:BindGamepadInputActions()
self:BindKeyboardInputActions()
end
function BaseCamera:AssignActivateGamepad()
local connectedGamepads = UserInputService:GetConnectedGamepads()
if #connectedGamepads > 0 then
for i = 1, #connectedGamepads do
if self.activeGamepad == nil then
self.activeGamepad = connectedGamepads[i]
elseif connectedGamepads[i].Value <
self.activeGamepad.Value then
self.activeGamepad = connectedGamepads[i]
end
end
end
function BaseCamera:DisconnectInputEvents()
if self.inputBeganConn then
self.inputBeganConn:Disconnect()
self.inputBeganConn = nil
end
if self.inputChangedConn then
self.inputChangedConn:Disconnect()
self.inputChangedConn = nil
end
if self.inputEndedConn then
self.inputEndedConn:Disconnect()
self.inputEndedConn = nil
end
end
function BaseCamera:UnbindContextActions()
for i = 1, #self.boundContextActions do
ContextActionService:UnbindAction(self.boundContextActions[i])
end
self.boundContextActions = {}
end
function BaseCamera:Cleanup()
if self.pointerActionConn then
self.pointerActionConn:Disconnect()
self.pointerActionConn = nil
end
if self.menuOpenedConn then
self.menuOpenedConn:Disconnect()
self.menuOpenedConn = nil
end
if self.mouseLockToggleConn then
self.mouseLockToggleConn:Disconnect()
self.mouseLockToggleConn = nil
end
if self.gamepadConnectedConn then
self.gamepadConnectedConn:Disconnect()
self.gamepadConnectedConn = nil
end
if self.gamepadDisconnectedConn then
self.gamepadDisconnectedConn:Disconnect()
self.gamepadDisconnectedConn = nil
end
if self.subjectStateChangedConn then
self.subjectStateChangedConn:Disconnect()
self.subjectStateChangedConn = nil
end
if self.viewportSizeChangedConn then
self.viewportSizeChangedConn:Disconnect()
self.viewportSizeChangedConn = nil
end
if self.touchActivateConn then
self.touchActivateConn:Disconnect()
self.touchActivateConn = nil
end
self.turningLeft = false
self.turningRight = false
self.lastCameraTransform = nil
self.lastSubjectCFrame = nil
self.userPanningTheCamera = false
self.rotateInput = Vector2.new()
self.gamepadPanningCamera = Vector2.new(0,0)
self.fingerTouches = {}
self.dynamicTouchInput = nil
self.numUnsunkTouches = 0
self.startingDiff = nil
self.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
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 in pairs(self.fingerTouches) do
self.fingerTouches[inputObject] = nil
end
self.dynamicTouchInput = nil
self.panBeginLook = nil
self.startPos = nil
self.lastPos = nil
self.userPanningTheCamera = false
self.startingDiff = nil
self.pinchBeginZoom = nil
self.numUnsunkTouches = 0
end
end
function BaseCamera:DoPanRotateCamera(rotateAngle)
local angle =
Util.RotateVectorByAngleAndRound(self:GetCameraLookVector() * Vector3.new(1,0,1),
rotateAngle, math.pi*0.25)
if angle ~= 0 then
self.rotateInput = self.rotateInput + Vector2.new(angle, 0)
self.lastUserPanCamera = tick()
self.lastCameraTransform = nil
end
end
self:SetCameraToSubjectDistance(GAMEPAD_ZOOM_STEP_2)
elseif dist > (GAMEPAD_ZOOM_STEP_1 +
GAMEPAD_ZOOM_STEP_2)/2 then
self:SetCameraToSubjectDistance(GAMEPAD_ZOOM_STEP_1)
else
self:SetCameraToSubjectDistance(GAMEPAD_ZOOM_STEP_3)
end
end
end
elseif input.KeyCode == Enum.KeyCode.DPadLeft then
self.dpadLeftDown = (state == Enum.UserInputState.Begin)
elseif input.KeyCode == Enum.KeyCode.DPadRight then
self.dpadRightDown = (state == Enum.UserInputState.Begin)
end
if self.dpadLeftDown then
self.currentZoomSpeed = 1.04
elseif self.dpadRightDown then
self.currentZoomSpeed = 0.96
else
self.currentZoomSpeed = 1.00
end
return Enum.ContextActionResult.Sink
end
return Enum.ContextActionResult.Pass
-- elseif input.UserInputType == self.activeGamepad and input.KeyCode ==
Enum.KeyCode.ButtonL3 then
-- if (state == Enum.UserInputState.Begin) then
-- self.L3ButtonDown = true
-- elseif (state == Enum.UserInputState.End) then
-- self.L3ButtonDown = false
-- self.currentZoomSpeed = 1.00
-- end
-- end
end
self:SetCameraToSubjectDistance( self.currentSubjectDistance - 5 )
elseif input.KeyCode == Enum.KeyCode.O then
self:SetCameraToSubjectDistance( self.currentSubjectDistance + 5 )
end
return Enum.ContextActionResult.Sink
end
return Enum.ContextActionResult.Pass
end
function BaseCamera:BindGamepadInputActions()
self:BindAction("BaseCameraGamepadPan", function(name, state, input)
return self:GetGamepadPan(name, state, input) end,
false, Enum.KeyCode.Thumbstick2)
self:BindAction("BaseCameraGamepadZoom", function(name, state, input)
return self:DoGamepadZoom(name, state, input) end,
false, Enum.KeyCode.DPadLeft, Enum.KeyCode.DPadRight,
Enum.KeyCode.ButtonR3)
end
function BaseCamera:BindKeyboardInputActions()
self:BindAction("BaseCameraKeyboardPanArrowKeys", function(name, state,
input) return self:DoKeyboardPanTurn(name, state, input) end,
false, Enum.KeyCode.Left, Enum.KeyCode.Right)
self:BindAction("BaseCameraKeyboardZoom", function(name, state, input)
return self:DoKeyboardZoom(name, state, input) end,
false, Enum.KeyCode.I, Enum.KeyCode.O)
end
return false
end
---Adjusts the camera Y touch Sensitivity when moving away from the center
and in the TOUCH_SENSITIVTY_ADJUST_AREA
function BaseCamera:AdjustTouchSensitivity(delta, sensitivity)
local cameraCFrame = game.Workspace.CurrentCamera and
game.Workspace.CurrentCamera.CFrame
if not cameraCFrame then
return sensitivity
end
local currPitchAngle = cameraCFrame:ToEulerAnglesYXZ()
return Vector2.new(
sensitivity.X,
sensitivity.Y * multiplierY
)
end
if self.numUnsunkTouches == 1 then
if self.fingerTouches[input] == false then
self.panBeginLook = self.panBeginLook or
self:GetCameraLookVector()
self.startPos = self.startPos or input.Position
self.lastPos = self.lastPos or self.startPos
self.userPanningTheCamera = true
local desiredXYVector =
self:InputTranslationToCameraAngleChange(delta, adjustedTouchSensitivity)
self.rotateInput = self.rotateInput + desiredXYVector
end
self.lastPos = input.Position
end
else
self.panBeginLook = nil
self.startPos = nil
self.lastPos = nil
self.userPanningTheCamera = false
end
if self.numUnsunkTouches == 2 then
local unsunkTouches = {}
for touch, wasSunk in pairs(self.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 self.startingDiff and self.pinchBeginZoom then
local scale = difference / math.max(0.01,
self.startingDiff)
local clampedScale = math.clamp(scale, 0.1, 10)
if self.distanceChangeEnabled then
self:SetCameraToSubjectDistance(self.pinchBeginZoom / clampedScale)
end
else
self.startingDiff = difference
self.pinchBeginZoom =
self:GetCameraToSubjectDistance()
end
end
else
self.startingDiff = nil
self.pinchBeginZoom = nil
end
end
self.isRightMouseDown = true
self:OnMousePanButtonPressed(input, processed)
end
self.isMiddleMouseDown = true
self:OnMousePanButtonPressed(input, processed)
end
function BaseCamera:UpdateMouseBehavior()
if FFlagUserCameraToggle and self.isCameraToggle then
CameraUI.setCameraModeToastEnabled(true)
CameraInput.enableCameraToggleInput()
CameraToggleStateController(self.inFirstPerson)
else
if FFlagUserCameraToggle then
CameraUI.setCameraModeToastEnabled(false)
CameraInput.disableCameraToggleInput()
end
-- first time transition to first person mode or mouse-locked
third person
if self.inFirstPerson or self.inMouseLockedMode then
--UserGameSettings.RotationType =
Enum.RotationType.CameraRelative
UserInputService.MouseBehavior =
Enum.MouseBehavior.LockCenter
else
UserGameSettings.RotationType =
Enum.RotationType.MovementRelative
if self.isRightMouseDown or self.isMiddleMouseDown then
UserInputService.MouseBehavior =
Enum.MouseBehavior.LockCurrentPosition
else
UserInputService.MouseBehavior =
Enum.MouseBehavior.Default
end
end
end
end
function BaseCamera:UpdateForDistancePropertyChange()
-- Calling this setter with the current value will force checking that
it is still
-- in range after a change to the min/max distance limits
self:SetCameraToSubjectDistance(self.currentSubjectDistance)
end
function BaseCamera:SetCameraToSubjectDistance(desiredSubjectDistance)
local lastSubjectDistance = self.currentSubjectDistance
function BaseCamera:GetCameraType()
return self.cameraType
end
function BaseCamera:GetCameraMovementMode()
return self.cameraMovementMode
end
function BaseCamera:SetIsMouseLocked(mouseLocked)
self.inMouseLockedMode = mouseLocked
if not FFlagUserCameraToggle then
self:UpdateMouseBehavior()
end
end
function BaseCamera:GetIsMouseLocked()
return self.inMouseLockedMode
end
function BaseCamera:SetMouseLockOffset(offsetVector)
self.mouseLockOffset = offsetVector
end
function BaseCamera:GetMouseLockOffset()
return self.mouseLockOffset
end
function BaseCamera:InFirstPerson()
return self.inFirstPerson
end
function BaseCamera:EnterFirstPerson()
-- Overridden in ClassicCamera, the only module which supports
FirstPerson
end
function BaseCamera:LeaveFirstPerson()
-- Overridden in ClassicCamera, the only module which supports
FirstPerson
end
-- Nominal distance, set by dollying in and out with the mouse wheel or
equivalent, not measured distance
function BaseCamera:GetCameraToSubjectDistance()
return self.currentSubjectDistance
end
-- Actual measured distance to the camera Focus point, which may be needed in
special circumstances, but should
-- never be used as the starting point for updating the nominal camera-to-
subject distance (self.currentSubjectDistance)
-- since that is a desired target value set only by mouse wheel (or
equivalent) input, PopperCam, and clamped to min max camera distance
function BaseCamera:GetMeasuredDistanceToFocus()
local camera = game.Workspace.CurrentCamera
if camera then
return (camera.CoordinateFrame.p - camera.Focus.p).magnitude
end
return nil
end
function BaseCamera:GetCameraLookVector()
return game.Workspace.CurrentCamera and
game.Workspace.CurrentCamera.CFrame.lookVector or UNIT_Z
end
function BaseCamera:CalculateNewLookVectorVR()
local subjectPosition = self:GetSubjectPosition()
local vecToSubject = (subjectPosition -
game.Workspace.CurrentCamera.CFrame.p)
local currLookVector = (vecToSubject * X1_Y0_Z1).unit
local vrRotateInput = Vector2.new(self.rotateInput.x, 0)
local startCFrame = CFrame.new(ZERO_VECTOR3, currLookVector)
local yawRotatedVector = (CFrame.Angles(0, -vrRotateInput.x, 0) *
startCFrame * CFrame.Angles(-vrRotateInput.y,0,0)).lookVector
return (yawRotatedVector * X1_Y0_Z1).unit
end
function BaseCamera:GetHumanoid()
local character = player and player.Character
if character then
local resultHumanoid = self.humanoidCache[player]
if resultHumanoid and resultHumanoid.Parent == character then
return resultHumanoid
else
self.humanoidCache[player] = nil -- Bust Old Cache
local humanoid =
character:FindFirstChildOfClass("Humanoid")
if humanoid then
self.humanoidCache[player] = humanoid
end
return humanoid
end
end
return nil
end
function BaseCamera:UpdateGamepad()
local gamepadPan = self.gamepadPanningCamera
if gamepadPan and (self.hasGameLoaded or not VRService.VREnabled) then
gamepadPan = Util.GamepadLinearToCurve(gamepadPan)
local currentTime = tick()
if gamepadPan.X ~= 0 or gamepadPan.Y ~= 0 then
self.userPanningTheCamera = true
elseif gamepadPan == ZERO_VECTOR2 then
self.lastThumbstickRotate = nil
if self.lastThumbstickPos == ZERO_VECTOR2 then
self.currentSpeed = 0
end
end
local finalConstant = 0
if self.lastThumbstickRotate then
if VRService.VREnabled then
self.currentSpeed = self.vrMaxSpeed
else
local elapsedTime = (currentTime -
self.lastThumbstickRotate) * 10
self.currentSpeed = self.currentSpeed +
(self.maxSpeed * ((elapsedTime*elapsedTime)/self.numOfSeconds))
if self.lastVelocity then
local velocity = (gamepadPan -
self.lastThumbstickPos)/(currentTime - self.lastThumbstickRotate)
local velocityDeltaMag = (velocity -
self.lastVelocity).magnitude
finalConstant = UserGameSettings.GamepadCameraSensitivity *
self.currentSpeed
self.lastVelocity = (gamepadPan -
self.lastThumbstickPos)/(currentTime - self.lastThumbstickRotate)
end
self.lastThumbstickPos = gamepadPan
self.lastThumbstickRotate = currentTime
-- [[ VR Support Section ]] --
function BaseCamera:ApplyVRTransform()
if not VRService.VREnabled then
return
end
function BaseCamera:IsInFirstPerson()
return self.inFirstPerson
end
function BaseCamera:ShouldUseVRRotation()
if not VRService.VREnabled then
return false
end
return self.shouldUseVRRotation
end
function BaseCamera:GetVRRotationInput()
local vrRotateSum = ZERO_VECTOR2
local success, vrRotationIntensity = pcall(function() return
StarterGui:GetCore("VRRotationIntensity") end)
return vrRotateSum
end
function BaseCamera:CancelCameraFreeze(keepConstraints)
if not keepConstraints then
self.cameraTranslationConstraints =
Vector3.new(self.cameraTranslationConstraints.x, 1,
self.cameraTranslationConstraints.z)
end
if self.cameraFrozen then
self.trackingHumanoid = nil
self.cameraFrozen = false
end
end
function BaseCamera:OnNewCameraSubject()
if self.subjectStateChangedConn then
self.subjectStateChangedConn:Disconnect()
self.subjectStateChangedConn = nil
end
local newFocus
if self.cameraFrozen and self.humanoidJumpOrigin and
self.humanoidJumpOrigin.y > lastFocus.y then
newFocus = CFrame.new(Vector3.new(subjectPosition.x,
math.min(self.humanoidJumpOrigin.y, lastFocus.y + 5 * timeDelta),
subjectPosition.z))
else
newFocus = CFrame.new(Vector3.new(subjectPosition.x, lastFocus.y,
subjectPosition.z):lerp(subjectPosition, self.cameraTranslationConstraints.y))
end
if self.cameraFrozen then
-- No longer in 3rd person
if self.inFirstPerson 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 BaseCamera: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 BaseCamera: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 BaseCamera:Update(dt)
error("BaseCamera:Update() This is a virtual function that should never
be getting called.", 2)
end
BaseCamera.UpCFrame = CFrame.new()
function BaseCamera:UpdateUpCFrame(cf)
self.UpCFrame = cf
end
local ZERO = Vector3.new(0, 0, 0)
function BaseCamera:CalculateNewLookCFrame(suppliedLookVector)
local currLookVector = suppliedLookVector or self:GetCameraLookVector()
currLookVector = self.UpCFrame:VectorToObjectSpace(currLookVector)
return newLookCFrame
end
return BaseCamera
end
function _BaseOcclusion()
--[[ The Module ]]--
local BaseOcclusion = {}
BaseOcclusion.__index = BaseOcclusion
setmetatable(BaseOcclusion, {
__call = function(_, ...)
return BaseOcclusion.new(...)
end
})
function BaseOcclusion.new()
local self = setmetatable({}, BaseOcclusion)
return self
end
function BaseOcclusion:OnCameraSubjectChanged(newSubject)
end
--[[ Derived classes are required to override and implement all of the
following functions ]]--
function BaseOcclusion:GetOcclusionMode()
-- Must be overridden in derived classes to return an
Enum.DevCameraOcclusionMode value
warn("BaseOcclusion GetOcclusionMode must be overridden by derived
classes")
return nil
end
function BaseOcclusion:Enable(enabled)
warn("BaseOcclusion Enable must be overridden by derived classes")
end
return BaseOcclusion
end
function _Popper()
projY = 2*tan(fov/2)
projX = ar*projY
end
camera:GetPropertyChangedSignal("FieldOfView"):Connect(updateProjection)
camera:GetPropertyChangedSignal("ViewportSize"):Connect(updateProjection)
updateProjection()
nearPlaneZ = camera.NearPlaneZ
camera:GetPropertyChangedSignal("NearPlaneZ"):Connect(function()
nearPlaneZ = camera.NearPlaneZ
end)
end
local blacklist = {} do
local charMap = {}
player.CharacterAdded:Connect(characterAdded)
player.CharacterRemoving:Connect(characterRemoving)
if player.Character then
characterAdded(player.Character)
end
end
Players.PlayerAdded:Connect(playerAdded)
Players.PlayerRemoving:Connect(playerRemoving)
-----------------------------------------------------------------------------
---------------
-- Popper uses the level geometry find an upper bound on subject-to-camera
distance.
--
-- Hard limits are applied immediately and unconditionally. They are
generally caused
-- when level geometry intersects with the near plane (with exceptions, see
below).
--
-- Soft limits are only applied under certain conditions.
-- They are caused when level geometry occludes the subject without actually
intersecting
-- with the near plane at the target distance.
--
-- Soft limits can be promoted to hard limits and hard limits can be demoted
to soft limits.
-- We usually don"t want the latter to happen.
--
-- A soft limit will be promoted to a hard limit if an obstruction
-- lies between the current and target camera positions.
-----------------------------------------------------------------------------
---------------
local subjectRoot
local subjectPart
camera:GetPropertyChangedSignal("CameraSubject"):Connect(function()
local subject = camera.CameraSubject
if subject:IsA("Humanoid") then
subjectPart = subject.RootPart
elseif subject:IsA("BasePart") then
subjectPart = subject
else
subjectPart = nil
end
end)
return
getTotalTransparency(part) < 0.25 and
part.CanCollide and
subjectRoot ~= (part:GetRootPart() or part) and
not part:IsA("TrussPart")
end
-----------------------------------------------------------------------------
---
-- Piercing raycasts
if hitPart then
if hitPart.CanCollide then
eraseFromEnd(blacklist, originalSize)
return hitPoint, true
end
blacklist[#blacklist + 1] = hitPart
end
until not hitPart
eraseFromEnd(blacklist, originalSize)
return origin + dir, false
end
-----------------------------------------------------------------------------
---
repeat
local entryPart, entryPos =
workspace:FindPartOnRayWithIgnoreList(ray(movingOrigin, target - movingOrigin),
blacklist, false, true)
if entryPart then
if canOcclude(entryPart) then
local wl = {entryPart}
local exitPart =
workspace:FindPartOnRayWithWhitelist(ray(target, entryPos - target), wl, true)
if exitPart then
local promote = false
if lastPos then
promote =
if promote then
-- Ostensibly a soft limit, but the
camera has passed through it in the last frame, so promote to a hard limit.
hardLimit = lim
elseif dist < softLimit then
-- Trivial soft limit
softLimit = lim
end
else
-- Trivial hard limit
hardLimit = lim
end
end
blacklist[#blacklist + 1] = entryPart
movingOrigin = entryPos - unitDir*1e-3
end
until hardLimit < inf or not entryPart
eraseFromEnd(blacklist, originalSize)
debug.profileend()
return softLimit - nearPlaneZ, hardLimit - nearPlaneZ
end
local fP = focus.p
local fX = focus.rightVector
local fY = focus.upVector
local fZ = -focus.lookVector
-- Center the viewport on the PoI, sweep points on the edge towards the
target, and take the minimum limits
for viewX = 0, 1 do
local worldX = fX*((viewX - 0.5)*projX)
for viewY = 0, 1 do
local worldY = fY*((viewY - 0.5)*projY)
local fP = focus.p
local fX = focus.rightVector
local fY = focus.upVector
local fZ = -focus.lookVector
do
-- Dead reckoning the camera rotation and focus
debug.profilebegin("extrapolate")
for dt = 0, min(SAMPLE_MAX_T,
focusExtrapolation.rotVelocity.magnitude + maxDist/combinedSpeed), SAMPLE_DT do
local cfDt = focusExtrapolation.extrapolate(dt) --
Extrapolated CFrame at time dt
debug.profileend()
end
do
-- Test screen-space offsets from the focus for the presence of
soft limits
debug.profilebegin("testOffsets")
debug.profileend()
end
debug.profileend()
return true
end
subjectRoot = nil
debug.profileend()
return dist
end
return Popper
end
function _ZoomController()
local ZOOM_STIFFNESS = 4.5
local ZOOM_DEFAULT = 12.5
local ZOOM_ACCELERATION = 0.0375
updateBounds()
Player:GetPropertyChangedSignal("CameraMinZoomDistance"):Connect(updateBounds)
Player:GetPropertyChangedSignal("CameraMaxZoomDistance"):Connect(updateBounds)
end
local ConstrainedSpring = {} do
ConstrainedSpring.__index = ConstrainedSpring
-- Solve the spring ODE for position and velocity after time t,
assuming critical damping:
-- 2*f*x'[t] + x''[t] = f^2*(g - x[t])
-- Knowns are x[0] and x'[0].
-- Solve for x[t] and x'[t].
-- Constrain
if x1 < minValue then
x1 = minValue
v1 = 0
elseif x1 > maxValue then
x1 = maxValue
v1 = 0
end
self.x = x1
self.v = v1
return x1
end
end
local zoomDelta = 0
local Zoom = {} do
function Zoom.Update(renderDt, focus, extrapolation)
local poppedZoom = math.huge
zoomSpring.minValue = MIN_FOCUS_DIST
zoomSpring.maxValue = min(cameraMaxZoomDistance, poppedZoom)
return zoomSpring:Step(renderDt)
end
return Zoom
end
function _MouseLockController()
--[[ Constants ]]--
local DEFAULT_MOUSE_LOCK_CURSOR = "rbxasset://textures/MouseLockedCursor.png"
function MouseLockController.new()
local self = setmetatable({}, MouseLockController)
self.isMouseLocked = false
self.savedMouseCursor = nil
self.boundKeys = {Enum.KeyCode.LeftShift, Enum.KeyCode.RightShift} --
defaults
self.mouseLockToggledEvent = Instance.new("BindableEvent")
boundKeysObj = Instance.new("StringValue")
boundKeysObj.Name = "BoundKeys"
boundKeysObj.Value = "LeftShift,RightShift"
boundKeysObj.Parent = script
end
if boundKeysObj then
boundKeysObj.Changed:Connect(function(value)
self:OnBoundKeysObjectChanged(value)
end)
self:OnBoundKeysObjectChanged(boundKeysObj.Value) -- Initial
setup call
end
PlayersService.LocalPlayer:GetPropertyChangedSignal("DevEnableMouseLock"):Connect(f
unction()
self:UpdateMouseLockAvailability()
end)
PlayersService.LocalPlayer:GetPropertyChangedSignal("DevComputerMovementMode"):Conn
ect(function()
self:UpdateMouseLockAvailability()
end)
self:UpdateMouseLockAvailability()
return self
end
function MouseLockController:GetIsMouseLocked()
return self.isMouseLocked
end
function MouseLockController:GetBindableToggleEvent()
return self.mouseLockToggledEvent.Event
end
function MouseLockController:GetMouseLockOffset()
local offsetValueObj = script:FindFirstChild("CameraOffset")
if offsetValueObj and offsetValueObj:IsA("Vector3Value") then
return offsetValueObj.Value
else
-- If CameraOffset object was found but not correct type, destroy
if offsetValueObj then
offsetValueObj:Destroy()
end
offsetValueObj = Instance.new("Vector3Value")
offsetValueObj.Name = "CameraOffset"
offsetValueObj.Value = Vector3.new(1.75,0,0) -- Legacy Default
Value
offsetValueObj.Parent = script
end
return Vector3.new(1.75,0,0)
end
function MouseLockController:UpdateMouseLockAvailability()
local devAllowsMouseLock =
PlayersService.LocalPlayer.DevEnableMouseLock
local devMovementModeIsScriptable =
PlayersService.LocalPlayer.DevComputerMovementMode ==
Enum.DevComputerMovementMode.Scriptable
local userHasMouseLockModeEnabled = GameSettings.ControlMode ==
Enum.ControlMode.MouseLockSwitch
local userHasClickToMoveEnabled = GameSettings.ComputerMovementMode ==
Enum.ComputerMovementMode.ClickToMove
local MouseLockAvailable = devAllowsMouseLock and
userHasMouseLockModeEnabled and not userHasClickToMoveEnabled and not
devMovementModeIsScriptable
if MouseLockAvailable~=self.enabled then
self:EnableMouseLock(MouseLockAvailable)
end
end
function MouseLockController:OnBoundKeysObjectChanged(newValue)
self.boundKeys = {} -- Overriding defaults, note: possibly with nothing
at all if boundKeysObj.Value is "" or contains invalid values
for token in string.gmatch(newValue,"[^%s,]+") do
for _, keyEnum in pairs(Enum.KeyCode:GetEnumItems()) do
if token == keyEnum.Name then
self.boundKeys[#self.boundKeys+1] = keyEnum
break
end
end
end
self:UnbindContextActions()
self:BindContextActions()
end
if self.isMouseLocked then
local cursorImageValueObj = script:FindFirstChild("CursorImage")
if cursorImageValueObj and cursorImageValueObj:IsA("StringValue")
and cursorImageValueObj.Value then
self.savedMouseCursor = Mouse.Icon
Mouse.Icon = cursorImageValueObj.Value
else
if cursorImageValueObj then
cursorImageValueObj:Destroy()
end
cursorImageValueObj = Instance.new("StringValue")
cursorImageValueObj.Name = "CursorImage"
cursorImageValueObj.Value = DEFAULT_MOUSE_LOCK_CURSOR
cursorImageValueObj.Parent = script
self.savedMouseCursor = Mouse.Icon
Mouse.Icon = DEFAULT_MOUSE_LOCK_CURSOR
end
else
if self.savedMouseCursor then
Mouse.Icon = self.savedMouseCursor
self.savedMouseCursor = nil
end
end
self.mouseLockToggledEvent:Fire()
end
function MouseLockController:BindContextActions()
ContextActionService:BindActionAtPriority(CONTEXT_ACTION_NAME,
function(name, state, input)
return self:DoMouseLockSwitch(name, state, input)
end, false, MOUSELOCK_ACTION_PRIORITY, unpack(self.boundKeys))
end
function MouseLockController:UnbindContextActions()
ContextActionService:UnbindAction(CONTEXT_ACTION_NAME)
end
function MouseLockController:IsMouseLocked()
return self.enabled and self.isMouseLocked
end
function MouseLockController:EnableMouseLock(enable)
if enable ~= self.enabled then
self.enabled = enable
if self.enabled then
-- Enabling the mode
self:BindContextActions()
else
-- Disabling
-- Restore mouse cursor
if Mouse.Icon~="" then
Mouse.Icon = ""
end
self:UnbindContextActions()
self.isMouseLocked = false
end
end
end
return MouseLockController
end
function _TransparencyController()
function TransparencyController.new()
local self = setmetatable({}, TransparencyController)
self.lastUpdate = tick()
self.transparencyDirty = false
self.enabled = false
self.lastTransparency = nil
return self
end
function TransparencyController:HasToolAncestor(object)
if object.Parent == nil then return false end
return object.Parent:IsA('Tool') or self:HasToolAncestor(object.Parent)
end
function TransparencyController:IsValidPartToModify(part)
if part:IsA('BasePart') or part:IsA('Decal') then
return not self:HasToolAncestor(part)
end
return false
end
function TransparencyController:CachePartsRecursive(object)
if object then
if self:IsValidPartToModify(object) then
self.cachedParts[object] = true
self.transparencyDirty = true
end
for _, child in pairs(object:GetChildren()) do
self:CachePartsRecursive(child)
end
end
end
function TransparencyController:TeardownTransparency()
for child, _ in pairs(self.cachedParts) do
child.LocalTransparencyModifier = 0
end
self.cachedParts = {}
self.transparencyDirty = true
self.lastTransparency = nil
if self.descendantAddedConn then
self.descendantAddedConn:disconnect()
self.descendantAddedConn = nil
end
if self.descendantRemovingConn then
self.descendantRemovingConn:disconnect()
self.descendantRemovingConn = nil
end
for object, conn in pairs(self.toolDescendantAddedConns) do
conn:Disconnect()
self.toolDescendantAddedConns[object] = nil
end
for object, conn in pairs(self.toolDescendantRemovingConns) do
conn:Disconnect()
self.toolDescendantRemovingConns[object] = nil
end
end
function TransparencyController:SetupTransparency(character)
self:TeardownTransparency()
function TransparencyController:Enable(enable)
if self.enabled ~= enable then
self.enabled = enable
self:Update()
end
end
function TransparencyController: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
self:SetupTransparency(character)
else
self:TeardownTransparency()
end
end
function TransparencyController:Update()
local instant = false
local now = tick()
local currentCamera = workspace.CurrentCamera
if currentCamera then
local transparency = 0
if not self.enabled then
instant = true
else
local distance = (currentCamera.Focus.p -
currentCamera.CoordinateFrame.p).magnitude
transparency = (distance<2) and (1.0-(distance-0.5)/1.5) or
0 --(7 - distance) / 5
if transparency < 0.5 then
transparency = 0
end
if self.lastTransparency then
local deltaTransparency = transparency -
self.lastTransparency
if self.transparencyDirty or self.lastTransparency ~=
transparency then
for child, _ in pairs(self.cachedParts) do
child.LocalTransparencyModifier = transparency
end
self.transparencyDirty = false
self.lastTransparency = transparency
end
end
self.lastUpdate = now
end
return TransparencyController
end
function _Poppercam()
local ZoomController = _ZoomController()
local TransformExtrapolator = {} do
TransformExtrapolator.__index = TransformExtrapolator
function TransformExtrapolator.new()
return setmetatable({
lastCFrame = nil,
}, TransformExtrapolator)
end
-- Estimate velocities from the delta between now and the last
frame
-- This estimation can be a little noisy.
local dp = (currentPos - lastPos)/dt
local dr = cframeToAxis(currentRot*lastRot:inverse())/dt
return {
extrapolate = extrapolate,
posVelocity = dp,
rotVelocity = dr,
}
end
function TransformExtrapolator:Reset()
self.lastCFrame = nil
end
end
function Poppercam.new()
local self = setmetatable(BaseOcclusion.new(), Poppercam)
self.focusExtrapolator = TransformExtrapolator.new()
return self
end
function Poppercam:GetOcclusionMode()
return Enum.DevCameraOcclusionMode.Zoom
end
function Poppercam:Enable(enable)
self.focusExtrapolator:Reset()
end
function Poppercam:OnCameraSubjectChanged(newSubject)
end
return Poppercam
end
function _Invisicam()
local MODE = {
--CUSTOM = 1, -- Retired, unused
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, -- Dynamic outline around the character
}
local LIMB_TRACKING_SET = {
-- Body parts common to R15 and R6
['Head'] = 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
-- 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
function Invisicam.new()
local self = setmetatable(BaseOcclusion.new(), Invisicam)
self.char = nil
self.humanoidRootPart = nil
self.torsoPart = nil
self.headPart = nil
self.childAddedConn = nil
self.childRemovedConn = nil
self.mode = MODE.SMART_CIRCLE
self.behaviorFunction = self.SmartCircleBehavior
self.camera = game.Workspace.CurrentCamera
self.enabled = false
return self
end
function Invisicam:Enable(enable)
self.enabled = enable
function Invisicam:GetOcclusionMode()
return Enum.DevCameraOcclusionMode.Invisicam
end
function Invisicam:MoveBehavior(castPoints)
for i = 1, MOVE_CASTS do
local position, velocity = self.humanoidRootPart.Position,
self.humanoidRootPart.Velocity
local horizontalSpeed = Vector3.new(velocity.X, 0,
velocity.Z).Magnitude / 2
local offsetVector = (i - 1) *
self.humanoidRootPart.CFrame.lookVector * horizontalSpeed
castPoints[#castPoints + 1] = position + offsetVector
end
end
function Invisicam:CornerBehavior(castPoints)
local cframe = self.humanoidRootPart.CFrame
local centerPoint = cframe.p
local rotation = cframe - centerPoint
local halfSize = self.char: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
function Invisicam:CircleBehavior(castPoints)
local cframe
if self.mode == MODE.CIRCLE1 then
cframe = self.humanoidRootPart.CFrame
else
local camCFrame = self.camera.CoordinateFrame
cframe = camCFrame - camCFrame.p + self.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
function Invisicam:LimbMoveBehavior(castPoints)
self:LimbBehavior(castPoints)
self:MoveBehavior(castPoints)
end
function Invisicam:CharacterOutlineBehavior(castPoints)
local torsoUp = self.torsoPart.CFrame.upVector.unit
local torsoRight = self.torsoPart.CFrame.rightVector.unit
local cframe =
CFrame.new(ZERO_VECTOR3,Vector3.new(self.camera.CoordinateFrame.lookVector.X,0,self
.camera.CoordinateFrame.lookVector.Z))
local centerPoint = (self.torsoPart and self.torsoPart.Position or
self.humanoidRootPart.Position)
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))
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
function Invisicam:SmartCircleBehavior(castPoints)
local torsoUp = self.torsoPart.CFrame.upVector.unit
local torsoRight = self.torsoPart.CFrame.rightVector.unit
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
-- Vector from the offset hit point, along the hit surface
local v1 = (perp:Cross(hitNormal)).unit
if hit then
local hprime2 = hitPoint + 0.1 *
hitNormal.unit
castPoint = hprime2
end
else
castPoint = hprime
end
else
castPoint = hprime
end
if hit then
local castPoint2 = hitPoint - 0.1 * (castPoint -
torsoPoint).unit
castPoint = castPoint2
end
end
castPoints[#castPoints + 1] = castPoint
end
end
function Invisicam:CheckTorsoReference()
if self.char then
self.torsoPart = self.char:FindFirstChild("Torso")
if not self.torsoPart then
self.torsoPart = self.char:FindFirstChild("UpperTorso")
if not self.torsoPart then
self.torsoPart =
self.char:FindFirstChild("HumanoidRootPart")
end
end
self.headPart = self.char:FindFirstChild("Head")
end
end
if self.childAddedConn then
self.childAddedConn:Disconnect()
self.childAddedConn = nil
end
if self.childRemovedConn then
self.childRemovedConn:Disconnect()
self.childRemovedConn = nil
end
self.char = char
self.trackedLimbs = {}
local function childAdded(child)
if child:IsA("BasePart") then
if LIMB_TRACKING_SET[child.Name] then
self.trackedLimbs[child] = true
end
if child.Name == "Torso" or child.Name == "UpperTorso" then
self.torsoPart = child
end
self.childAddedConn = char.ChildAdded:Connect(childAdded)
self.childRemovedConn = char.ChildRemoved:Connect(childRemoved)
for _, child in pairs(self.char:GetChildren()) do
childAdded(child)
end
end
function Invisicam:SetMode(newMode)
AssertTypes(newMode, 'number')
for _, modeNum in pairs(MODE) do
if modeNum == newMode then
self.mode = newMode
self.behaviorFunction = self.behaviors[self.mode]
return
end
end
error("Invalid mode number")
end
function Invisicam:GetObscuredParts()
return self.savedHits
end
self.camera = game.Workspace.CurrentCamera
-- Cast to get a list of objects between the camera and the cast points
local currentHits = {}
local ignoreList = {self.char}
local function add(hit)
currentHits[hit] = true
if not self.savedHits[hit] then
self.savedHits[hit] = hit.LocalTransparencyModifier
end
end
local hitParts
local hitPartCount = 0
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 = self.headPart and self.headPart.CFrame.p or
castPoints[1]
local torsoPoint = self.torsoPart and self.torsoPart.CFrame.p or
castPoints[2]
hitParts = self.camera:GetPartsObscuringTarget({headPoint,
torsoPoint}, ignoreList)
local partTargetTransparency = {}
-- Invisibilize objects that are in the way, restore those that aren't
anymore
for hitPart, originalLTM in pairs(self.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
self.savedHits[hitPart] = nil
end
end
return Invisicam
end
function _LegacyCamera()
self.cameraType = Enum.CameraType.Fixed
self.lastUpdate = tick()
self.lastDistanceToSubject = nil
return self
end
function LegacyCamera:GetModuleName()
return "LegacyCamera"
end
function LegacyCamera:Update(dt)
local forwardVector =
humanoid.RootPart.CFrame.lookVector
local y =
Util.GetAngleBetweenXZVectors(forwardVector, self:GetCameraLookVector())
if Util.IsFinite(y) then
-- Preserve vertical rotation from user input
self.rotateInput = Vector2.new(y,
self.rotateInput.Y)
end
end
newCameraFocus = CFrame.new(subjectPosition)
newCameraCFrame = CFrame.new(subjectPosition -
(distanceToSubject * newLookVector), subjectPosition)
end
elseif self.cameraType == Enum.CameraType.Watch then
if subjectPosition and player and camera then
local cameraLook = nil
if self.lastDistanceToSubject and
self.lastDistanceToSubject == self:GetCameraToSubjectDistance() then
-- Don't clobber the zoom if they zoomed the
camera
local newDistanceToSubject =
diffVector.magnitude
self:SetCameraToSubjectDistance(newDistanceToSubject)
end
end
newCameraFocus = CFrame.new(subjectPosition)
newCameraCFrame = CFrame.new(subjectPosition -
(distanceToSubject * newLookVector), subjectPosition)
self.lastDistanceToSubject = distanceToSubject
end
else
-- Unsupported type, return current values unchanged
return camera.CFrame, camera.Focus
end
self.lastUpdate = now
return newCameraCFrame, newCameraFocus
end
return LegacyCamera
end
function _OrbitalCamera()
-- 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
self.lastUpdate = tick()
-- OrbitalCamera-specific members
self.changedSignalConnections = {}
self.refAzimuthRad = nil
self.curAzimuthRad = nil
self.minAzimuthAbsoluteRad = nil
self.maxAzimuthAbsoluteRad = nil
self.useAzimuthLimits = nil
self.curElevationRad = nil
self.minElevationRad = nil
self.maxElevationRad = nil
self.curDistance = nil
self.minDistance = nil
self.maxDistance = nil
-- Gamepad
self.r3ButtonDown = false
self.l3ButtonDown = false
self.gamepadDollySpeedMultiplier = 1
self.lastUserPanCamera = tick()
self.externalProperties = {}
self.externalProperties["InitialDistance"] = 25
self.externalProperties["MinDistance"] = 10
self.externalProperties["MaxDistance"] = 100
self.externalProperties["InitialElevation"] = 35
self.externalProperties["MinElevation"] = 35
self.externalProperties["MaxElevation"] = 35
self.externalProperties["ReferenceAzimuth"] = -45 -- Angle around
the Y axis where the camera starts. -45 offsets the camera in the -X and +Z
directions equally
self.externalProperties["CWAzimuthTravel"] = 90 -- How many
degrees the camera is allowed to rotate from the reference position, CW as seen
from above
self.externalProperties["CCWAzimuthTravel"] = 90 -- How many
degrees the camera is allowed to rotate from the reference position, CCW as seen
from above
self.externalProperties["UseAzimuthLimits"] = false -- Full rotation
around Y axis available by default
self:LoadNumberValueParameters()
return self
end
if updateFunction then
if self.changedSignalConnections[name] then
self.changedSignalConnections[name]:Disconnect()
end
self.changedSignalConnections[name] =
valueObj.Changed:Connect(function(newValue)
self.externalProperties[name] = newValue
updateFunction(self)
end)
end
end
function OrbitalCamera:SetAndBoundsCheckAzimuthValues()
self.minAzimuthAbsoluteRad =
math.rad(self.externalProperties["ReferenceAzimuth"]) -
math.abs(math.rad(self.externalProperties["CWAzimuthTravel"]))
self.maxAzimuthAbsoluteRad =
math.rad(self.externalProperties["ReferenceAzimuth"]) +
math.abs(math.rad(self.externalProperties["CCWAzimuthTravel"]))
self.useAzimuthLimits = self.externalProperties["UseAzimuthLimits"]
if self.useAzimuthLimits then
self.curAzimuthRad = math.max(self.curAzimuthRad,
self.minAzimuthAbsoluteRad)
self.curAzimuthRad = math.min(self.curAzimuthRad,
self.maxAzimuthAbsoluteRad)
end
end
function OrbitalCamera: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(self.externalProperties["MinElevation"], MIN_ALLOWED_ELEVATION_DEG)
local maxElevationDeg =
math.min(self.externalProperties["MaxElevation"], MAX_ALLOWED_ELEVATION_DEG)
function OrbitalCamera:SetAndBoundsCheckDistanceValues()
self.minDistance = self.externalProperties["MinDistance"]
self.maxDistance = self.externalProperties["MaxDistance"]
self.curDistance = math.max(self.curDistance, self.minDistance)
self.curDistance = math.min(self.curDistance, self.maxDistance)
end
self:SetAndBoundsCheckAzimuthValues()
self:SetAndBoundsCheckElevationValues()
self:SetAndBoundsCheckDistanceValues()
end
function OrbitalCamera:GetModuleName()
return "OrbitalCamera"
end
function OrbitalCamera:SetInitialOrientation(humanoid)
if not humanoid or not humanoid.RootPart then
warn("OrbitalCamera could not set initial orientation due to
missing humanoid")
return
end
local newDesiredLook = (humanoid.RootPart.CFrame.lookVector -
Vector3.new(0,0.23,0)).unit
local horizontalShift = Util.GetAngleBetweenXZVectors(newDesiredLook,
self:GetCameraLookVector())
local vertShift = math.asin(self:GetCameraLookVector().y) -
math.asin(newDesiredLook.y)
if not Util.IsFinite(horizontalShift) then
horizontalShift = 0
end
if not Util.IsFinite(vertShift) then
vertShift = 0
end
self.rotateInput = Vector2.new(horizontalShift, vertShift)
end
function OrbitalCamera:SetCameraToSubjectDistance(desiredSubjectDistance)
print("OrbitalCamera SetCameraToSubjectDistance
",desiredSubjectDistance)
local player = PlayersService.LocalPlayer
if player then
self.currentSubjectDistance = math.clamp(desiredSubjectDistance,
self.minDistance, self.maxDistance)
function OrbitalCamera:CalculateNewLookVector(suppliedLookVector,
xyRotateVector)
local currLookVector = suppliedLookVector or self:GetCameraLookVector()
local currPitchAngle = math.asin(currLookVector.y)
local yTheta = math.clamp(xyRotateVector.y, currPitchAngle -
math.rad(MAX_ALLOWED_ELEVATION_DEG), currPitchAngle -
math.rad(MIN_ALLOWED_ELEVATION_DEG))
local constrainedRotateInput = Vector2.new(xyRotateVector.x, yTheta)
local startCFrame = CFrame.new(ZERO_VECTOR3, currLookVector)
local newLookVector = (CFrame.Angles(0, -constrainedRotateInput.x, 0) *
startCFrame * CFrame.Angles(-constrainedRotateInput.y,0,0)).lookVector
return newLookVector
end
function OrbitalCamera:BindGamepadInputActions()
self:BindAction("OrbitalCamGamepadPan", function(name, state, input)
return self:GetGamepadPan(name, state, input) end,
false, Enum.KeyCode.Thumbstick2)
self:BindAction("OrbitalCamGamepadZoom", function(name, state, input)
return self:DoGamepadZoom(name, state, input) end,
false, Enum.KeyCode.ButtonR3, Enum.KeyCode.ButtonL3)
end
-- [[ Update ]]--
function OrbitalCamera:Update(dt)
local now = tick()
local timeDelta = (now - self.lastUpdate)
local userPanningTheCamera = (self.UserPanningTheCamera == true)
local camera = workspace.CurrentCamera
local newCameraCFrame = camera.CFrame
local newCameraFocus = camera.Focus
local player = PlayersService.LocalPlayer
local cameraSubject = camera and camera.CameraSubject
local isInVehicle = cameraSubject and cameraSubject:IsA('VehicleSeat')
local isOnASkateboard = cameraSubject and
cameraSubject:IsA('SkateboardPlatform')
if self.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, timeDelta)
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
if self.useAzimuthLimits then
self.curAzimuthRad = math.clamp(self.curAzimuthRad,
self.minAzimuthAbsoluteRad, self.maxAzimuthAbsoluteRad)
else
self.curAzimuthRad = (self.curAzimuthRad ~= 0) and
(math.sign(self.curAzimuthRad) * (math.abs(self.curAzimuthRad) % TAU)) or 0
end
self.curElevationRad = math.clamp(self.curElevationRad +
self.rotateInput.y, self.minElevationRad, self.maxElevationRad)
local cameraPosVector = self.currentSubjectDistance *
( CFrame.fromEulerAnglesYXZ( -self.curElevationRad, self.curAzimuthRad, 0 ) *
UNIT_Z )
local camPos = subjectPosition + cameraPosVector
self.rotateInput = ZERO_VECTOR2
end
self.lastCameraTransform = newCameraCFrame
self.lastCameraFocus = newCameraFocus
if (isInVehicle or isOnASkateboard) and
cameraSubject:IsA('BasePart') then
self.lastSubjectCFrame = cameraSubject.CFrame
else
self.lastSubjectCFrame = nil
end
end
self.lastUpdate = now
return newCameraCFrame, newCameraFocus
end
return OrbitalCamera
end
function _ClassicCamera()
local FFlagUserCameraToggle do
local success, result = pcall(function()
return UserSettings():IsUserFeatureEnabled("UserCameraToggle")
end)
FFlagUserCameraToggle = success and result
end
self.isFollowCamera = false
self.isCameraToggle = false
self.lastUpdate = tick()
self.cameraToggleSpring = Util.Spring.new(5, 0)
return self
end
function ClassicCamera:GetCameraToggleOffset(dt)
assert(FFlagUserCameraToggle)
if self.isCameraToggle then
local zoom = self.currentSubjectDistance
if CameraInput.getTogglePan() then
self.cameraToggleSpring.goal = math.clamp(Util.map(zoom,
0.5, self.FIRST_PERSON_DISTANCE_THRESHOLD, 0, 1), 0, 1)
else
self.cameraToggleSpring.goal = 0
end
return Vector3.new()
end
self.isFollowCamera = cameraMovementMode ==
Enum.ComputerCameraMovementMode.Follow
self.isCameraToggle = cameraMovementMode ==
Enum.ComputerCameraMovementMode.CameraToggle
end
function ClassicCamera:Update()
local now = tick()
local timeDelta = now - self.lastUpdate
if self.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, timeDelta)
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)
end
end
end
if (isInVehicle or isOnASkateboard or
(self.isFollowCamera and isClimbing)) and self.lastUpdate and humanoid and
humanoid.Torso then
if isInFirstPerson then
if self.lastSubjectCFrame and
(isInVehicle or isOnASkateboard) and cameraSubject:IsA('BasePart') then
local y = -
Util.GetAngleBetweenXZVectors(self.lastSubjectCFrame.lookVector,
cameraSubject.CFrame.lookVector)
if Util.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 = math.clamp(tweenSpeed +
tweenAcceleration * timeDelta, 0, tweenMaxSpeed)
local y =
Util.GetAngleBetweenXZVectors(forwardVector, self:GetCameraLookVector())
if Util.IsFinite(y) and math.abs(y) >
0.0001 then
self.rotateInput = self.rotateInput
+ Vector2.new(y * percent, 0)
end
end
local y =
Util.GetAngleBetweenXZVectors(lastVec, self:GetCameraLookVector())
if VREnabled then
newCameraFocus = self:GetVRFocus(subjectPosition,
timeDelta)
else
newCameraFocus = CFrame.new(subjectPosition)
end
if VRService.VREnabled then
newCameraFocus = self:GetVRFocus(subjectPosition,
timeDelta)
else
newCameraFocus = CFrame.new(subjectPosition)
end
newCameraCFrame = CFrame.new(newCameraFocus.p - (zoom *
newLookVector), newCameraFocus.p) + Vector3.new(0, cameraHeight, 0)
end
if FFlagUserCameraToggle then
local toggleOffset = self:GetCameraToggleOffset(timeDelta)
newCameraFocus = newCameraFocus + toggleOffset
newCameraCFrame = newCameraCFrame + toggleOffset
end
self.lastCameraTransform = newCameraCFrame
self.lastCameraFocus = newCameraFocus
if (isInVehicle or isOnASkateboard) and
cameraSubject:IsA('BasePart') then
self.lastSubjectCFrame = cameraSubject.CFrame
else
self.lastSubjectCFrame = nil
end
end
self.lastUpdate = now
return newCameraCFrame, newCameraFocus
end
function ClassicCamera:EnterFirstPerson()
self.inFirstPerson = true
self:UpdateMouseBehavior()
end
function ClassicCamera:LeaveFirstPerson()
self.inFirstPerson = false
self:UpdateMouseBehavior()
end
return ClassicCamera
end
function _CameraUtils()
local CameraUtils = {}
local FFlagUserCameraToggle do
local success, result = pcall(function()
return UserSettings():IsUserFeatureEnabled("UserCameraToggle")
end)
FFlagUserCameraToggle = success and result
end
local offset = p0 - g
local decay = math.exp(-f*dt)
self.pos = p1
self.vel = v1
return p1
end
end
CameraUtils.Spring = Spring
-- From TransparencyController
function CameraUtils.Round(num, places)
local decimalPivot = 10^places
return math.floor(num * decimalPivot + 0.5) / decimalPivot
end
function CameraUtils.IsFinite(val)
return val == val and val ~= math.huge and val ~= -math.huge
end
function CameraUtils.IsFiniteVector3(vec3)
return CameraUtils.IsFinite(vec3.X) and CameraUtils.IsFinite(vec3.Y)
and CameraUtils.IsFinite(vec3.Z)
end
function CameraUtils.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 math.clamp(point, -1, 1)
end
return Vector2.new(onAxis(thumbstickPosition.x),
onAxis(thumbstickPosition.y))
end
if enumValue == Enum.TouchCameraMovementMode.Classic or
enumValue == Enum.DevTouchCameraMovementMode.Classic or
enumValue == Enum.DevComputerCameraMovementMode.Classic or
enumValue == Enum.ComputerCameraMovementMode.Classic then
return Enum.ComputerCameraMovementMode.Classic
end
if enumValue == Enum.TouchCameraMovementMode.Follow or
enumValue == Enum.DevTouchCameraMovementMode.Follow or
enumValue == Enum.DevComputerCameraMovementMode.Follow or
enumValue == Enum.ComputerCameraMovementMode.Follow then
return Enum.ComputerCameraMovementMode.Follow
end
if enumValue == Enum.TouchCameraMovementMode.Orbital or
enumValue == Enum.DevTouchCameraMovementMode.Orbital or
enumValue == Enum.DevComputerCameraMovementMode.Orbital or
enumValue == Enum.ComputerCameraMovementMode.Orbital then
return Enum.ComputerCameraMovementMode.Orbital
end
if FFlagUserCameraToggle then
if enumValue == Enum.ComputerCameraMovementMode.CameraToggle or
enumValue ==
Enum.DevComputerCameraMovementMode.CameraToggle then
return Enum.ComputerCameraMovementMode.CameraToggle
end
end
return CameraUtils
end
function _CameraModule()
local CameraModule = {}
CameraModule.__index = CameraModule
local FFlagUserCameraToggle do
local success, result = pcall(function()
return UserSettings():IsUserFeatureEnabled("UserCameraToggle")
end)
FFlagUserCameraToggle = success and result
end
local FFlagUserRemoveTheCameraApi do
local success, result = pcall(function()
return
UserSettings():IsUserFeatureEnabled("UserRemoveTheCameraApi")
end)
FFlagUserRemoveTheCameraApi = success and result
end
local USER_GAME_SETTINGS_PROPERTIES =
{
"ComputerCameraMovementMode",
"ComputerMovementMode",
"ControlMode",
"GamepadCameraSensitivity",
"MouseSensitivity",
"RotationType",
"TouchCameraMovementMode",
"TouchMovementMode",
}
-- Load the near-field character transparency controller and the mouse lock
"shift lock" controller
local TransparencyController = _TransparencyController()
local MouseLockController = _MouseLockController()
PlayerScripts:RegisterTouchCameraMovementMode(Enum.TouchCameraMovementMode.Default)
PlayerScripts:RegisterTouchCameraMovementMode(Enum.TouchCameraMovementMode.Follow)
PlayerScripts:RegisterTouchCameraMovementMode(Enum.TouchCameraMovementMode.Classic)
PlayerScripts:RegisterComputerCameraMovementMode(Enum.ComputerCameraMovementMode.De
fault)
PlayerScripts:RegisterComputerCameraMovementMode(Enum.ComputerCameraMovementMode.Fo
llow)
PlayerScripts:RegisterComputerCameraMovementMode(Enum.ComputerCameraMovementMode.Cl
assic)
if FFlagUserCameraToggle then
PlayerScripts:RegisterComputerCameraMovementMode(Enum.ComputerCameraMovementMode.Ca
meraToggle)
end
end
CameraModule.FFlagUserCameraToggle = FFlagUserCameraToggle
function CameraModule.new()
local self = setmetatable({},CameraModule)
self.currentComputerCameraMovementMode = nil
-- Connections to events
self.cameraSubjectChangedConn = nil
self.cameraTypeChangedConn = nil
self.activeTransparencyController = TransparencyController.new()
self.activeTransparencyController:Enable(true)
self:ActivateCameraController(self:GetCameraControlChoice())
self:ActivateOcclusionModule(Players.LocalPlayer.DevCameraOcclusionMode)
self:OnCurrentCameraChanged() -- Does initializations and makes first
camera controller
RunService:BindToRenderStep("cameraRenderUpdate",
Enum.RenderPriority.Camera.Value, function(dt) self:Update(dt) end)
Players.LocalPlayer:GetPropertyChangedSignal(propertyName):Connect(function()
self:OnLocalPlayerCameraPropertyChanged(propertyName)
end)
end
UserGameSettings:GetPropertyChangedSignal(propertyName):Connect(function()
self:OnUserGameSettingsPropertyChanged(propertyName)
end)
end
game.Workspace:GetPropertyChangedSignal("CurrentCamera"):Connect(function()
self:OnCurrentCameraChanged()
end)
self.lastInputType = UserInputService:GetLastInputType()
UserInputService.LastInputTypeChanged:Connect(function(newLastInputType)
self.lastInputType = newLastInputType
end)
return self
end
function CameraModule:GetCameraMovementModeFromSettings()
local cameraMode = Players.LocalPlayer.CameraMode
-- Lock First Person trumps all other settings and forces ClassicCamera
if cameraMode == Enum.CameraMode.LockFirstPerson then
return
CameraUtils.ConvertCameraModeEnumToStandard(Enum.ComputerCameraMovementMode.Classic
)
end
return devMode
end
self.activeOcclusionModule:CharacterAdded(Players.LocalPlayer.Character,
Players.LocalPlayer )
end
else
-- When Poppercam is enabled, we send it all existing
player characters for its raycast ignore list
for _, player in pairs(Players:GetPlayers()) do
if player and player.Character then
self.activeOcclusionModule:CharacterAdded(player.Character, player)
end
end
self.activeOcclusionModule:OnCameraSubjectChanged(game.Workspace.CurrentCamera.Came
raSubject)
end
if legacyCameraType~=nil then
--[[
This function has been passed a CameraType enum value. Some
of these map to the use of
the LegacyCamera module, the value "Custom" will be
translated to a movementMode enum
value based on Dev and User settings, and "Scriptable" will
disable the camera controller.
--]]
if self.activeCameraController then
if cameraMovementMode~=nil then
self.activeCameraController:SetCameraMovementMode(cameraMovementMode)
elseif legacyCameraType~=nil then
-- Note that this is only called when legacyCameraType is
not a type that
-- was convertible to a ComputerCameraMovementMode value,
i.e. really only applies to LegacyCamera
self.activeCameraController:SetCameraType(legacyCameraType)
end
end
end
-- Note: The active transparency controller could be made to listen for this
event itself.
function CameraModule:OnCameraSubjectChanged()
if self.activeTransparencyController then
self.activeTransparencyController:SetSubject(game.Workspace.CurrentCamera.CameraSub
ject)
end
if self.activeOcclusionModule then
self.activeOcclusionModule:OnCameraSubjectChanged(game.Workspace.CurrentCamera.Came
raSubject)
end
end
function CameraModule:OnCameraTypeChanged(newCameraType)
if newCameraType == Enum.CameraType.Scriptable then
if UserInputService.MouseBehavior ==
Enum.MouseBehavior.LockCenter then
UserInputService.MouseBehavior = Enum.MouseBehavior.Default
end
end
if self.cameraSubjectChangedConn then
self.cameraSubjectChangedConn:Disconnect()
end
if self.cameraTypeChangedConn then
self.cameraTypeChangedConn:Disconnect()
end
self.cameraSubjectChangedConn =
currentCamera:GetPropertyChangedSignal("CameraSubject"):Connect(function()
self:OnCameraSubjectChanged(currentCamera.CameraSubject)
end)
self.cameraTypeChangedConn =
currentCamera:GetPropertyChangedSignal("CameraType"):Connect(function()
self:OnCameraTypeChanged(currentCamera.CameraType)
end)
self:OnCameraSubjectChanged(currentCamera.CameraSubject)
self:OnCameraTypeChanged(currentCamera.CameraType)
end
function CameraModule:OnLocalPlayerCameraPropertyChanged(propertyName)
if propertyName == "CameraMode" then
-- CameraMode is only used to turn on/off forcing the player into
first person view. The
-- Note: The case "Classic" is used for all other views and does
not correspond only to the ClassicCamera module
if Players.LocalPlayer.CameraMode ==
Enum.CameraMode.LockFirstPerson then
-- Locked in first person, use ClassicCamera which supports
this
if not self.activeCameraController or
self.activeCameraController:GetModuleName() ~= "ClassicCamera" then
self:ActivateCameraController(CameraUtils.ConvertCameraModeEnumToStandard(Enum.DevC
omputerCameraMovementMode.Classic))
end
if self.activeCameraController then
self.activeCameraController:UpdateForDistancePropertyChange()
end
elseif Players.LocalPlayer.CameraMode == Enum.CameraMode.Classic
then
-- Not locked in first person view
local cameraMovementMode =self:
GetCameraMovementModeFromSettings()
self:ActivateCameraController(CameraUtils.ConvertCameraModeEnumToStandard(cameraMov
ementMode))
else
warn("Unhandled value for property player.CameraMode:
",Players.LocalPlayer.CameraMode)
end
self:ActivateCameraController(CameraUtils.ConvertCameraModeEnumToStandard(cameraMov
ementMode))
self:ActivateOcclusionModule(Players.LocalPlayer.DevCameraOcclusionMode)
self.activeCameraController:UpdateForDistancePropertyChange()
end
elseif propertyName == "DevTouchMovementMode" then
elseif propertyName == "DevComputerMovementMode" then
elseif propertyName == "DevEnableMouseLock" then
-- This is the enabling/disabling of "Shift Lock" mode, not
LockFirstPerson (which is a CameraMode)
-- Note: Enabling and disabling of MouseLock mode is normally
only a publish-time choice made via
-- the corresponding EnableMouseLockOption checkbox of
StarterPlayer, and this script does not have
-- support for changing the availability of MouseLock at runtime
(this would require listening to
-- Player.DevEnableMouseLock changes)
end
end
function CameraModule:OnUserGameSettingsPropertyChanged(propertyName)
if propertyName == "ComputerCameraMovementMode" then
local cameraMovementMode =
self:GetCameraMovementModeFromSettings()
self:ActivateCameraController(CameraUtils.ConvertCameraModeEnumToStandard(cameraMov
ementMode))
end
end
--[[
Main RenderStep Update. The camera controller and occlusion module both
have opportunities
to set and modify (respectively) the CFrame and Focus before it is set
once on CurrentCamera.
The camera and occlusion modules should only return CFrames, not set
the CFrame property of
CurrentCamera directly.
--]]
function CameraModule:Update(dt)
if self.activeCameraController then
if FFlagUserCameraToggle then
self.activeCameraController:UpdateMouseBehavior()
end
-- Here is where the new CFrame and Focus are set for this render
frame
game.Workspace.CurrentCamera.CFrame = newCameraCFrame
game.Workspace.CurrentCamera.Focus = newCameraFocus
if player then
if self.lastInputType == Enum.UserInputType.Touch or
UserInputService.TouchEnabled then
-- Touch
if player.DevTouchCameraMode ==
Enum.DevTouchCameraMovementMode.UserChoice then
return
CameraUtils.ConvertCameraModeEnumToStandard( UserGameSettings.TouchCameraMovementMo
de )
else
return
CameraUtils.ConvertCameraModeEnumToStandard( player.DevTouchCameraMode )
end
else
-- Computer
if player.DevComputerCameraMode ==
Enum.DevComputerCameraMovementMode.UserChoice then
local computerMovementMode =
CameraUtils.ConvertCameraModeEnumToStandard(UserGameSettings.ComputerCameraMovement
Mode)
return
CameraUtils.ConvertCameraModeEnumToStandard(computerMovementMode)
else
return
CameraUtils.ConvertCameraModeEnumToStandard(player.DevComputerCameraMode)
end
end
end
end
function CameraModule:OnPlayerAdded(player)
player.CharacterAdded:Connect(function(char)
self:OnCharacterAdded(char, player)
end)
player.CharacterRemoving:Connect(function(char)
self:OnCharacterRemoving(char, player)
end)
end
function CameraModule:OnMouseLockToggled()
if self.activeMouseLockController then
local mouseLocked =
self.activeMouseLockController:GetIsMouseLocked()
local mouseLockOffset =
self.activeMouseLockController:GetMouseLockOffset()
if self.activeCameraController then
self.activeCameraController:SetIsMouseLocked(mouseLocked)
self.activeCameraController:SetMouseLockOffset(mouseLockOffset)
end
end
end
--begin edit
local Camera = CameraModule
local IDENTITYCF = CFrame.new()
local lastUpCFrame = IDENTITYCF
Camera.UpVector = Vector3.new(0, 1, 0)
Camera.TransitionRate = 0.15
Camera.UpCFrame = IDENTITYCF
function Camera:GetUpVector(oldUpVector)
return oldUpVector
end
local function getRotationBetween(u, v, axis)
local dot, uxv = u:Dot(v), u:Cross(v)
if (dot < -0.99999) then return CFrame.fromAxisAngle(axis, math.pi) end
return CFrame.new(0, 0, 0, uxv.x, uxv.y, uxv.z, 1 + dot)
end
function Camera:CalculateUpCFrame()
local oldUpVector = self.UpVector
local newUpVector = self:GetUpVector(oldUpVector)
lastUpCFrame = self.UpCFrame
end
function Camera:Update(dt)
if self.activeCameraController then
if Camera.FFlagUserCameraToggle then
self.activeCameraController:UpdateMouseBehavior()
end
self:CalculateUpCFrame()
self.activeCameraController:UpdateUpCFrame(self.UpCFrame)
if (self.activeCameraController.lastCameraTransform) then
self.activeCameraController.lastCameraTransform =
newCameraCFrame
self.activeCameraController.lastCameraFocus =
newCameraFocus
end
if self.activeOcclusionModule then
newCameraCFrame, newCameraFocus =
self.activeOcclusionModule:Update(dt, newCameraCFrame, newCameraFocus)
end
game.Workspace.CurrentCamera.CFrame = newCameraCFrame
game.Workspace.CurrentCamera.Focus = newCameraFocus
if self.activeTransparencyController then
self.activeTransparencyController:Update()
end
end
end
function Camera:IsFirstPerson()
if self.activeCameraController then
return self.activeCameraController:InFirstPerson()
end
return false
end
function Camera:IsMouseLocked()
if self.activeCameraController then
return self.activeCameraController:GetIsMouseLocked()
end
return false
end
function Camera:IsToggleMode()
if self.activeCameraController then
return self.activeCameraController.isCameraToggle
end
return false
end
function Camera:IsCamRelative()
return self:IsMouseLocked() or self:IsFirstPerson()
--return self:IsToggleMode(), self:IsMouseLocked(),
self:IsFirstPerson()
end
--
local Utils = _CameraUtils()
function Utils.GetAngleBetweenXZVectors(v1, v2)
local upCFrame = lastUpCFrame
v1 = upCFrame:VectorToObjectSpace(v1)
v2 = upCFrame:VectorToObjectSpace(v2)
return math.atan2(v2.X*v1.Z-v2.Z*v1.X, v2.X*v1.X+v2.Z*v1.Z)
end
--end edit
local cameraModuleObject = CameraModule.new()
local cameraApi = {}
return cameraModuleObject
end
function _ClickToMoveDisplay()
local ClickToMoveDisplay = {}
local WAYPOINT_INCLUDE_FACTOR = 2
local LAST_DOT_DISTANCE = 3
local TWEEN_WAYPOINT_THRESHOLD = 5
local TRAIL_DOT_MIN_SCALE = 1
local TRAIL_DOT_MIN_DISTANCE = 10
local TRAIL_DOT_MAX_SCALE = 2.5
local TRAIL_DOT_MAX_DISTANCE = 100
local TrailDot = {}
TrailDot.__index = TrailDot
function TrailDot:Destroy()
self.DisplayModel:Destroy()
end
function TrailDot:NewDisplayModel(position)
local newDisplayModel = TrailDotTemplate:Clone()
placePathWaypoint(newDisplayModel, position)
return newDisplayModel
end
self.DisplayModel = self:NewDisplayModel(position)
self.ClosestWayPoint = closestWaypoint
return self
end
local EndWaypoint = {}
EndWaypoint.__index = EndWaypoint
function EndWaypoint:Destroy()
self.Destroyed = true
self.Tween:Cancel()
self.DisplayModel:Destroy()
end
function EndWaypoint:NewDisplayModel(position)
local newDisplayModel = EndWaypointTemplate:Clone()
placePathWaypoint(newDisplayModel, position)
return newDisplayModel
end
function EndWaypoint:CreateTween()
local tweenInfo = TweenInfo.new(0.5, Enum.EasingStyle.Sine,
Enum.EasingDirection.Out, -1, true)
local tween = TweenService:Create(
self.DisplayModel.EndWaypointBillboard,
tweenInfo,
{ SizeOffset = ENDWAYPOINT_SIZE_OFFSET_MAX }
)
tween:Play()
return tween
end
function EndWaypoint:TweenInFrom(originalPosition)
local currentPositon = self.DisplayModel.Position
local studsOffset = originalPosition - currentPositon
self.DisplayModel.EndWaypointBillboard.StudsOffset = Vector3.new(0,
studsOffset.Y, 0)
local tweenInfo = TweenInfo.new(1, Enum.EasingStyle.Sine,
Enum.EasingDirection.Out)
local tween = TweenService:Create(
self.DisplayModel.EndWaypointBillboard,
tweenInfo,
{ StudsOffset = Vector3.new(0, 0, 0) }
)
tween:Play()
return tween
end
self.DisplayModel = self:NewDisplayModel(position)
self.Destroyed = false
if originalPosition and (originalPosition - position).magnitude >
TWEEN_WAYPOINT_THRESHOLD then
self.Tween = self:TweenInFrom(originalPosition)
coroutine.wrap(function()
self.Tween.Completed:Wait()
if not self.Destroyed then
self.Tween = self:CreateTween()
end
end)()
else
self.Tween = self:CreateTween()
end
self.ClosestWayPoint = closestWaypoint
return self
end
local FailureWaypoint = {}
FailureWaypoint.__index = FailureWaypoint
function FailureWaypoint:Hide()
self.DisplayModel.Parent = nil
end
function FailureWaypoint:Destroy()
self.DisplayModel:Destroy()
end
function FailureWaypoint:NewDisplayModel(position)
local newDisplayModel = FailureWaypointTemplate:Clone()
placePathWaypoint(newDisplayModel, position)
local ray = Ray.new(position + Vector3.new(0, 2.5, 0), Vector3.new(0, -
10, 0))
local hitPart, hitPoint, hitNormal =
Workspace:FindPartOnRayWithIgnoreList(
ray, { Workspace.CurrentCamera, LocalPlayer.Character }
)
if hitPart then
newDisplayModel.CFrame = CFrame.new(hitPoint, hitPoint +
hitNormal)
newDisplayModel.Parent = getTrailDotParent()
end
return newDisplayModel
end
function FailureWaypoint:RunFailureTween()
wait(FAILURE_TWEEN_LENGTH) -- Delay one tween length betfore starting
tweening
-- Tween out from center
local tweenInfo = TweenInfo.new(FAILURE_TWEEN_LENGTH/2,
Enum.EasingStyle.Sine, Enum.EasingDirection.Out)
local tweenLeft =
TweenService:Create(self.DisplayModel.FailureWaypointBillboard, tweenInfo,
{ SizeOffset = FAIL_WAYPOINT_SIZE_OFFSET_LEFT })
tweenLeft:Play()
local tweenLeftRoation =
TweenService:Create(self.DisplayModel.FailureWaypointBillboard.Frame, tweenInfo,
{ Rotation = 10 })
tweenLeftRoation:Play()
tweenLeft.Completed:wait()
local tweenRotate =
TweenService:Create(self.DisplayModel.FailureWaypointBillboard.Frame, tweenInfo,
{ Rotation = -10 })
tweenRotate:Play()
tweenSideToSide.Completed:wait()
local tweenRoation =
TweenService:Create(self.DisplayModel.FailureWaypointBillboard.Frame, tweenInfo,
{ Rotation = 0 })
tweenRoation:Play()
tweenCenter.Completed:wait()
function FailureWaypoint.new(position)
local self = setmetatable({}, FailureWaypoint)
self.DisplayModel = self:NewDisplayModel(position)
return self
end
local reversedTrailDots = {}
count = 1
for i = #newTrailDots, 1, -1 do
reversedTrailDots[count] = newTrailDots[i]
count = count + 1
end
return reversedTrailDots
end
local createPathCount = 0
-- originalEndWaypoint is optional, causes the waypoint to tween from that
position.
function ClickToMoveDisplay.CreatePathDisplay(wayPoints, originalEndWaypoint)
createPathCount = createPathCount + 1
local trailDots = createTrailDots(wayPoints, originalEndWaypoint)
local reiszeTrailDotsUpdateName =
"ClickToMoveResizeTrail" ..createPathCount
local function resizeTrailDots()
if #trailDots == 0 then
RunService:UnbindFromRenderStep(reiszeTrailDotsUpdateName)
return
end
local cameraPos = Workspace.CurrentCamera.CFrame.p
for i = 1, #trailDots do
local trailDotImage =
trailDots[i].DisplayModel:FindFirstChild("TrailDotImage")
if trailDotImage then
local distanceToCamera =
(trailDots[i].DisplayModel.Position - cameraPos).magnitude
trailDotImage.Size =
getTrailDotScale(distanceToCamera, TrailDotSize)
end
end
end
RunService:BindToRenderStep(reiszeTrailDotsUpdateName,
Enum.RenderPriority.Camera.Value - 1, resizeTrailDots)
function ClickToMoveDisplay.CreateEndWaypoint(position)
return EndWaypoint.new(position)
end
function ClickToMoveDisplay.PlayFailureAnimation()
local myHumanoid = findPlayerHumanoid()
if myHumanoid then
local animationTrack = getFailureAnimationTrack(myHumanoid)
animationTrack:Play()
end
end
function ClickToMoveDisplay.CancelFailureAnimation()
if lastFailureAnimationTrack ~= nil and
lastFailureAnimationTrack.IsPlaying then
lastFailureAnimationTrack:Stop()
end
end
function ClickToMoveDisplay.SetWaypointTexture(texture)
TrailDotIcon = texture
TrailDotTemplate, EndWaypointTemplate, FailureWaypointTemplate =
CreateWaypointTemplates()
end
function ClickToMoveDisplay.GetWaypointTexture()
return TrailDotIcon
end
function ClickToMoveDisplay.SetWaypointRadius(radius)
TrailDotSize = Vector2.new(radius, radius)
TrailDotTemplate, EndWaypointTemplate, FailureWaypointTemplate =
CreateWaypointTemplates()
end
function ClickToMoveDisplay.GetWaypointRadius()
return TrailDotSize.X
end
function ClickToMoveDisplay.SetEndWaypointTexture(texture)
EndWaypointIcon = texture
TrailDotTemplate, EndWaypointTemplate, FailureWaypointTemplate =
CreateWaypointTemplates()
end
function ClickToMoveDisplay.GetEndWaypointTexture()
return EndWaypointIcon
end
function ClickToMoveDisplay.SetWaypointsAlwaysOnTop(alwaysOnTop)
WaypointsAlwaysOnTop = alwaysOnTop
TrailDotTemplate, EndWaypointTemplate, FailureWaypointTemplate =
CreateWaypointTemplates()
end
function ClickToMoveDisplay.GetWaypointsAlwaysOnTop()
return WaypointsAlwaysOnTop
end
return ClickToMoveDisplay
end
function _BaseCharacterController()
local ZERO_VECTOR3 = Vector3.new(0,0,0)
function BaseCharacterController.new()
local self = setmetatable({}, BaseCharacterController)
self.enabled = false
self.moveVector = ZERO_VECTOR3
self.moveVectorIsCameraRelative = true
self.isJumping = false
return self
end
function BaseCharacterController:OnRenderStepped(dt)
-- By default, nothing to do
end
function BaseCharacterController:GetMoveVector()
return self.moveVector
end
function BaseCharacterController:IsMoveVectorCameraRelative()
return self.moveVectorIsCameraRelative
end
function BaseCharacterController:GetIsJumping()
return self.isJumping
end
return BaseCharacterController
end
function _VehicleController()
local ContextActionService = game:GetService("ContextActionService")
local AUTO_PILOT_DEFAULT_MAX_STEERING_ANGLE = 35
-- Note that VehicleController does not derive from BaseCharacterController,
it is a special case
local VehicleController = {}
VehicleController.__index = VehicleController
function VehicleController.new(CONTROL_ACTION_PRIORITY)
local self = setmetatable({}, VehicleController)
self.CONTROL_ACTION_PRIORITY = CONTROL_ACTION_PRIORITY
self.enabled = false
self.vehicleSeat = nil
self.throttle = 0
self.steer = 0
self.acceleration = 0
self.decceleration = 0
self.turningRight = 0
self.turningLeft = 0
self.vehicleMoveVector = ZERO_VECTOR3
self.autoPilot = {}
self.autoPilot.MaxSpeed = 0
self.autoPilot.MaxSteeringAngle = 0
return self
end
function VehicleController:BindContextActions()
if useTriggersForThrottle then
ContextActionService:BindActionAtPriority("throttleAccel",
(function(actionName, inputState, inputObject)
self:OnThrottleAccel(actionName, inputState, inputObject)
return Enum.ContextActionResult.Pass
end), false, self.CONTROL_ACTION_PRIORITY, Enum.KeyCode.ButtonR2)
ContextActionService:BindActionAtPriority("throttleDeccel",
(function(actionName, inputState, inputObject)
self:OnThrottleDeccel(actionName, inputState, inputObject)
return Enum.ContextActionResult.Pass
end), false, self.CONTROL_ACTION_PRIORITY, Enum.KeyCode.ButtonL2)
end
ContextActionService:BindActionAtPriority("arrowSteerRight",
(function(actionName, inputState, inputObject)
self:OnSteerRight(actionName, inputState, inputObject)
return Enum.ContextActionResult.Pass
end), false, self.CONTROL_ACTION_PRIORITY, Enum.KeyCode.Right)
ContextActionService:BindActionAtPriority("arrowSteerLeft",
(function(actionName, inputState, inputObject)
self:OnSteerLeft(actionName, inputState, inputObject)
return Enum.ContextActionResult.Pass
end), false, self.CONTROL_ACTION_PRIORITY, Enum.KeyCode.Left)
end
if enable then
if vehicleSeat then
self.vehicleSeat = vehicleSeat
self:SetupAutoPilot()
self:BindContextActions()
end
else
if useTriggersForThrottle then
ContextActionService:UnbindAction("throttleAccel")
ContextActionService:UnbindAction("throttleDeccel")
end
ContextActionService:UnbindAction("arrowSteerRight")
ContextActionService:UnbindAction("arrowSteerLeft")
self.vehicleSeat = nil
end
end
self.vehicleSeat.ThrottleFloat =
self:ComputeThrottle(localMoveVector)
self.vehicleSeat.SteerFloat =
self:ComputeSteer(localMoveVector)
function VehicleController:ComputeThrottle(localMoveVector)
if localMoveVector ~= ZERO_VECTOR3 then
local throttle = -localMoveVector.Z
return throttle
else
return 0.0
end
end
function VehicleController:ComputeSteer(localMoveVector)
if localMoveVector ~= ZERO_VECTOR3 then
local steerAngle = -math.atan2(-localMoveVector.x, -
localMoveVector.z) * (180 / math.pi)
return steerAngle / self.autoPilot.MaxSteeringAngle
else
return 0.0
end
end
function VehicleController:SetupAutoPilot()
-- Setup default
self.autoPilot.MaxSpeed = self.vehicleSeat.MaxSpeed
self.autoPilot.MaxSteeringAngle = AUTO_PILOT_DEFAULT_MAX_STEERING_ANGLE
-- VehicleSeat should have a MaxSteeringAngle as well.
-- Or we could look for a child "AutoPilotConfigModule" to find these
values
-- Or allow developer to set them through the API as like the
CLickToMove customization API
end
return VehicleController
end
function _TouchJump()
function TouchJump.new()
local self = setmetatable(BaseCharacterController.new(), TouchJump)
self.parentUIFrame = nil
self.jumpButton = nil
self.characterAddedConn = nil
self.humanoidStateEnabledChangedConn = nil
self.humanoidJumpPowerConn = nil
self.humanoidParentConn = nil
self.externallyEnabled = false
self.jumpPower = 0
self.jumpStateEnabled = true
self.isJumping = false
self.humanoid = nil -- saved reference because property change
connections are made using it
return self
end
function TouchJump:EnableButton(enable)
if enable then
if not self.jumpButton then
self:Create()
end
local humanoid = Players.LocalPlayer.Character and
Players.LocalPlayer.Character:FindFirstChildOfClass("Humanoid")
if humanoid and self.externallyEnabled then
if self.externallyEnabled then
if humanoid.JumpPower > 0 then
self.jumpButton.Visible = true
end
end
end
else
self.jumpButton.Visible = false
self.isJumping = false
self.jumpButton.ImageRectOffset = Vector2.new(1, 146)
end
end
function TouchJump:UpdateEnabled()
if self.jumpPower > 0 and self.jumpStateEnabled then
self:EnableButton(true)
else
self:EnableButton(false)
end
end
function TouchJump:HumanoidChanged(prop)
local humanoid = Players.LocalPlayer.Character and
Players.LocalPlayer.Character:FindFirstChildOfClass("Humanoid")
if humanoid then
if prop == "JumpPower" then
self.jumpPower = humanoid.JumpPower
self:UpdateEnabled()
elseif prop == "Parent" then
if not humanoid.Parent then
self.humanoidChangeConn:Disconnect()
end
end
end
end
function TouchJump:CharacterAdded(char)
if self.humanoidChangeConn then
self.humanoidChangeConn:Disconnect()
self.humanoidChangeConn = nil
end
self.humanoid = char:FindFirstChildOfClass("Humanoid")
while not self.humanoid do
char.ChildAdded:wait()
self.humanoid = char:FindFirstChildOfClass("Humanoid")
end
self.humanoidJumpPowerConn =
self.humanoid:GetPropertyChangedSignal("JumpPower"):Connect(function()
self.jumpPower = self.humanoid.JumpPower
self:UpdateEnabled()
end)
self.humanoidParentConn =
self.humanoid:GetPropertyChangedSignal("Parent"):Connect(function()
if not self.humanoid.Parent then
self.humanoidJumpPowerConn:Disconnect()
self.humanoidJumpPowerConn = nil
self.humanoidParentConn:Disconnect()
self.humanoidParentConn = nil
end
end)
self.humanoidStateEnabledChangedConn =
self.humanoid.StateEnabledChanged:Connect(function(state, enabled)
self:HumanoidStateEnabledChanged(state, enabled)
end)
self.jumpPower = self.humanoid.JumpPower
self.jumpStateEnabled =
self.humanoid:GetStateEnabled(Enum.HumanoidStateType.Jumping)
self:UpdateEnabled()
end
function TouchJump:SetupCharacterAddedFunction()
self.characterAddedConn =
Players.LocalPlayer.CharacterAdded:Connect(function(char)
self:CharacterAdded(char)
end)
if Players.LocalPlayer.Character then
self:CharacterAdded(Players.LocalPlayer.Character)
end
end
function TouchJump:Create()
if not self.parentUIFrame then
return
end
if self.jumpButton then
self.jumpButton:Destroy()
self.jumpButton = nil
end
self.jumpButton = Instance.new("ImageButton")
self.jumpButton.Name = "JumpButton"
self.jumpButton.Visible = false
self.jumpButton.BackgroundTransparency = 1
self.jumpButton.Image = TOUCH_CONTROL_SHEET
self.jumpButton.ImageRectOffset = Vector2.new(1, 146)
self.jumpButton.ImageRectSize = Vector2.new(144, 144)
self.jumpButton.Size = UDim2.new(0, jumpButtonSize, 0, jumpButtonSize)
touchObject = inputObject
self.jumpButton.ImageRectOffset = Vector2.new(146, 146)
self.isJumping = true
end)
self.jumpButton.InputEnded:connect(function(inputObject)
if inputObject == touchObject then
OnInputEnded()
end
end)
GuiService.MenuOpened:connect(function()
if touchObject then
OnInputEnded()
end
end)
self.jumpButton.Parent = self.parentUIFrame
end
return TouchJump
end
function _ClickToMoveController()
--[[ Roblox Services ]]--
local UserInputService = game:GetService("UserInputService")
local PathfindingService = game:GetService("PathfindingService")
local Players = game:GetService("Players")
local DebrisService = game:GetService('Debris')
local StarterGui = game:GetService("StarterGui")
local Workspace = game:GetService("Workspace")
local CollectionService = game:GetService("CollectionService")
local GuiService = game:GetService("GuiService")
--[[ Configuration ]]
local ShowPath = true
local PlayFailureAnimation = true
local UseDirectPath = false
local UseDirectPathForVehicle = true
local AgentSizeIncreaseFactor = 1.0
local UnreachableWaypointTimeout = 8
local FFlagUserNavigationClickToMoveSkipPassedWaypointsSuccess,
FFlagUserNavigationClickToMoveSkipPassedWaypointsResult = pcall(function() return
UserSettings():IsUserFeatureEnabled("UserNavigationClickToMoveSkipPassedWaypoints")
end)
local FFlagUserNavigationClickToMoveSkipPassedWaypoints =
FFlagUserNavigationClickToMoveSkipPassedWaypointsSuccess and
FFlagUserNavigationClickToMoveSkipPassedWaypointsResult
--------------------------UTIL LIBRARY-------------------------------
local Utility = {}
do
local function FindCharacterAncestor(part)
if part then
local humanoid = part:FindFirstChildOfClass("Humanoid")
if humanoid then
return part, humanoid
else
return FindCharacterAncestor(part.Parent)
end
end
end
Utility.FindCharacterAncestor = FindCharacterAncestor
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
--------------------------CHARACTER CONTROL-------------------------------
local CurrentIgnoreList
local CurrentIgnoreTag = nil
-----------------------------------
PATHER--------------------------------------
local directPathForHumanoid
local directPathForVehicle
if overrideUseDirectPath ~= nil then
directPathForHumanoid = overrideUseDirectPath
directPathForVehicle = overrideUseDirectPath
else
directPathForHumanoid = UseDirectPath
directPathForVehicle = UseDirectPathForVehicle
end
this.Cancelled = false
this.Started = false
this.Finished = Instance.new("BindableEvent")
this.PathFailed = Instance.new("BindableEvent")
this.PathComputing = false
this.PathComputed = false
this.OriginalTargetPoint = endPoint
this.TargetPoint = endPoint
this.TargetSurfaceNormal = surfaceNormal
this.DiedConn = nil
this.SeatedConn = nil
this.BlockedConn = nil
this.TeleportedConn = nil
this.CurrentPoint = 0
this.HumanoidOffsetFromPath = ZERO_VECTOR3
this.CurrentWaypointPosition = nil
this.CurrentWaypointPlaneNormal = ZERO_VECTOR3
this.CurrentWaypointPlaneDistance = 0
this.CurrentWaypointNeedsJump = false;
this.CurrentHumanoidPosition = ZERO_VECTOR3
this.CurrentHumanoidVelocity = 0
this.NextActionMoveDirection = ZERO_VECTOR3
this.NextActionJump = false
this.Timeout = 0
this.Humanoid = findPlayerHumanoid(Player)
this.OriginPoint = nil
this.AgentCanFollowPath = false
this.DirectPath = false
this.DirectPathRiseFirst = false
-- Setup agent
local agentRadius = 2
local agentHeight = 5
local agentCanJump = true
-- Reset PrimaryPart
vehicle.PrimaryPart = tempPrimaryPart
end
else
local extents = GetCharacter():GetExtentsSize()
agentRadius = AgentSizeIncreaseFactor * 0.5 *
math.sqrt(extents.X * extents.X + extents.Z * extents.Z)
agentHeight = AgentSizeIncreaseFactor * extents.Y
agentCanJump = (this.Humanoid.JumpPower > 0)
this.AgentCanFollowPath = true
this.DirectPath = directPathForHumanoid
this.DirectPathRiseFirst = this.Humanoid.Sit
end
function this:Cleanup()
if this.stopTraverseFunc then
this.stopTraverseFunc()
this.stopTraverseFunc = nil
end
if this.MoveToConn then
this.MoveToConn:Disconnect()
this.MoveToConn = nil
end
if this.BlockedConn then
this.BlockedConn:Disconnect()
this.BlockedConn = nil
end
if this.DiedConn then
this.DiedConn:Disconnect()
this.DiedConn = nil
end
if this.SeatedConn then
this.SeatedConn:Disconnect()
this.SeatedConn = nil
end
if this.TeleportedConn then
this.TeleportedConn:Disconnect()
this.TeleportedConn = nil
end
this.Started = false
end
function this:Cancel()
this.Cancelled = true
this:Cleanup()
end
function this:IsActive()
return this.AgentCanFollowPath and this.Started and not
this.Cancelled
end
function this:OnPathInterrupted()
-- Stop moving
this.Cancelled = true
this:OnPointReached(false)
end
function this:ComputePath()
if this.OriginPoint then
if this.PathComputed or this.PathComputing then return end
this.PathComputing = true
if this.AgentCanFollowPath then
if this.DirectPath then
this.pointList = {
PathWaypoint.new(this.OriginPoint,
Enum.PathWaypointAction.Walk),
PathWaypoint.new(this.TargetPoint,
this.DirectPathRiseFirst and Enum.PathWaypointAction.Jump or
Enum.PathWaypointAction.Walk)
}
this.PathComputed = true
else
this.pathResult:ComputeAsync(this.OriginPoint,
this.TargetPoint)
this.pointList = this.pathResult:GetWaypoints()
this.BlockedConn =
this.pathResult.Blocked:Connect(function(blockedIdx) this:OnPathBlocked(blockedIdx)
end)
this.PathComputed = this.pathResult.Status ==
Enum.PathStatus.Success
end
end
this.PathComputing = false
end
end
function this:IsValidPath()
this:ComputePath()
return this.PathComputed and this.AgentCanFollowPath
end
this.Recomputing = false
function this:OnPathBlocked(blockedWaypointIdx)
local pathBlocked = blockedWaypointIdx >= this.CurrentPoint
if not pathBlocked or this.Recomputing then
return
end
this.Recomputing = true
if this.stopTraverseFunc then
this.stopTraverseFunc()
this.stopTraverseFunc = nil
end
this.OriginPoint = this.Humanoid.RootPart.CFrame.p
this.pathResult:ComputeAsync(this.OriginPoint, this.TargetPoint)
this.pointList = this.pathResult:GetWaypoints()
if #this.pointList > 0 then
this.HumanoidOffsetFromPath = this.pointList[1].Position -
this.OriginPoint
end
this.PathComputed = this.pathResult.Status ==
Enum.PathStatus.Success
if ShowPath then
this.stopTraverseFunc, this.setPointFunc =
ClickToMoveDisplay.CreatePathDisplay(this.pointList)
end
if this.PathComputed then
this.CurrentPoint = 1 -- The first waypoint is always the
start location. Skip it.
this:OnPointReached(true) -- Move to first point
else
this.PathFailed:Fire()
this:Cleanup()
end
this.Recomputing = false
end
function this:OnRenderStepped(dt)
if this.Started and not this.Cancelled then
-- Check for Timeout (if a waypoint is not reached within
the delay, we fail)
this.Timeout = this.Timeout + dt
if this.Timeout > UnreachableWaypointTimeout then
this:OnPointReached(false)
return
end
function this:IsCurrentWaypointReached()
local reached = false
if reached then
this.CurrentWaypointPosition = nil
this.CurrentWaypointPlaneNormal = ZERO_VECTOR3
this.CurrentWaypointPlaneDistance = 0
end
return reached
end
function this:OnPointReached(reached)
if isInAir then
local shouldWaitForGround = nextWaypoint.Action
== Enum.PathWaypointAction.Jump
if not shouldWaitForGround and
this.CurrentPoint > 1 then
local prevWaypoint =
this.pointList[this.CurrentPoint - 1]
if shouldWaitForGround then
this.Humanoid.FreeFalling:Wait()
this.CurrentPoint = nextWaypointIdx
end
end
else
this.PathFailed:Fire()
this:Cleanup()
end
end
-- Should we jump
this.CurrentWaypointNeedsJump = nextWaypoint.Action ==
Enum.PathWaypointAction.Jump;
function this:Start(overrideShowPath)
if not this.AgentCanFollowPath then
this.PathFailed:Fire()
return
end
ClickToMoveDisplay.CancelFailureAnimation()
if ShowPath then
if overrideShowPath == nil or overrideShowPath then
this.stopTraverseFunc, this.setPointFunc =
ClickToMoveDisplay.CreatePathDisplay(this.pointList, this.OriginalTargetPoint)
end
end
-- Connect to events
this.SeatedConn =
this.Humanoid.Seated:Connect(function(isSeated, seat) this:OnPathInterrupted() end)
this.DiedConn = this.Humanoid.Died:Connect(function()
this:OnPathInterrupted() end)
this.TeleportedConn =
this.Humanoid.RootPart:GetPropertyChangedSignal("CFrame"):Connect(function()
this:OnPathInterrupted() end)
-- Actually start
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
--We always raycast to the ground in the case that the user clicked a
wall.
local offsetPoint = this.TargetPoint + this.TargetSurfaceNormal*1.5
local ray = Ray.new(offsetPoint, Vector3.new(0,-1,0)*50)
local newHitPart, newHitPos =
Workspace:FindPartOnRayWithIgnoreList(ray, getIgnoreList())
if newHitPart then
this.TargetPoint = newHitPos
end
this:ComputePath()
return this
end
-------------------------------------------------------------------------
PathCompleteListener = thisPather.Finished.Event:Connect(function()
CleanupPath()
if hitChar then
local currentWeapon = GetEquippedTool(character)
if currentWeapon then
currentWeapon:Activate()
end
end
end)
PathFailedListener = thisPather.PathFailed.Event:Connect(function()
CleanupPath()
if overrideShowPath == nil or overrideShowPath then
local shouldPlayFailureAnim = PlayFailureAnimation and not
(ExistingPather and ExistingPather:IsActive())
if shouldPlayFailureAnim then
ClickToMoveDisplay.PlayFailureAnimation()
end
ClickToMoveDisplay.DisplayFailureWaypoint(hitPt)
end
end)
end
function ClickToMove.new(CONTROL_ACTION_PRIORITY)
local self =
setmetatable(KeyboardController.new(CONTROL_ACTION_PRIORITY), ClickToMove)
self.fingerTouches = {}
self.numUnsunkTouches = 0
-- PC simulation
self.mouse1Down = tick()
self.mouse1DownPos = Vector2.new()
self.mouse2DownTime = tick()
self.mouse2DownPos = Vector2.new()
self.mouse2UpTime = tick()
self.keyboardMoveVector = ZERO_VECTOR3
self.tapConn = nil
self.inputBeganConn = nil
self.inputChangedConn = nil
self.inputEndedConn = nil
self.humanoidDiedConn = nil
self.characterChildAddedConn = nil
self.onCharacterAddedConn = nil
self.characterChildRemovedConn = nil
self.renderSteppedConn = nil
self.menuOpenedConnection = nil
self.running = false
self.wasdEnabled = false
return self
end
function ClickToMove:DisconnectEvents()
DisconnectEvent(self.tapConn)
DisconnectEvent(self.inputBeganConn)
DisconnectEvent(self.inputChangedConn)
DisconnectEvent(self.inputEndedConn)
DisconnectEvent(self.humanoidDiedConn)
DisconnectEvent(self.characterChildAddedConn)
DisconnectEvent(self.onCharacterAddedConn)
DisconnectEvent(self.renderSteppedConn)
DisconnectEvent(self.characterChildRemovedConn)
DisconnectEvent(self.menuOpenedConnection)
end
function ClickToMove:OnCharacterAdded(character)
self:DisconnectEvents()
self.inputBeganConn =
UserInputService.InputBegan:Connect(function(input, processed)
if input.UserInputType == Enum.UserInputType.Touch then
self:OnTouchBegan(input, processed)
end
self.inputChangedConn =
UserInputService.InputChanged:Connect(function(input, processed)
if input.UserInputType == Enum.UserInputType.Touch then
self:OnTouchChanged(input, processed)
end
end)
self.inputEndedConn =
UserInputService.InputEnded:Connect(function(input, processed)
if input.UserInputType == Enum.UserInputType.Touch then
self:OnTouchEnded(input, processed)
end
self.tapConn =
UserInputService.TouchTap:Connect(function(touchPositions, processed)
if not processed then
OnTap(touchPositions, nil, true)
end
end)
self.menuOpenedConnection = GuiService.MenuOpened:Connect(function()
CleanupPath()
end)
self.characterChildAddedConn =
character.ChildAdded:Connect(function(child)
OnCharacterChildAdded(child)
end)
self.characterChildRemovedConn =
character.ChildRemoved:Connect(function(child)
if UserInputService.TouchEnabled then
if child:IsA('Tool') then
child.ManualActivationOnly = false
end
end
end)
for _, child in pairs(character:GetChildren()) do
OnCharacterChildAdded(child)
end
end
function ClickToMove:Start()
self:Enable(true)
end
function ClickToMove:Stop()
self:Enable(false)
end
function ClickToMove:CleanupPath()
CleanupPath()
end
self.forwardValue = 0
self.backwardValue = 0
self.leftValue = 0
self.rightValue = 0
self.moveVector = ZERO_VECTOR3
if enable then
self:BindContextActions()
self:ConnectFocusEventListeners()
else
self:UnbindContextActions()
self:DisconnectFocusEventListeners()
end
end
function ClickToMove:OnRenderStepped(dt)
-- Reset jump
self.isJumping = false
-- Handle Pather
if ExistingPather then
-- Let the Pather update
ExistingPather:OnRenderStepped(dt)
function ClickToMove:GetShowPath()
return ShowPath
end
function ClickToMove:SetWaypointTexture(texture)
ClickToMoveDisplay.SetWaypointTexture(texture)
end
function ClickToMove:GetWaypointTexture()
return ClickToMoveDisplay.GetWaypointTexture()
end
function ClickToMove:SetWaypointRadius(radius)
ClickToMoveDisplay.SetWaypointRadius(radius)
end
function ClickToMove:GetWaypointRadius()
return ClickToMoveDisplay.GetWaypointRadius()
end
function ClickToMove:SetEndWaypointTexture(texture)
ClickToMoveDisplay.SetEndWaypointTexture(texture)
end
function ClickToMove:GetEndWaypointTexture()
return ClickToMoveDisplay.GetEndWaypointTexture()
end
function ClickToMove:SetWaypointsAlwaysOnTop(alwaysOnTop)
ClickToMoveDisplay.SetWaypointsAlwaysOnTop(alwaysOnTop)
end
function ClickToMove:GetWaypointsAlwaysOnTop()
return ClickToMoveDisplay.GetWaypointsAlwaysOnTop()
end
function ClickToMove:SetFailureAnimationEnabled(enabled)
PlayFailureAnimation = enabled
end
function ClickToMove:GetFailureAnimationEnabled()
return PlayFailureAnimation
end
function ClickToMove:SetIgnoredPartsTag(tag)
UpdateIgnoreTag(tag)
end
function ClickToMove:GetIgnoredPartsTag()
return CurrentIgnoreTag
end
function ClickToMove:SetUseDirectPath(directPath)
UseDirectPath = directPath
end
function ClickToMove:GetUseDirectPath()
return UseDirectPath
end
function ClickToMove:SetAgentSizeIncreaseFactor(increaseFactorPercent)
AgentSizeIncreaseFactor = 1.0 + (increaseFactorPercent / 100.0)
end
function ClickToMove:GetAgentSizeIncreaseFactor()
return (AgentSizeIncreaseFactor - 1.0) * 100.0
end
function ClickToMove:SetUnreachableWaypointTimeout(timeoutInSec)
UnreachableWaypointTimeout = timeoutInSec
end
function ClickToMove:GetUnreachableWaypointTimeout()
return UnreachableWaypointTimeout
end
function ClickToMove:SetUserJumpEnabled(jumpEnabled)
self.jumpEnabled = jumpEnabled
if self.touchJumpController then
self.touchJumpController:Enable(jumpEnabled)
end
end
function ClickToMove:GetUserJumpEnabled()
return self.jumpEnabled
end
return ClickToMove
end
function _TouchThumbstick()
local Players = game:GetService("Players")
local GuiService = game:GetService("GuiService")
local UserInputService = game:GetService("UserInputService")
--[[ Constants ]]--
local ZERO_VECTOR3 = Vector3.new(0,0,0)
local TOUCH_CONTROL_SHEET = "rbxasset://textures/ui/TouchControlsSheet.png"
--[[ The Module ]]--
local BaseCharacterController = _BaseCharacterController()
local TouchThumbstick = setmetatable({}, BaseCharacterController)
TouchThumbstick.__index = TouchThumbstick
function TouchThumbstick.new()
local self = setmetatable(BaseCharacterController.new(),
TouchThumbstick)
self.isFollowStick = false
self.thumbstickFrame = nil
self.moveTouchObject = nil
self.onTouchMovedConn = nil
self.onTouchEndedConn = nil
self.screenPos = nil
self.stickImage = nil
self.thumbstickSize = nil -- Float
return self
end
function TouchThumbstick:Enable(enable, uiParentFrame)
if enable == nil then return false end -- If nil, return
false (invalid argument)
enable = enable and true or false -- Force anything
non-nil to boolean before comparison
if self.enabled == enable then return true end -- If no state change,
return true indicating already in requested state
self.moveVector = ZERO_VECTOR3
self.isJumping = false
if enable then
-- Enable
if not self.thumbstickFrame then
self:Create(uiParentFrame)
end
self.thumbstickFrame.Visible = true
else
-- Disable
self.thumbstickFrame.Visible = false
self:OnInputEnded()
end
self.enabled = enable
end
function TouchThumbstick:OnInputEnded()
self.thumbstickFrame.Position = self.screenPos
self.stickImage.Position = UDim2.new(0,
self.thumbstickFrame.Size.X.Offset/2 - self.thumbstickSize/4, 0,
self.thumbstickFrame.Size.Y.Offset/2 - self.thumbstickSize/4)
self.moveVector = ZERO_VECTOR3
self.isJumping = false
self.thumbstickFrame.Position = self.screenPos
self.moveTouchObject = nil
end
function TouchThumbstick:Create(parentFrame)
if self.thumbstickFrame then
self.thumbstickFrame:Destroy()
self.thumbstickFrame = nil
if self.onTouchMovedConn then
self.onTouchMovedConn:Disconnect()
self.onTouchMovedConn = nil
end
if self.onTouchEndedConn then
self.onTouchEndedConn:Disconnect()
self.onTouchEndedConn = nil
end
end
self.thumbstickFrame = Instance.new("Frame")
self.thumbstickFrame.Name = "ThumbstickFrame"
self.thumbstickFrame.Active = true
self.thumbstickFrame.Visible = false
self.thumbstickFrame.Size = UDim2.new(0, self.thumbstickSize, 0,
self.thumbstickSize)
self.thumbstickFrame.Position = self.screenPos
self.thumbstickFrame.BackgroundTransparency = 1
self.stickImage = Instance.new("ImageLabel")
self.stickImage.Name = "StickImage"
self.stickImage.Image = TOUCH_CONTROL_SHEET
self.stickImage.ImageRectOffset = Vector2.new(220, 0)
self.stickImage.ImageRectSize = Vector2.new(111, 111)
self.stickImage.BackgroundTransparency = 1
self.stickImage.Size = UDim2.new(0, self.thumbstickSize/2, 0,
self.thumbstickSize/2)
self.stickImage.Position = UDim2.new(0, self.thumbstickSize/2 -
self.thumbstickSize/4, 0, self.thumbstickSize/2 - self.thumbstickSize/4)
self.stickImage.ZIndex = 2
self.stickImage.Parent = self.thumbstickFrame
self.moveVector = currentMoveVector
end
-- input connections
self.thumbstickFrame.InputBegan:Connect(function(inputObject)
--A touch that starts elsewhere on the screen will be sent to a
frame's InputBegan event
--if it moves over the frame. So we check that this is actually a
new touch (inputObject.UserInputState ~= Enum.UserInputState.Begin)
if self.moveTouchObject or inputObject.UserInputType ~=
Enum.UserInputType.Touch
or inputObject.UserInputState ~= Enum.UserInputState.Begin
then
return
end
self.moveTouchObject = inputObject
self.thumbstickFrame.Position = UDim2.new(0,
inputObject.Position.x - self.thumbstickFrame.Size.X.Offset/2, 0,
inputObject.Position.y - self.thumbstickFrame.Size.Y.Offset/2)
centerPosition =
Vector2.new(self.thumbstickFrame.AbsolutePosition.x +
self.thumbstickFrame.AbsoluteSize.x/2,
self.thumbstickFrame.AbsolutePosition.y +
self.thumbstickFrame.AbsoluteSize.y/2)
local direction = Vector2.new(inputObject.Position.x -
centerPosition.x, inputObject.Position.y - centerPosition.y)
end)
self.onTouchMovedConn =
UserInputService.TouchMoved:Connect(function(inputObject, isProcessed)
if inputObject == self.moveTouchObject then
centerPosition =
Vector2.new(self.thumbstickFrame.AbsolutePosition.x +
self.thumbstickFrame.AbsoluteSize.x/2,
self.thumbstickFrame.AbsolutePosition.y +
self.thumbstickFrame.AbsoluteSize.y/2)
local direction = Vector2.new(inputObject.Position.x -
centerPosition.x, inputObject.Position.y - centerPosition.y)
DoMove(direction)
MoveStick(inputObject.Position)
end
end)
self.onTouchEndedConn =
UserInputService.TouchEnded:Connect(function(inputObject, isProcessed)
if inputObject == self.moveTouchObject then
self:OnInputEnded()
end
end)
GuiService.MenuOpened:Connect(function()
if self.moveTouchObject then
self:OnInputEnded()
end
end)
self.thumbstickFrame.Parent = parentFrame
end
return TouchThumbstick
end
function _DynamicThumbstick()
local ZERO_VECTOR3 = Vector3.new(0,0,0)
local TOUCH_CONTROLS_SHEET =
"rbxasset://textures/ui/Input/TouchControlsSheetV2.png"
local MIDDLE_TRANSPARENCIES = {
1 - 0.89,
1 - 0.70,
1 - 0.60,
1 - 0.50,
1 - 0.40,
1 - 0.30,
1 - 0.25
}
local NUM_MIDDLE_IMAGES = #MIDDLE_TRANSPARENCIES
function DynamicThumbstick.new()
local self = setmetatable(BaseCharacterController.new(),
DynamicThumbstick)
self.moveTouchObject = nil
self.moveTouchLockedIn = false
self.moveTouchFirstChanged = false
self.moveTouchStartPosition = nil
self.startImage = nil
self.endImage = nil
self.middleImages = {}
self.startImageFadeTween = nil
self.endImageFadeTween = nil
self.middleImageFadeTweens = {}
self.isFirstTouch = true
self.thumbstickFrame = nil
self.onRenderSteppedConn = nil
self.fadeInAndOutBalance = FADE_IN_OUT_BALANCE_DEFAULT
self.fadeInAndOutHalfDuration = FADE_IN_OUT_HALF_DURATION_DEFAULT
self.hasFadedBackgroundInPortrait = false
self.hasFadedBackgroundInLandscape = false
self.tweenInAlphaStart = nil
self.tweenOutAlphaStart = nil
return self
end
if enable then
-- Enable
if not self.thumbstickFrame then
self:Create(uiParentFrame)
end
self:BindContextActions()
else
ContextActionService:UnbindAction(DYNAMIC_THUMBSTICK_ACTION_NAME)
-- Disable
self:OnInputEnded() -- Cleanup
end
self.enabled = enable
self.thumbstickFrame.Visible = enable
end
function DynamicThumbstick:FadeThumbstick(visible)
if not visible and self.moveTouchObject then
return
end
if self.isFirstTouch then return end
if self.startImageFadeTween then
self.startImageFadeTween:Cancel()
end
if self.endImageFadeTween then
self.endImageFadeTween:Cancel()
end
for i = 1, #self.middleImages do
if self.middleImageFadeTweens[i] then
self.middleImageFadeTweens[i]:Cancel()
end
end
if visible then
self.startImageFadeTween = TweenService:Create(self.startImage,
ThumbstickFadeTweenInfo, { ImageTransparency = 0 })
self.startImageFadeTween:Play()
self.endImageFadeTween = TweenService:Create(self.endImage,
ThumbstickFadeTweenInfo, { ImageTransparency = 0.2 })
self.endImageFadeTween:Play()
for i = 1, #self.middleImages do
self.middleImageFadeTweens[i] =
TweenService:Create(self.middleImages[i], ThumbstickFadeTweenInfo,
{ ImageTransparency = MIDDLE_TRANSPARENCIES[i] })
self.middleImageFadeTweens[i]:Play()
end
else
self.startImageFadeTween = TweenService:Create(self.startImage,
ThumbstickFadeTweenInfo, { ImageTransparency = 1 })
self.startImageFadeTween:Play()
self.endImageFadeTween = TweenService:Create(self.endImage,
ThumbstickFadeTweenInfo, { ImageTransparency = 1 })
self.endImageFadeTween:Play()
for i = 1, #self.middleImages do
self.middleImageFadeTweens[i] =
TweenService:Create(self.middleImages[i], ThumbstickFadeTweenInfo,
{ ImageTransparency = 1 })
self.middleImageFadeTweens[i]:Play()
end
end
end
function DynamicThumbstick:InputInFrame(inputObject)
local frameCornerTopLeft = self.thumbstickFrame.AbsolutePosition
local frameCornerBottomRight = frameCornerTopLeft +
self.thumbstickFrame.AbsoluteSize
local inputPosition = inputObject.Position
if inputPosition.X >= frameCornerTopLeft.X and inputPosition.Y >=
frameCornerTopLeft.Y then
if inputPosition.X <= frameCornerBottomRight.X and
inputPosition.Y <= frameCornerBottomRight.Y then
return true
end
end
return false
end
function DynamicThumbstick:DoFadeInBackground()
local playerGui = LocalPlayer:FindFirstChildOfClass("PlayerGui")
local hasFadedBackgroundInOrientation = false
function DynamicThumbstick:DoMove(direction)
local currentMoveVector = direction
self.moveVector = currentMoveVector
end
for i = 1, NUM_MIDDLE_IMAGES do
local image = self.middleImages[i]
local distWithout = startDist + (spacing * (i - 2))
local currentDist = startDist + (spacing * (i - 1))
image.Visible = true
image.Position = UDim2.new(0, pos.X, 0, pos.Y)
image.Size = UDim2.new(0, self.middleSize *
exposedFraction, 0, self.middleSize * exposedFraction)
else
image.Visible = false
end
end
end
function DynamicThumbstick:MoveStick(pos)
local vector2StartPosition = Vector2.new(self.moveTouchStartPosition.X,
self.moveTouchStartPosition.Y)
local startPos = vector2StartPosition -
self.thumbstickFrame.AbsolutePosition
local endPos = Vector2.new(pos.X, pos.Y) -
self.thumbstickFrame.AbsolutePosition
self.endImage.Position = UDim2.new(0, endPos.X, 0, endPos.Y)
self:LayoutMiddleImages(startPos, endPos)
end
function DynamicThumbstick:BindContextActions()
local function inputBegan(inputObject)
if self.moveTouchObject then
return Enum.ContextActionResult.Pass
end
if self.isFirstTouch then
self.isFirstTouch = false
local tweenInfo = TweenInfo.new(0.5, Enum.EasingStyle.Quad,
Enum.EasingDirection.Out,0,false,0)
TweenService:Create(self.startImage, tweenInfo, {Size =
UDim2.new(0, 0, 0, 0)}):Play()
TweenService:Create(
self.endImage,
tweenInfo,
{Size = UDim2.new(0, self.thumbstickSize, 0,
self.thumbstickSize), ImageColor3 = Color3.new(0,0,0)}
):Play()
end
self.moveTouchLockedIn = false
self.moveTouchObject = inputObject
self.moveTouchStartPosition = inputObject.Position
self.moveTouchFirstChanged = true
if FADE_IN_OUT_BACKGROUND then
self:DoFadeInBackground()
end
return Enum.ContextActionResult.Pass
end
self:FadeThumbstick(true)
self:MoveStick(inputObject.Position)
end
self.moveTouchLockedIn = true
ContextActionService:BindActionAtPriority(
DYNAMIC_THUMBSTICK_ACTION_NAME,
handleInput,
false,
DYNAMIC_THUMBSTICK_ACTION_PRIORITY,
Enum.UserInputType.Touch)
end
function DynamicThumbstick:Create(parentFrame)
if self.thumbstickFrame then
self.thumbstickFrame:Destroy()
self.thumbstickFrame = nil
if self.onRenderSteppedConn then
self.onRenderSteppedConn:Disconnect()
self.onRenderSteppedConn = nil
end
end
self.thumbstickSize = 45
self.thumbstickRingSize = 20
self.middleSize = 10
self.middleSpacing = self.middleSize + 4
self.radiusOfDeadZone = 2
self.radiusOfMaxSpeed = 20
self.thumbstickFrame = Instance.new("Frame")
self.thumbstickFrame.BorderSizePixel = 0
self.thumbstickFrame.Name = "DynamicThumbstickFrame"
self.thumbstickFrame.Visible = false
self.thumbstickFrame.BackgroundTransparency = 1.0
self.thumbstickFrame.BackgroundColor3 = Color3.fromRGB(0, 0, 0)
self.thumbstickFrame.Active = false
layoutThumbstickFrame(false)
self.startImage = Instance.new("ImageLabel")
self.startImage.Name = "ThumbstickStart"
self.startImage.Visible = true
self.startImage.BackgroundTransparency = 1
self.startImage.Image = TOUCH_CONTROLS_SHEET
self.startImage.ImageRectOffset = Vector2.new(1,1)
self.startImage.ImageRectSize = Vector2.new(144, 144)
self.startImage.ImageColor3 = Color3.new(0, 0, 0)
self.startImage.AnchorPoint = Vector2.new(0.5, 0.5)
self.startImage.Position = UDim2.new(0, self.thumbstickRingSize * 3.3,
1, -self.thumbstickRingSize * 2.8)
self.startImage.Size = UDim2.new(0, self.thumbstickRingSize * 3.7, 0,
self.thumbstickRingSize * 3.7)
self.startImage.ZIndex = 10
self.startImage.Parent = self.thumbstickFrame
self.endImage = Instance.new("ImageLabel")
self.endImage.Name = "ThumbstickEnd"
self.endImage.Visible = true
self.endImage.BackgroundTransparency = 1
self.endImage.Image = TOUCH_CONTROLS_SHEET
self.endImage.ImageRectOffset = Vector2.new(1,1)
self.endImage.ImageRectSize = Vector2.new(144, 144)
self.endImage.AnchorPoint = Vector2.new(0.5, 0.5)
self.endImage.Position = self.startImage.Position
self.endImage.Size = UDim2.new(0, self.thumbstickSize * 0.8, 0,
self.thumbstickSize * 0.8)
self.endImage.ZIndex = 10
self.endImage.Parent = self.thumbstickFrame
for i = 1, NUM_MIDDLE_IMAGES do
self.middleImages[i] = Instance.new("ImageLabel")
self.middleImages[i].Name = "ThumbstickMiddle"
self.middleImages[i].Visible = false
self.middleImages[i].BackgroundTransparency = 1
self.middleImages[i].Image = TOUCH_CONTROLS_SHEET
self.middleImages[i].ImageRectOffset = Vector2.new(1,1)
self.middleImages[i].ImageRectSize = Vector2.new(144, 144)
self.middleImages[i].ImageTransparency = MIDDLE_TRANSPARENCIES[i]
self.middleImages[i].AnchorPoint = Vector2.new(0.5, 0.5)
self.middleImages[i].ZIndex = 9
self.middleImages[i].Parent = self.thumbstickFrame
end
workspace:GetPropertyChangedSignal("CurrentCamera"):Connect(onCurrentCameraChanged)
if workspace.CurrentCamera then
onCurrentCameraChanged()
end
self.moveTouchStartPosition = nil
self.startImageFadeTween = nil
self.endImageFadeTween = nil
self.middleImageFadeTweens = {}
self.onRenderSteppedConn = RunService.RenderStepped:Connect(function()
if self.tweenInAlphaStart ~= nil then
local delta = tick() - self.tweenInAlphaStart
local fadeInTime = (self.fadeInAndOutHalfDuration * 2 *
self.fadeInAndOutBalance)
self.thumbstickFrame.BackgroundTransparency = 1 -
FADE_IN_OUT_MAX_ALPHA*math.min(delta/fadeInTime, 1)
if delta > fadeInTime then
self.tweenOutAlphaStart = tick()
self.tweenInAlphaStart = nil
end
elseif self.tweenOutAlphaStart ~= nil then
local delta = tick() - self.tweenOutAlphaStart
local fadeOutTime = (self.fadeInAndOutHalfDuration * 2) -
(self.fadeInAndOutHalfDuration * 2 * self.fadeInAndOutBalance)
self.thumbstickFrame.BackgroundTransparency = 1 -
FADE_IN_OUT_MAX_ALPHA + FADE_IN_OUT_MAX_ALPHA*math.min(delta/fadeOutTime, 1)
if delta > fadeOutTime then
self.tweenOutAlphaStart = nil
end
end
end)
self.onTouchEndedConn =
UserInputService.TouchEnded:connect(function(inputObject)
if inputObject == self.moveTouchObject then
self:OnInputEnded()
end
end)
GuiService.MenuOpened:connect(function()
if self.moveTouchObject then
self:OnInputEnded()
end
end)
playerGui.CurrentScreenOrientation == Enum.ScreenOrientation.LandscapeRight
playerGuiChangedConn =
playerGui:GetPropertyChangedSignal("CurrentScreenOrientation"):Connect(function()
if (originalScreenOrientationWasLandscape and
playerGui.CurrentScreenOrientation == Enum.ScreenOrientation.Portrait) or
(not originalScreenOrientationWasLandscape and
playerGui.CurrentScreenOrientation ~= Enum.ScreenOrientation.Portrait) then
playerGuiChangedConn:disconnect()
longShowBackground()
if originalScreenOrientationWasLandscape then
self.hasFadedBackgroundInPortrait = true
else
self.hasFadedBackgroundInLandscape = true
end
end
end)
self.thumbstickFrame.Parent = parentFrame
if game:IsLoaded() then
longShowBackground()
else
coroutine.wrap(function()
game.Loaded:Wait()
longShowBackground()
end)()
end
end
return DynamicThumbstick
end
function _Gamepad()
local UserInputService = game:GetService("UserInputService")
local ContextActionService = game:GetService("ContextActionService")
function Gamepad.new(CONTROL_ACTION_PRIORITY)
local self = setmetatable(BaseCharacterController.new(), Gamepad)
self.CONTROL_ACTION_PRIORITY = CONTROL_ACTION_PRIORITY
self.forwardValue = 0
self.backwardValue = 0
self.leftValue = 0
self.rightValue = 0
function Gamepad:Enable(enable)
if not UserInputService.GamepadEnabled then
return false
end
self.forwardValue = 0
self.backwardValue = 0
self.leftValue = 0
self.rightValue = 0
self.moveVector = ZERO_VECTOR3
self.isJumping = false
if enable then
self.activeGamepad = self:GetHighestPriorityGamepad()
if self.activeGamepad ~= NONE then
self:BindContextActions()
self:ConnectGamepadConnectionListeners()
else
-- No connected gamepads, failure to enable
return false
end
else
self:UnbindContextActions()
self:DisconnectGamepadConnectionListeners()
self.activeGamepad = NONE
end
self.enabled = enable
return true
end
-- This function selects the lowest number gamepad from the currently-
connected gamepad
-- and sets it as the active gamepad
function Gamepad:GetHighestPriorityGamepad()
local connectedGamepads = UserInputService:GetConnectedGamepads()
local bestGamepad = NONE -- Note that this value is higher than all
valid gamepad values
for _, gamepad in pairs(connectedGamepads) do
if gamepad.Value < bestGamepad.Value then
bestGamepad = gamepad
end
end
return bestGamepad
end
function Gamepad:BindContextActions()
ContextActionService:BindActivate(self.activeGamepad,
Enum.KeyCode.ButtonR2)
ContextActionService:BindActionAtPriority("jumpAction",
handleJumpAction, false,
self.CONTROL_ACTION_PRIORITY, Enum.KeyCode.ButtonA)
ContextActionService:BindActionAtPriority("moveThumbstick",
handleThumbstickInput, false,
self.CONTROL_ACTION_PRIORITY, Enum.KeyCode.Thumbstick1)
return true
end
function Gamepad:UnbindContextActions()
if self.activeGamepad ~= NONE then
ContextActionService:UnbindActivate(self.activeGamepad,
Enum.KeyCode.ButtonR2)
end
ContextActionService:UnbindAction("moveThumbstick")
ContextActionService:UnbindAction("jumpAction")
end
function Gamepad:OnNewGamepadConnected()
-- A new gamepad has been connected.
local bestGamepad = self:GetHighestPriorityGamepad()
self.activeGamepad = bestGamepad
self:BindContextActions()
end
function Gamepad:OnCurrentGamepadDisconnected()
if self.activeGamepad ~= NONE then
ContextActionService:UnbindActivate(self.activeGamepad,
Enum.KeyCode.ButtonR2)
end
function Gamepad:ConnectGamepadConnectionListeners()
self.gamepadConnectedConn =
UserInputService.GamepadConnected:Connect(function(gamepadEnum)
self:OnNewGamepadConnected()
end)
self.gamepadDisconnectedConn =
UserInputService.GamepadDisconnected:Connect(function(gamepadEnum)
if self.activeGamepad == gamepadEnum then
self:OnCurrentGamepadDisconnected()
end
end)
end
function Gamepad:DisconnectGamepadConnectionListeners()
if self.gamepadConnectedConn then
self.gamepadConnectedConn:Disconnect()
self.gamepadConnectedConn = nil
end
if self.gamepadDisconnectedConn then
self.gamepadDisconnectedConn:Disconnect()
self.gamepadDisconnectedConn = nil
end
end
return Gamepad
end
function _Keyboard()
function Keyboard.new(CONTROL_ACTION_PRIORITY)
local self = setmetatable(BaseCharacterController.new(), Keyboard)
self.CONTROL_ACTION_PRIORITY = CONTROL_ACTION_PRIORITY
self.textFocusReleasedConn = nil
self.textFocusGainedConn = nil
self.windowFocusReleasedConn = nil
self.forwardValue = 0
self.backwardValue = 0
self.leftValue = 0
self.rightValue = 0
self.jumpEnabled = true
return self
end
function Keyboard:Enable(enable)
if not UserInputService.KeyboardEnabled then
return false
end
self.forwardValue = 0
self.backwardValue = 0
self.leftValue = 0
self.rightValue = 0
self.moveVector = ZERO_VECTOR3
self.jumpRequested = false
self:UpdateJump()
if enable then
self:BindContextActions()
self:ConnectFocusEventListeners()
else
self:UnbindContextActions()
self:DisconnectFocusEventListeners()
end
self.enabled = enable
return true
end
function Keyboard:UpdateMovement(inputState)
if inputState == Enum.UserInputState.Cancel then
self.moveVector = ZERO_VECTOR3
else
self.moveVector = Vector3.new(self.leftValue + self.rightValue,
0, self.forwardValue + self.backwardValue)
end
end
function Keyboard:UpdateJump()
self.isJumping = self.jumpRequested
end
function Keyboard:BindContextActions()
-- Note: In the previous version of this code, the movement values were
not zeroed-out on UserInputState. Cancel, now they are,
-- which fixes them from getting stuck on.
-- We return ContextActionResult.Pass here for legacy reasons.
-- Many games rely on gameProcessedEvent being false on
UserInputService.InputBegan for these control actions.
local handleMoveForward = function(actionName, inputState, inputObject)
self.forwardValue = (inputState == Enum.UserInputState.Begin) and
-1 or 0
self:UpdateMovement(inputState)
return Enum.ContextActionResult.Pass
end
function Keyboard:UnbindContextActions()
ContextActionService:UnbindAction("moveForwardAction")
ContextActionService:UnbindAction("moveBackwardAction")
ContextActionService:UnbindAction("moveLeftAction")
ContextActionService:UnbindAction("moveRightAction")
ContextActionService:UnbindAction("jumpAction")
end
function Keyboard:ConnectFocusEventListeners()
local function onFocusReleased()
self.moveVector = ZERO_VECTOR3
self.forwardValue = 0
self.backwardValue = 0
self.leftValue = 0
self.rightValue = 0
self.jumpRequested = false
self:UpdateJump()
end
self.textFocusReleasedConn =
UserInputService.TextBoxFocusReleased:Connect(onFocusReleased)
self.textFocusGainedConn =
UserInputService.TextBoxFocused:Connect(onTextFocusGained)
self.windowFocusReleasedConn =
UserInputService.WindowFocused:Connect(onFocusReleased)
end
function Keyboard:DisconnectFocusEventListeners()
if self.textFocusReleasedCon then
self.textFocusReleasedCon:Disconnect()
self.textFocusReleasedCon = nil
end
if self.textFocusGainedConn then
self.textFocusGainedConn:Disconnect()
self.textFocusGainedConn = nil
end
if self.windowFocusReleasedConn then
self.windowFocusReleasedConn:Disconnect()
self.windowFocusReleasedConn = nil
end
end
return Keyboard
end
function _ControlModule()
local ControlModule = {}
ControlModule.__index = ControlModule
--[[ Roblox Services ]]--
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local UserInputService = game:GetService("UserInputService")
local Workspace = game:GetService("Workspace")
local UserGameSettings = UserSettings():GetService("UserGameSettings")
local FFlagUserMakeThumbstickDynamic do
local success, value = pcall(function()
return
UserSettings():IsUserFeatureEnabled("UserMakeThumbstickDynamic")
end)
FFlagUserMakeThumbstickDynamic = success and value
end
-- Current default
[Enum.TouchMovementMode.Default] = DynamicThumbstick,
[Enum.ComputerMovementMode.Default] = Keyboard,
[Enum.ComputerMovementMode.KeyboardMouse] = Keyboard,
[Enum.DevComputerMovementMode.KeyboardMouse] = Keyboard,
[Enum.DevComputerMovementMode.Scriptable] = nil,
[Enum.ComputerMovementMode.ClickToMove] = ClickToMove,
[Enum.DevComputerMovementMode.ClickToMove] = ClickToMove,
}
local lastInputType
function ControlModule.new()
local self = setmetatable({},ControlModule)
self.touchControlFrame = nil
self.vehicleController = VehicleController.new(CONTROL_ACTION_PRIORITY)
Players.LocalPlayer.CharacterAdded:Connect(function(char)
self:OnCharacterAdded(char) end)
Players.LocalPlayer.CharacterRemoving:Connect(function(char)
self:OnCharacterRemoving(char) end)
if Players.LocalPlayer.Character then
self:OnCharacterAdded(Players.LocalPlayer.Character)
end
RunService:BindToRenderStep("ControlScriptRenderstep",
Enum.RenderPriority.Input.Value, function(dt)
self:OnRenderStepped(dt)
end)
UserInputService.LastInputTypeChanged:Connect(function(newLastInputType)
self:OnLastInputTypeChanged(newLastInputType)
end)
UserGameSettings:GetPropertyChangedSignal("TouchMovementMode"):Connect(function()
self:OnTouchMovementModeChange()
end)
Players.LocalPlayer:GetPropertyChangedSignal("DevTouchMovementMode"):Connect(functi
on()
self:OnTouchMovementModeChange()
end)
UserGameSettings:GetPropertyChangedSignal("ComputerMovementMode"):Connect(function(
)
self:OnComputerMovementModeChange()
end)
Players.LocalPlayer:GetPropertyChangedSignal("DevComputerMovementMode"):Connect(fun
ction()
self:OnComputerMovementModeChange()
end)
if UserInputService.TouchEnabled then
self.playerGui =
Players.LocalPlayer:FindFirstChildOfClass("PlayerGui")
if self.playerGui then
self:CreateTouchGuiContainer()
self:OnLastInputTypeChanged(UserInputService:GetLastInputType())
else
self.playerGuiAddedConn =
Players.LocalPlayer.ChildAdded:Connect(function(child)
if child:IsA("PlayerGui") then
self.playerGui = child
self:CreateTouchGuiContainer()
self.playerGuiAddedConn:Disconnect()
self.playerGuiAddedConn = nil
self:OnLastInputTypeChanged(UserInputService:GetLastInputType())
end
end)
end
else
self:OnLastInputTypeChanged(UserInputService:GetLastInputType())
end
return self
end
-- Convenience function so that calling code does not have to first get the
activeController
-- and then call GetMoveVector on it. When there is no active controller,
this function returns
-- nil so that this case can be distinguished from no current movement (which
returns zero vector).
function ControlModule:GetMoveVector()
if self.activeController then
return self.activeController:GetMoveVector()
end
return Vector3.new(0,0,0)
end
function ControlModule:GetActiveController()
return self.activeController
end
function ControlModule:EnableActiveControlModule()
if self.activeControlModule == ClickToMove then
-- For ClickToMove, when it is the player's choice, we also
enable the full keyboard controls.
-- When the developer is forcing click to move, the most keyboard
controls (WASD) are not available, only jump.
self.activeController:Enable(
true,
Players.LocalPlayer.DevComputerMovementMode ==
Enum.DevComputerMovementMode.UserChoice,
self.touchJumpController
)
elseif self.touchControlFrame then
self.activeController:Enable(true, self.touchControlFrame)
else
self.activeController:Enable(true)
end
end
function ControlModule:Enable(enable)
if not self.activeController then
return
end
if self.moveFunction then
self.moveFunction(Players.LocalPlayer, Vector3.new(0,0,0),
true)
end
end
end
-- Returns module (possibly nil) and success code to differentiate returning
nil due to error vs Scriptable
function ControlModule:SelectComputerMovementModule()
if not (UserInputService.KeyboardEnabled or
UserInputService.GamepadEnabled) then
return nil, false
end
local computerModule
local DevMovementMode = Players.LocalPlayer.DevComputerMovementMode
if computerModule then
return computerModule, true
elseif DevMovementMode == Enum.DevComputerMovementMode.Scriptable then
-- Special case where nil is returned and we actually want to set
self.activeController to nil for Scriptable
return nil, true
else
-- This case is for when computerModule is nil because of an
error and no suitable control module could
-- be found.
return nil, false
end
end
local c, s
local _, _, _, R00, R01, R02, _, _, R12, _, _, R22 =
camera.CFrame:GetComponents()
if R12 < 1 and R12 > -1 then
-- X and Z components from back vector.
c = R22
s = R02
else
-- In this case the camera is looking straight up or straight
down.
-- Use X components from right and up vectors.
c = R00
s = -R01*math.sign(R12)
end
local norm = math.sqrt(c*c + s*s)
return Vector3.new(
(c*cameraRelativeMoveVector.x +
s*cameraRelativeMoveVector.z)/norm,
0,
(c*cameraRelativeMoveVector.z -
s*cameraRelativeMoveVector.x)/norm
)
end
function ControlModule:OnRenderStepped(dt)
if self.activeController and self.activeController.enabled and
self.humanoid then
-- Give the controller a chance to adjust its state
self.activeController:OnRenderStepped(dt)
function ControlModule:OnCharacterAdded(char)
self.humanoid = char:FindFirstChildOfClass("Humanoid")
while not self.humanoid do
char.ChildAdded:wait()
self.humanoid = char:FindFirstChildOfClass("Humanoid")
end
if self.touchGui then
self.touchGui.Enabled = true
end
if self.humanoidSeatedConn then
self.humanoidSeatedConn:Disconnect()
self.humanoidSeatedConn = nil
end
self.humanoidSeatedConn = self.humanoid.Seated:Connect(function(active,
currentSeatPart)
self:OnHumanoidSeated(active, currentSeatPart)
end)
end
function ControlModule:OnCharacterRemoving(char)
self.humanoid = nil
if self.touchGui then
self.touchGui.Enabled = false
end
end
self:EnableActiveControlModule()
end
end
end
function ControlModule:OnLastInputTypeChanged(newLastInputType)
if lastInputType == newLastInputType then
warn("LastInputType Change listener called with current type.")
end
lastInputType = newLastInputType
function ControlModule:OnTouchMovementModeChange()
local touchModule, success = self:SelectTouchModule()
if success then
while not self.touchControlFrame do
wait()
end
self:SwitchToController(touchModule)
end
end
function ControlModule:CreateTouchGuiContainer()
if self.touchGui then self.touchGui:Destroy() end
self.touchControlFrame = Instance.new("Frame")
self.touchControlFrame.Name = "TouchControlFrame"
self.touchControlFrame.Size = UDim2.new(1, 0, 1, 0)
self.touchControlFrame.BackgroundTransparency = 1
self.touchControlFrame.Parent = self.touchGui
self.touchGui.Parent = self.playerGui
end
function ControlModule:GetClickToMoveController()
if not self.controllers[ClickToMove] then
self.controllers[ClickToMove] =
ClickToMove.new(CONTROL_ACTION_PRIORITY)
end
return self.controllers[ClickToMove]
end
function ControlModule:IsJumping()
if self.activeController then
return self.activeController:GetIsJumping() or
(self.touchJumpController and self.touchJumpController:GetIsJumping())
end
return false
end
return ControlModule.new()
end
function _PlayerModule()
local PlayerModule = {}
PlayerModule.__index = PlayerModule
function PlayerModule.new()
local self = setmetatable({},PlayerModule)
self.cameras = _CameraModule()
self.controls = _ControlModule()
return self
end
function PlayerModule:GetCameras()
return self.cameras
end
function PlayerModule:GetControls()
return self.controls
end
function PlayerModule:GetClickToMoveController()
return self.controls:GetClickToMoveController()
end
return PlayerModule.new()
end
function _sounds()
local SetState = Instance.new("BindableEvent",script)
local SOUND_DATA = {
Climbing = {
SoundId = "rbxasset://sounds/action_footsteps_plastic.mp3",
Looped = true,
},
Died = {
SoundId = "rbxasset://sounds/uuhhh.mp3",
},
FreeFalling = {
SoundId = "rbxasset://sounds/action_falling.mp3",
Looped = true,
},
GettingUp = {
SoundId = "rbxasset://sounds/action_get_up.mp3",
},
Jumping = {
SoundId = "rbxasset://sounds/action_jump.mp3",
},
Landing = {
SoundId = "rbxasset://sounds/action_jump_land.mp3",
},
Running = {
SoundId = "rbxasset://sounds/action_footsteps_plastic.mp3",
Looped = true,
Pitch = 1.85,
},
Splash = {
SoundId = "rbxasset://sounds/impact_water.mp3",
},
Swimming = {
SoundId = "rbxasset://sounds/action_swim.mp3",
Looped = true,
Pitch = 1.6,
},
}
return shunt:Fire(...)
end
for i = 1, #slots do
slots[i] = slots[i]:Connect(fire)
end
return shunt.Event:Wait()
end
-- initialize sounds
for name, props in pairs(SOUND_DATA) do
local sound = Instance.new("Sound")
sound.Name = name
sound.Parent = rootPart
sounds[name] = sound
end
local playingLoopedSounds = {}
[Enum.HumanoidStateType.GettingUp] = function()
stopPlayingLoopedSounds()
playSound(sounds.GettingUp)
end,
[Enum.HumanoidStateType.Jumping] = function()
stopPlayingLoopedSounds()
playSound(sounds.Jumping)
end,
[Enum.HumanoidStateType.Swimming] = function()
local verticalSpeed = math.abs(rootPart.Velocity.Y)
if verticalSpeed > 0.1 then
sounds.Splash.Volume = math.clamp(map(verticalSpeed,
100, 350, 0.28, 1), 0, 1)
playSound(sounds.Splash)
end
stopPlayingLoopedSounds(sounds.Swimming)
sounds.Swimming.Playing = true
playingLoopedSounds[sounds.Swimming] = true
end,
[Enum.HumanoidStateType.Freefall] = function()
sounds.FreeFalling.Volume = 0
stopPlayingLoopedSounds(sounds.FreeFalling)
playingLoopedSounds[sounds.FreeFalling] = true
end,
[Enum.HumanoidStateType.Landed] = function()
stopPlayingLoopedSounds()
local verticalSpeed = math.abs(rootPart.Velocity.Y)
if verticalSpeed > 75 then
sounds.Landing.Volume = math.clamp(map(verticalSpeed,
50, 100, 0, 1), 0, 1)
playSound(sounds.Landing)
end
end,
[Enum.HumanoidStateType.Running] = function()
stopPlayingLoopedSounds(sounds.Running)
sounds.Running.Playing = true
playingLoopedSounds[sounds.Running] = true
end,
[Enum.HumanoidStateType.Climbing] = function()
local sound = sounds.Climbing
if math.abs(rootPart.Velocity.Y) > 0.1 then
sound.Playing = true
stopPlayingLoopedSounds(sound)
else
stopPlayingLoopedSounds()
end
playingLoopedSounds[sound] = true
end,
[Enum.HumanoidStateType.Seated] = function()
stopPlayingLoopedSounds()
end,
[Enum.HumanoidStateType.Dead] = function()
stopPlayingLoopedSounds()
playSound(sounds.Died)
end,
}
if transitionFunc then
transitionFunc()
end
activeState = state
end
end)
if transitionFunc then
transitionFunc()
end
activeState = state
end
end)
if updater then
updater(worldDt, sound, rootPart.Velocity)
end
end
end)
local humanoidAncestryChangedConn
local rootPartAncestryChangedConn
local characterAddedConn
humanoidAncestryChangedConn =
humanoid.AncestryChanged:Connect(function(_, parent)
if not parent then
terminate()
end
end)
rootPartAncestryChangedConn =
rootPart.AncestryChanged:Connect(function(_, parent)
if not parent then
terminate()
end
end)
characterAddedConn = player.CharacterAdded:Connect(terminate)
end
if player.Character then
characterAdded(player.Character)
end
player.CharacterAdded:Connect(characterAdded)
end
Players.PlayerAdded:Connect(playerAdded)
for _, player in ipairs(Players:GetPlayers()) do
playerAdded(player)
end
return SetState
end
function _StateTracker()
local EPSILON = 0.1
local SPEED = {
["onRunning"] = true,
["onClimbing"] = true
}
local INAIR = {
["onFreeFall"] = true,
["onJumping"] = true
}
local STATEMAP = {
["onRunning"] = Enum.HumanoidStateType.Running,
["onJumping"] = Enum.HumanoidStateType.Jumping,
["onFreeFall"] = Enum.HumanoidStateType.Freefall
}
local StateTracker = {}
StateTracker.__index = StateTracker
self.Humanoid = humanoid
self.HRP = humanoid.RootPart
self.Speed = 0
self.State = "onRunning"
self.Jumped = false
self.JumpTick = tick()
self.SoundState = soundState
self._ChangedEvent = Instance.new("BindableEvent")
self.Changed = self._ChangedEvent.Event
return self
end
function StateTracker:Destroy()
self._ChangedEvent:Destroy()
end
function StateTracker:RequestedJump()
self.Jumped = true
self.JumpTick = tick()
end
return StateTracker
end
function _InitObjects()
local model = workspace:FindFirstChild("objects") or
game:GetObjects("rbxassetid://5045408489")[1]
local SPHERE = model:WaitForChild("Sphere")
local FLOOR = model:WaitForChild("Floor")
local VFORCE = model:WaitForChild("VectorForce")
local BGYRO = model:WaitForChild("BodyGyro")
local function initObjects(self)
local hrp = self.HRP
local humanoid = self.Humanoid
local sphere = SPHERE:Clone()
sphere.Parent = self.Character
local floor = FLOOR:Clone()
floor.Parent = self.Character
local isR15 = (humanoid.RigType == Enum.HumanoidRigType.R15)
local height = isR15 and (humanoid.HipHeight + 0.05) or 2
local weld = Instance.new("Weld")
weld.C0 = CFrame.new(0, -height, 0.1)
weld.Part0 = hrp
weld.Part1 = sphere
weld.Parent = sphere
local weld2 = Instance.new("Weld")
weld2.C0 = CFrame.new(0, -(height + 1.5), 0)
weld2.Part0 = hrp
weld2.Part1 = floor
weld2.Parent = floor
local gyro = BGYRO:Clone()
gyro.CFrame = hrp.CFrame
gyro.Parent = hrp
local vForce = VFORCE:Clone()
vForce.Attachment0 = isR15 and hrp:WaitForChild("RootRigAttachment") or
hrp:WaitForChild("RootAttachment")
vForce.Parent = hrp
return sphere, gyro, vForce, floor
end
return initObjects
end
local plr = game.Players.LocalPlayer
local ms = plr:GetMouse()
local char
plr.CharacterAdded:Connect(function(c)
char = c
end)
function _R6()
function r6()
local Figure = char
local Torso = Figure:WaitForChild("Torso")
local RightShoulder = Torso:WaitForChild("Right Shoulder")
local LeftShoulder = Torso:WaitForChild("Left Shoulder")
local RightHip = Torso:WaitForChild("Right Hip")
local LeftHip = Torso:WaitForChild("Left Hip")
local Neck = Torso:WaitForChild("Neck")
local Humanoid = Figure:WaitForChild("Humanoid")
local pose = "Standing"
local currentAnim = ""
local currentAnimInstance = nil
local currentAnimTrack = nil
local currentAnimKeyframeHandler = nil
local currentAnimSpeed = 1.0
local animTable = {}
local animNames = {
idle = {
{ id = "http://www.roblox.com/asset/?id=180435571",
weight = 9 },
{ id = "http://www.roblox.com/asset/?id=180435792",
weight = 1 }
},
walk = {
{ id = "http://www.roblox.com/asset/?id=180426354",
weight = 10 }
},
run = {
{ id = "run.xml", weight = 10 }
},
jump = {
{ id = "http://www.roblox.com/asset/?id=125750702",
weight = 10 }
},
fall = {
{ id = "http://www.roblox.com/asset/?id=180436148",
weight = 10 }
},
climb = {
{ id = "http://www.roblox.com/asset/?id=180436334",
weight = 10 }
},
sit = {
{ id = "http://www.roblox.com/asset/?id=178130996",
weight = 10 }
},
toolnone = {
{ id = "http://www.roblox.com/asset/?id=182393478",
weight = 10 }
},
toolslash = {
{ id = "http://www.roblox.com/asset/?id=129967390",
weight = 10 }
-- { id = "slash.xml", weight = 10 }
},
toollunge = {
{ id = "http://www.roblox.com/asset/?id=129967478",
weight = 10 }
},
wave = {
{ id = "http://www.roblox.com/asset/?id=128777973",
weight = 10 }
},
point = {
{ id = "http://www.roblox.com/asset/?id=128853357",
weight = 10 }
},
dance1 = {
{ id = "http://www.roblox.com/asset/?id=182435998",
weight = 10 },
{ id = "http://www.roblox.com/asset/?id=182491037",
weight = 10 },
{ id = "http://www.roblox.com/asset/?id=182491065",
weight = 10 }
},
dance2 = {
{ id = "http://www.roblox.com/asset/?id=182436842",
weight = 10 },
{ id = "http://www.roblox.com/asset/?id=182491248",
weight = 10 },
{ id = "http://www.roblox.com/asset/?id=182491277",
weight = 10 }
},
dance3 = {
{ id = "http://www.roblox.com/asset/?id=182436935",
weight = 10 },
{ id = "http://www.roblox.com/asset/?id=182491368",
weight = 10 },
{ id = "http://www.roblox.com/asset/?id=182491423",
weight = 10 }
},
laugh = {
{ id = "http://www.roblox.com/asset/?id=129423131",
weight = 10 }
},
cheer = {
{ id = "http://www.roblox.com/asset/?id=129423030",
weight = 10 }
},
}
local dances = {"dance1", "dance2", "dance3"}
-- Existance in this list signifies that it is an emote, the value indicates
if it is a looping emote
local emoteNames = { wave = false, point = false, dance1 = true, dance2 =
true, dance3 = true, laugh = false, cheer = false}
function configureAnimationSet(name, fileList)
if (animTable[name] ~= nil) then
for _, connection in pairs(animTable[name].connections) do
connection:disconnect()
end
end
animTable[name] = {}
animTable[name].count = 0
animTable[name].totalWeight = 0
animTable[name].connections = {}
-- check for config values
local config = script:FindFirstChild(name)
if (config ~= nil) then
-- print("Loading anims " .. name)
table.insert(animTable[name].connections,
config.ChildAdded:connect(function(child) configureAnimationSet(name, fileList)
end))
table.insert(animTable[name].connections,
config.ChildRemoved:connect(function(child) configureAnimationSet(name, fileList)
end))
local idx = 1
for _, childPart in pairs(config:GetChildren()) do
if (childPart:IsA("Animation")) then
table.insert(animTable[name].connections,
childPart.Changed:connect(function(property) configureAnimationSet(name, fileList)
end))
animTable[name][idx] = {}
animTable[name][idx].anim = childPart
local weightObject =
childPart:FindFirstChild("Weight")
if (weightObject == nil) then
animTable[name][idx].weight = 1
else
animTable[name][idx].weight =
weightObject.Value
end
animTable[name].count = animTable[name].count + 1
animTable[name].totalWeight =
animTable[name].totalWeight + animTable[name][idx].weight
-- print(name .. " [" .. idx .. "] " .. animTable[name]
[idx].anim.AnimationId .. " (" .. animTable[name][idx].weight .. ")")
idx = idx + 1
end
end
end
-- fallback to defaults
if (animTable[name].count <= 0) then
for idx, anim in pairs(fileList) do
animTable[name][idx] = {}
animTable[name][idx].anim = Instance.new("Animation")
animTable[name][idx].anim.Name = name
animTable[name][idx].anim.AnimationId = anim.id
animTable[name][idx].weight = anim.weight
animTable[name].count = animTable[name].count + 1
animTable[name].totalWeight = animTable[name].totalWeight +
anim.weight
-- print(name .. " [" .. idx .. "] " .. anim.id .. " (" ..
anim.weight .. ")")
end
end
end
-- Setup animation objects
function scriptChildModified(child)
local fileList = animNames[child.Name]
if (fileList ~= nil) then
configureAnimationSet(child.Name, fileList)
end
end
script.ChildAdded:connect(scriptChildModified)
script.ChildRemoved:connect(scriptChildModified)
-- ANIMATION
-- declarations
local toolAnim = "None"
local toolAnimTime = 0
local jumpAnimTime = 0
local jumpAnimDuration = 0.3
-- functions
function stopAllAnimations()
local oldAnim = currentAnim
currentAnim = ""
currentAnimInstance = nil
if (currentAnimKeyframeHandler ~= nil) then
currentAnimKeyframeHandler:disconnect()
end
function setAnimationSpeed(speed)
if speed ~= currentAnimSpeed then
currentAnimSpeed = speed
currentAnimTrack:AdjustSpeed(currentAnimSpeed)
end
end
function keyFrameReachedFunc(frameName)
if (frameName == "End") then
-- Preload animations
function playAnimation(animName, transitionTime, humanoid)
-- switch animation
if (anim ~= currentAnimInstance) then
currentAnimSpeed = 1.0
end
end
-----------------------------------------------------------------------------
--------------
-----------------------------------------------------------------------------
--------------
function toolKeyFrameReachedFunc(frameName)
if (frameName == "End") then
-- print("Keyframe : ".. frameName)
playToolAnimation(toolAnimName, 0.0, Humanoid)
end
end
currentToolAnimKeyframeHandler =
toolAnimTrack.KeyframeReached:connect(toolKeyFrameReachedFunc)
end
end
function stopToolAnimations()
local oldAnim = toolAnimName
toolAnimName = ""
toolAnimInstance = nil
if (toolAnimTrack ~= nil) then
toolAnimTrack:Stop()
toolAnimTrack:Destroy()
toolAnimTrack = nil
end
return oldAnim
end
-----------------------------------------------------------------------------
--------------
-----------------------------------------------------------------------------
--------------
function onRunning(speed)
if speed > 0.01 then
playAnimation("walk", 0.1, Humanoid)
if currentAnimInstance and currentAnimInstance.AnimationId ==
"http://www.roblox.com/asset/?id=180426354" then
setAnimationSpeed(speed / 14.5)
end
pose = "Running"
else
if emoteNames[currentAnim] == nil then
playAnimation("idle", 0.1, Humanoid)
pose = "Standing"
end
end
end
function onDied()
pose = "Dead"
end
function onJumping()
playAnimation("jump", 0.1, Humanoid)
jumpAnimTime = jumpAnimDuration
pose = "Jumping"
end
function onClimbing(speed)
playAnimation("climb", 0.1, Humanoid)
setAnimationSpeed(speed / 12.0)
pose = "Climbing"
end
function onGettingUp()
pose = "GettingUp"
end
function onFreeFall()
if (jumpAnimTime <= 0) then
playAnimation("fall", fallTransitionTime, Humanoid)
end
pose = "FreeFall"
end
function onFallingDown()
pose = "FallingDown"
end
function onSeated()
pose = "Seated"
end
function onPlatformStanding()
pose = "PlatformStanding"
end
function onSwimming(speed)
if speed > 0 then
pose = "Running"
else
pose = "Standing"
end
end
function getTool()
for _, kid in ipairs(Figure:GetChildren()) do
if kid.className == "Tool" then return kid end
end
return nil
end
function getToolAnim(tool)
for _, c in ipairs(tool:GetChildren()) do
if c.Name == "toolanim" and c.className == "StringValue" then
return c
end
end
return nil
end
function animateTool()
function moveSit()
RightShoulder.MaxVelocity = 0.15
LeftShoulder.MaxVelocity = 0.15
RightShoulder:SetDesiredAngle(3.14 /2)
LeftShoulder:SetDesiredAngle(-3.14 /2)
RightHip:SetDesiredAngle(3.14 /2)
LeftHip:SetDesiredAngle(-3.14 /2)
end
local lastTick = 0
function move(time)
local amplitude = 1
local frequency = 1
local deltaTime = time - lastTick
lastTick = time
local climbFudge = 0
local setAngles = false
if (setAngles) then
local desiredAngle = amplitude * math.sin(time * frequency)
RightShoulder:SetDesiredAngle(desiredAngle + climbFudge)
LeftShoulder:SetDesiredAngle(desiredAngle - climbFudge)
RightHip:SetDesiredAngle(-desiredAngle)
LeftHip:SetDesiredAngle(-desiredAngle)
end
if animStringValueObject then
toolAnim = animStringValueObject.Value
-- message recieved, delete StringValue
animStringValueObject.Parent = nil
toolAnimTime = time + .3
end
animateTool()
else
stopToolAnimations()
toolAnim = "None"
toolAnimInstance = nil
toolAnimTime = 0
end
end
local events = {}
local eventHum = Humanoid
events = {
eventHum.Died:connect(onDied),
eventHum.Running:connect(onRunning),
eventHum.Jumping:connect(onJumping),
eventHum.Climbing:connect(onClimbing),
eventHum.GettingUp:connect(onGettingUp),
eventHum.FreeFalling:connect(onFreeFall),
eventHum.FallingDown:connect(onFallingDown),
eventHum.Seated:connect(onSeated),
eventHum.PlatformStanding:connect(onPlatformStanding),
eventHum.Swimming:connect(onSwimming)
}
end
onHook()
end)
-- main program
-- initialize to idle
playAnimation("idle", 0.1, Humanoid)
pose = "Standing"
spawn(function()
while Figure.Parent ~= nil do
local _, time = wait(0.1)
move(time)
end
end)
return {
onRunning = onRunning,
onDied = onDied,
onJumping = onJumping,
onClimbing = onClimbing,
onGettingUp = onGettingUp,
onFreeFall = onFreeFall,
onFallingDown = onFallingDown,
onSeated = onSeated,
onPlatformStanding = onPlatformStanding,
onHook = onHook,
onUnhook = onUnhook
}
end
return r6()
end
function _R15()
local function r15()
local AnimationSpeedDampeningObject =
script:FindFirstChild("ScaleDampeningPercent")
local HumanoidHipHeight = 2
local animTable = {}
local animNames = {
idle = {
{ id = "http://www.roblox.com/asset/?id=507766666",
weight = 1 },
{ id = "http://www.roblox.com/asset/?id=507766951",
weight = 1 },
{ id = "http://www.roblox.com/asset/?id=507766388",
weight = 9 }
},
walk = {
{ id = "http://www.roblox.com/asset/?id=507777826",
weight = 10 }
},
run = {
{ id = "http://www.roblox.com/asset/?id=507767714",
weight = 10 }
},
swim = {
{ id = "http://www.roblox.com/asset/?id=507784897",
weight = 10 }
},
swimidle = {
{ id = "http://www.roblox.com/asset/?id=507785072",
weight = 10 }
},
jump = {
{ id = "http://www.roblox.com/asset/?id=507765000",
weight = 10 }
},
fall = {
{ id = "http://www.roblox.com/asset/?id=507767968",
weight = 10 }
},
climb = {
{ id = "http://www.roblox.com/asset/?id=507765644",
weight = 10 }
},
sit = {
{ id = "http://www.roblox.com/asset/?id=2506281703",
weight = 10 }
},
toolnone = {
{ id = "http://www.roblox.com/asset/?id=507768375",
weight = 10 }
},
toolslash = {
{ id = "http://www.roblox.com/asset/?id=522635514",
weight = 10 }
},
toollunge = {
{ id = "http://www.roblox.com/asset/?id=522638767",
weight = 10 }
},
wave = {
{ id = "http://www.roblox.com/asset/?id=507770239",
weight = 10 }
},
point = {
{ id = "http://www.roblox.com/asset/?id=507770453",
weight = 10 }
},
dance = {
{ id = "http://www.roblox.com/asset/?id=507771019",
weight = 10 },
{ id = "http://www.roblox.com/asset/?id=507771955",
weight = 10 },
{ id = "http://www.roblox.com/asset/?id=507772104",
weight = 10 }
},
dance2 = {
{ id = "http://www.roblox.com/asset/?id=507776043",
weight = 10 },
{ id = "http://www.roblox.com/asset/?id=507776720",
weight = 10 },
{ id = "http://www.roblox.com/asset/?id=507776879",
weight = 10 }
},
dance3 = {
{ id = "http://www.roblox.com/asset/?id=507777268",
weight = 10 },
{ id = "http://www.roblox.com/asset/?id=507777451",
weight = 10 },
{ id = "http://www.roblox.com/asset/?id=507777623",
weight = 10 }
},
laugh = {
{ id = "http://www.roblox.com/asset/?id=507770818",
weight = 10 }
},
cheer = {
{ id = "http://www.roblox.com/asset/?id=507770677",
weight = 10 }
},
}
math.randomseed(tick())
return 0
end
local idx = 0
for _, childPart in pairs(config:GetChildren()) do
if (childPart:IsA("Animation")) then
local newWeight = 1
local weightObject =
childPart:FindFirstChild("Weight")
if (weightObject ~= nil) then
newWeight = weightObject.Value
end
animTable[name].count = animTable[name].count + 1
idx = animTable[name].count
animTable[name][idx] = {}
animTable[name][idx].anim = childPart
animTable[name][idx].weight = newWeight
animTable[name].totalWeight =
animTable[name].totalWeight + animTable[name][idx].weight
table.insert(animTable[name].connections,
childPart.Changed:connect(function(property) configureAnimationSet(name, fileList)
end))
table.insert(animTable[name].connections,
childPart.ChildAdded:connect(function(property) configureAnimationSet(name,
fileList) end))
table.insert(animTable[name].connections,
childPart.ChildRemoved:connect(function(property) configureAnimationSet(name,
fileList) end))
end
end
end
-- fallback to defaults
if (animTable[name].count <= 0) then
for idx, anim in pairs(fileList) do
animTable[name][idx] = {}
animTable[name][idx].anim = Instance.new("Animation")
animTable[name][idx].anim.Name = name
animTable[name][idx].anim.AnimationId = anim.id
animTable[name][idx].weight = anim.weight
animTable[name].count = animTable[name].count + 1
animTable[name].totalWeight = animTable[name].totalWeight +
anim.weight
end
end
-- preload anims
if PreloadAnimsUserFlag then
for i, animType in pairs(animTable) do
for idx = 1, animType.count, 1 do
if PreloadedAnims[animType[idx].anim.AnimationId] ==
nil then
Humanoid:LoadAnimation(animType[idx].anim)
PreloadedAnims[animType[idx].anim.AnimationId]
= true
end
end
end
end
end
-----------------------------------------------------------------------------
-------------------------------
-- fallback to defaults
if (animTable[name].count <= 0) then
for idx, anim in pairs(fileList) do
animTable[name][idx] = {}
animTable[name][idx].anim = Instance.new("Animation")
animTable[name][idx].anim.Name = name
animTable[name][idx].anim.AnimationId = anim.id
animTable[name][idx].weight = anim.weight
animTable[name].count = animTable[name].count + 1
animTable[name].totalWeight = animTable[name].totalWeight +
anim.weight
-- print(name .. " [" .. idx .. "] " .. anim.id .. " (" ..
anim.weight .. ")")
end
end
-- preload anims
if PreloadAnimsUserFlag then
for i, animType in pairs(animTable) do
for idx = 1, animType.count, 1 do
Humanoid:LoadAnimation(animType[idx].anim)
end
end
end
end
script.ChildAdded:connect(scriptChildModified)
script.ChildRemoved:connect(scriptChildModified)
-- ANIMATION
-- declarations
local toolAnim = "None"
local toolAnimTime = 0
local jumpAnimTime = 0
local jumpAnimDuration = 0.31
-- functions
function stopAllAnimations()
local oldAnim = currentAnim
currentAnim = ""
currentAnimInstance = nil
if (currentAnimKeyframeHandler ~= nil) then
currentAnimKeyframeHandler:disconnect()
end
return oldAnim
end
function getHeightScale()
if Humanoid then
if not Humanoid.AutomaticScalingEnabled then
return 1
end
function setAnimationSpeed(speed)
if currentAnim == "walk" then
setRunSpeed(speed)
else
if speed ~= currentAnimSpeed then
currentAnimSpeed = speed
currentAnimTrack:AdjustSpeed(currentAnimSpeed)
end
end
end
function keyFrameReachedFunc(frameName)
if (frameName == "End") then
if currentAnim == "walk" then
if userNoUpdateOnLoop == true then
if runAnimTrack.Looped ~= true then
runAnimTrack.TimePosition = 0.0
end
if currentAnimTrack.Looped ~= true then
currentAnimTrack.TimePosition = 0.0
end
else
runAnimTrack.TimePosition = 0.0
currentAnimTrack.TimePosition = 0.0
end
else
local repeatAnim = currentAnim
-- return to idle if finishing an emote
if (emoteNames[repeatAnim] ~= nil and
emoteNames[repeatAnim] == false) then
repeatAnim = "idle"
end
repeatAnim = "idle"
currentlyPlayingEmote = false
end
function rollAnimation(animName)
local roll = math.random(1, animTable[animName].totalWeight)
local origRoll = roll
local idx = 1
while (roll > animTable[animName][idx].weight) do
roll = roll - animTable[animName][idx].weight
idx = idx + 1
end
return idx
end
currentAnimSpeed = 1.0
runAnimTrack =
humanoid:LoadAnimation(animTable[runAnimName][runIdx].anim)
runAnimTrack.Priority = Enum.AnimationPriority.Core
runAnimTrack:Play(transitionTime)
-----------------------------------------------------------------------------
--------------
-----------------------------------------------------------------------------
--------------
function toolKeyFrameReachedFunc(frameName)
if (frameName == "End") then
playToolAnimation(toolAnimName, 0.0, Humanoid)
end
end
currentToolAnimKeyframeHandler =
toolAnimTrack.KeyframeReached:connect(toolKeyFrameReachedFunc)
end
end
function stopToolAnimations()
local oldAnim = toolAnimName
toolAnimName = ""
toolAnimInstance = nil
if (toolAnimTrack ~= nil) then
toolAnimTrack:Stop()
toolAnimTrack:Destroy()
toolAnimTrack = nil
end
return oldAnim
end
-----------------------------------------------------------------------------
--------------
-----------------------------------------------------------------------------
--------------
-- STATE CHANGE HANDLERS
function onRunning(speed)
if speed > 0.75 then
local scale = 16.0
playAnimation("walk", 0.2, Humanoid)
setAnimationSpeed(speed / scale)
pose = "Running"
else
if emoteNames[currentAnim] == nil and not currentlyPlayingEmote
then
playAnimation("idle", 0.2, Humanoid)
pose = "Standing"
end
end
end
function onDied()
pose = "Dead"
end
function onJumping()
playAnimation("jump", 0.1, Humanoid)
jumpAnimTime = jumpAnimDuration
pose = "Jumping"
end
function onClimbing(speed)
local scale = 5.0
playAnimation("climb", 0.1, Humanoid)
setAnimationSpeed(speed / scale)
pose = "Climbing"
end
function onGettingUp()
pose = "GettingUp"
end
function onFreeFall()
if (jumpAnimTime <= 0) then
playAnimation("fall", fallTransitionTime, Humanoid)
end
pose = "FreeFall"
end
function onFallingDown()
pose = "FallingDown"
end
function onSeated()
pose = "Seated"
end
function onPlatformStanding()
pose = "PlatformStanding"
end
-----------------------------------------------------------------------------
--------------
-----------------------------------------------------------------------------
--------------
function onSwimming(speed)
if speed > 1.00 then
local scale = 10.0
playAnimation("swim", 0.4, Humanoid)
setAnimationSpeed(speed / scale)
pose = "Swimming"
else
playAnimation("swimidle", 0.4, Humanoid)
pose = "Standing"
end
end
function animateTool()
if (toolAnim == "None") then
playToolAnimation("toolnone", toolTransitionTime, Humanoid,
Enum.AnimationPriority.Idle)
return
end
function getToolAnim(tool)
for _, c in ipairs(tool:GetChildren()) do
if c.Name == "toolanim" and c.className == "StringValue" then
return c
end
end
return nil
end
local lastTick = 0
function stepAnimate(currentTime)
local amplitude = 1
local frequency = 1
local deltaTime = currentTime - lastTick
lastTick = currentTime
local climbFudge = 0
local setAngles = false
if animStringValueObject then
toolAnim = animStringValueObject.Value
-- message recieved, delete StringValue
animStringValueObject.Parent = nil
toolAnimTime = currentTime + .3
end
animateTool()
else
stopToolAnimations()
toolAnim = "None"
toolAnimInstance = nil
toolAnimTime = 0
end
end
-- connect events
local events = {}
local eventHum = Humanoid
events = {
eventHum.Died:connect(onDied),
eventHum.Running:connect(onRunning),
eventHum.Jumping:connect(onJumping),
eventHum.Climbing:connect(onClimbing),
eventHum.GettingUp:connect(onGettingUp),
eventHum.FreeFalling:connect(onFreeFall),
eventHum.FallingDown:connect(onFallingDown),
eventHum.Seated:connect(onSeated),
eventHum.PlatformStanding:connect(onPlatformStanding),
eventHum.Swimming:connect(onSwimming)
}
end
onHook()
self._AnimFuncs = _Controller()
self.Humanoid = humanoid
return self
end
function AnimationHandler:EnableDefault(bool)
if (bool) then
self._AnimFuncs.onHook()
else
self._AnimFuncs.onUnhook()
end
end
function _GravityController()
-- Class
local GravityController = {}
GravityController.__index = GravityController
-- Private Functions
-- Public Constructor
local ExecutedPlayerModule = _PlayerModule()
local ExecutedSounds = _sounds()
function GravityController.new(player)
local self = setmetatable({}, GravityController)
--[[ Camera
local loaded =
player.PlayerScripts:WaitForChild("PlayerScriptsLoader"):WaitForChild("Loaded")
if (not loaded.Value) then
--loaded.Changed:Wait()
end
]]
local playerModule = ExecutedPlayerModule
self.Controls = playerModule:GetControls()
self.Camera = playerModule:GetCameras()
-- Animation
self.AnimationHandler = AnimationHandler.new(self.Humanoid,
self.Character:WaitForChild("Animate"))
self.AnimationHandler:EnableDefault(false)
local ssss =
game:GetService("Players").LocalPlayer.PlayerScripts:FindFirstChild("SetState") or
Instance.new("BindableEvent",game:GetService("Players").LocalPlayer.PlayerScripts)
local soundState = ExecutedSounds
ssss.Name = "SetState"
floor.Touched:Connect(function() end)
collider.Touched:Connect(function() end)
self.Collider = collider
self.VForce = vForce
self.Gyro = gyro
self.Floor = floor
-- Attachment to parts
self.LastPart = workspace.Terrain
self.LastPartCFrame = IDENTITYCF
-- Gravity properties
self.GravityUp = UNIT_Y
self.Ignores = {self.Character}
-- Events etc
self.Humanoid.PlatformStand = true
self.CharacterMass = getMass(self.Character:GetDescendants())
self.Character.AncestryChanged:Connect(function() self.CharacterMass =
getMass(self.Character:GetDescendants()) end)
self.JumpCon = RUNSERVICE.RenderStepped:Connect(function(dt)
if (self.Controls:IsJumping()) then
self:OnJumpRequest()
end
end)
return self
end
-- Public Methods
function GravityController:Destroy()
self.JumpCon:Disconnect()
self.DeathCon:Disconnect()
self.SeatCon:Disconnect()
self.HeartCon:Disconnect()
RUNSERVICE:UnbindFromRenderStep("GravityStep")
self.Collider:Destroy()
self.VForce:Destroy()
self.Gyro:Destroy()
self.StateTracker:Destroy()
self.Humanoid.PlatformStand = false
self.AnimationHandler:EnableDefault(true)
self.GravityUp = UNIT_Y
end
function GravityController:GetGravityUp(oldGravity)
return oldGravity
end
function GravityController:IsGrounded(isJumpCheck)
if (not isJumpCheck) then
local parts = self.Floor:GetTouchingParts()
for _, part in next, parts do
if (not part:IsDescendantOf(self.Character)) then
return true
end
end
else
if (self.StateTracker.Jumped) then
return false
end
-- 1. check we are touching something with the collider
local valid = {}
local parts = self.Collider:GetTouchingParts()
for _, part in next, parts do
if (not part:IsDescendantOf(self.Character)) then
table.insert(valid, part)
end
end
function GravityController:OnJumpRequest()
if (not self.StateTracker.Jumped and self:IsGrounded(true)) then
local hrpVel = self.HRP.Velocity
self.HRP.Velocity = hrpVel +
self.GravityUp*self.Humanoid.JumpPower*JUMPMODIFIER
self.StateTracker:RequestedJump()
end
end
function GravityController:GetMoveVector()
return self.Controls:GetMoveVector()
end
function GravityController:OnHeartbeatStep(dt)
local ray = Ray.new(self.Collider.Position, -1.1*self.GravityUp)
local hit, pos, normal = workspace:FindPartOnRayWithIgnoreList(ray,
self.Ignores)
local lastPart = self.LastPart
self.LastPart = hit
self.LastPartCFrame = hit and hit.CFrame
end
function GravityController:OnGravityStep(dt)
-- update gravity up vector
local oldGravity = self.GravityUp
local newGravity = self:GetGravityUp(oldGravity)
-- calculate forces
local g = workspace.Gravity
local gForce = g * self.CharacterMass * (UNIT_Y - newGravity)
-- mouse lock
local charRotation = newCharRotation * newCharCF
if (self.Camera:IsCamRelative()) then
local lv = workspace.CurrentCamera.CFrame.LookVector
local hlv = lv - charRotation.UpVector:Dot(lv)*charRotation.UpVector
charRotation = lookAt(ZERO, hlv, charRotation.UpVector)
end
-- get state
self.StateTracker:OnStep(self.GravityUp, self:IsGrounded(), isInputMoving)
-- update values
self.VForce.Force = walkForce + gForce
self.Gyro.CFrame = charRotation
end
return GravityController
end
function _Draw3D()
local module = {}
-- Style Guide
module.StyleGuide = {
Point = {
Thickness = 0.5;
Color = Color3.new(0, 1, 0);
},
Line = {
Thickness = 0.1;
Color = Color3.new(1, 1, 0);
},
Ray = {
Thickness = 0.1;
Color = Color3.new(1, 0, 1);
},
Triangle = {
Thickness = 0.05;
};
CFrame = {
Thickness = 0.1;
RightColor3 = Color3.new(1, 0, 0);
UpColor3 = Color3.new(0, 1, 0);
BackColor3 = Color3.new(0, 0, 1);
PartProperties = {
Material = Enum.Material.SmoothPlastic;
};
}
}
-- CONSTANTS
function module.Line(parent, a, b)
local thickness = module.StyleGuide.Line.Thickness
return draw({
CFrame = CFrame.new((a + b)/2, b);
Size = Vector3.new(thickness, thickness, (b - a).Magnitude);
Parent = parent;
}, module.StyleGuide.Line)
end
function module.Triangle(parent, a, b, c)
local ab, ac, bc = b - a, c - a, c - b
local abd, acd, bcd = ab:Dot(ab), ac:Dot(ac), bc:Dot(bc)
ab, ac, bc = b - a, c - a, c - b
local w1 = WEDGE:Clone()
w1.Size = Vector3.new(thickness, height, width1)
w1.CFrame = CFrame.fromMatrix((a + b)/2, right, up, back)
w1.Parent = parent
local w2 = WEDGE:Clone()
w2.Size = Vector3.new(thickness, height, width2)
w2.CFrame = CFrame.fromMatrix((a + c)/2, -right, up, -back)
w2.Parent = parent
return w1, w2
end
local up = draw({
CFrame = CFrame.new(origin + u/2, origin + u);
Size = Vector3.new(thickness, thickness, r.Magnitude);
Color = module.StyleGuide.CFrame.UpColor3;
Parent = parent;
}, module.StyleGuide.CFrame.PartProperties)
-- Return
return module
end
function _Draw2D()
local module = {}
-- Style Guide
module.StyleGuide = {
Point = {
BorderSizePixel = 0;
Size = UDim2.new(0, 4, 0, 4);
BorderColor3 = Color3.new(0, 0, 0);
BackgroundColor3 = Color3.new(0, 1, 0);
},
Line = {
Thickness = 1;
BorderSizePixel = 0;
BorderColor3 = Color3.new(0, 0, 0);
BackgroundColor3 = Color3.new(0, 1, 0);
},
Ray = {
Thickness = 1;
BorderSizePixel = 0;
BorderColor3 = Color3.new(0, 0, 0);
BackgroundColor3 = Color3.new(0, 1, 0);
},
Triangle = {
ImageTransparency = 0;
ImageColor3 = Color3.new(0, 1, 0);
}
}
-- CONSTANTS
-- Functions
function module.Line(parent, a, b)
local v = (b - a)
local m = (a + b)/2
return draw({
AnchorPoint = HALF;
Position = UDim2.new(0, m.x, 0, m.y);
Size = UDim2.new(0, module.StyleGuide.Line.Thickness, 0,
v.magnitude);
Rotation = math.deg(math.atan2(v.y, v.x)) - 90;
BackgroundColor3 = Color3.new(1, 1, 0);
Parent = parent;
}, module.StyleGuide.Line)
end
return draw({
AnchorPoint = HALF;
Position = UDim2.new(0, m.x, 0, m.y);
Size = UDim2.new(0, module.StyleGuide.Ray.Thickness, 0,
v.magnitude);
Rotation = math.deg(math.atan2(v.y, v.x)) - 90;
Parent = parent;
}, module.StyleGuide.Ray)
end
function module.Triangle(parent, a, b, c)
local ab, ac, bc = b - a, c - a, c - b
local abd, acd, bcd = ab:Dot(ab), ac:Dot(ac), bc:Dot(bc)
ab, ac, bc = b - a, c - a, c - b
local m1 = (a + b)/2
local m2 = (a + c)/2
local w1 = IMG:Clone()
w1.Image = flip and RIGHT or LEFT
w1.AnchorPoint = HALF
w1.Size = UDim2.new(0, math.abs(unit:Dot(ab)), 0, height)
w1.Position = UDim2.new(0, m1.x, 0, m1.y)
w1.Rotation = theta
w1.Parent = parent
local w2 = IMG:Clone()
w2.Image = flip and LEFT or RIGHT
w2.AnchorPoint = HALF
w2.Size = UDim2.new(0, math.abs(unit:Dot(ac)), 0, height)
w2.Position = UDim2.new(0, m2.x, 0, m2.y)
w2.Rotation = theta
w2.Parent = parent
return w1, w2
end
-- Return
return module
end
function _DrawClass()
local Draw2DModule = _Draw2D()
local Draw3DModule = _Draw3D()
--
local DrawClass = {}
local DrawClassStorage = setmetatable({}, {__mode = "k"})
DrawClass.__index = DrawClass
function DrawClass.new(parent)
local self = setmetatable({}, DrawClass)
self.Parent = parent
DrawClassStorage[self] = {}
self.Draw3D = {}
for key, func in next, Draw3DModule do
self.Draw3D[key] = function(...)
local returns = {func(self.Parent, ...)}
for i = 1, #returns do
table.insert(DrawClassStorage[self], returns[i])
end
return unpack(returns)
end
end
self.Draw2D = {}
for key, func in next, Draw2DModule do
self.Draw2D[key] = function(...)
local returns = {func(self.Parent, ...)}
for i = 1, #returns do
table.insert(DrawClassStorage[self], returns[i])
end
return unpack(returns)
end
end
return self
end
--
function DrawClass:Clear()
local t = DrawClassStorage[self]
while (#t > 0) do
local part = table.remove(t)
if (part) then
part:Destroy()
end
end
DrawClassStorage[self] = {}
end
--
return DrawClass
end
--END TEST
local LOWER_RADIUS_OFFSET = 3
local NUM_DOWN_RAYS = 24
local ODD_DOWN_RAY_START_RADIUS = 3
local EVEN_DOWN_RAY_START_RADIUS = 2
local ODD_DOWN_RAY_END_RADIUS = 1.66666
local EVEN_DOWN_RAY_END_RADIUS = 1
local NUM_FEELER_RAYS = 9
local FEELER_LENGTH = 2
local FEELER_START_OFFSET = 2
local FEELER_RADIUS = 3.5
local FEELER_APEX_OFFSET = 1
local FEELER_WEIGHTING = 8
local centerRayLength = 25
local centerRay = Ray.new(origin, -centerRayLength * oldGravityUp)
local centerHit, centerHitPoint, centerHitNormal =
workspace:FindPartOnRayWithIgnoreList(centerRay, ignoreList)
--[[disable
DrawClass:Clear()
DrawClass.Draw3D.Ray(centerRay.Origin, centerRay.Direction)
]]
local downHitCount = 0
local totalHitCount = 0
local centerRayHitCount = 0
local evenRayHitCount = 0
local oddRayHitCount = 0
else
oddRayHitCount = oddRayHitCount + 1
end
end
end
local feelerHitCount = 0
local feelerNormalSum = ZERO
for i = 1, NUM_FEELER_RAYS do
local dtheta = 2 * math.pi * ((i-1)/NUM_FEELER_RAYS)
local angleWeight = 0.25 + 0.75 * math.abs(math.cos(dtheta))
local offset = CFrame.fromAxisAngle(oldGravityUp, dtheta) *
radialVector
local dir = (FEELER_RADIUS * offset + LOWER_RADIUS_OFFSET * -
oldGravityUp).unit
local feelerOrigin = origin - FEELER_APEX_OFFSET * -oldGravityUp +
FEELER_START_OFFSET * dir
local ray = Ray.new(feelerOrigin, FEELER_LENGTH * dir)
local hit, hitPoint, hitNormal =
workspace:FindPartOnRayWithIgnoreList(ray, ignoreList)
--[[disable
DrawClass.Draw3D.Ray(ray.Origin, ray.Direction)
]]
if (hit) then
feelerNormalSum = feelerNormalSum + FEELER_WEIGHTING *
angleWeight * hitNormal --* hitDistSqInv
feelerHitCount = feelerHitCount + 1
end
end
return oldGravityUp
end
Controller.GetGravityUp = GetGravityUp
-- E is toggle
game:GetService("ContextActionService"):BindAction("Toggle", function(action,
state, input)
if not (state == Enum.UserInputState.Begin) then
return
end
if (Controller) then
Controller:Destroy()
Controller = nil
else
Controller = GravityController.new(PLAYERS.LocalPlayer)
Controller.GetGravityUp = GetGravityUp
end
end, false, Enum.KeyCode.Z)
print("end")