--!optimize 2 --!native --!strict local Bobbing = {} Bobbing.__index = Bobbing Bobbing.TurnAlpha = 0.050 Bobbing.LeanMultiplier = 1.7 Bobbing.SwayMultiplier = 1.5 Bobbing.AnimationAlpha = 0.5 Bobbing.MaxGimbalLockY = 65 Bobbing.AnimationSpeed = 200 Bobbing.Tick = 0 Bobbing.ForceStop = false type AnimationsMap = {[string]: (tick: number, dt: number) -> Euler} local Animations: AnimationsMap = {} type Euler = CFrame type HumanoidRootPart = BasePart local UIS = game:GetService("UserInputService") local CN = CFrame.new local ANG = CFrame.Angles local CameraLean = CN() local Animation = CN() function Bobbing.constructor(HumanoidRootPart: HumanoidRootPart, CurrentCamera: Camera, Humanoid: Humanoid) return setmetatable({ HumanoidRootPart = HumanoidRootPart, CurrentCamera = CurrentCamera, Humanoid = Humanoid }, Bobbing) end type deltatime = number type tick = number type EulerValue = number function Animations.Idle(t: tick, dt: deltatime) return ANG( math.rad(math.cos(t/80)/(Bobbing.AnimationSpeed+50)), math.rad(math.sin(t/50)/Bobbing.AnimationSpeed), math.rad(math.sin(t/70)/(Bobbing.AnimationSpeed-50)) ) end function Animations.Walk(t: tick, dt: deltatime) -- return ANG( -- math.rad(-10*math.cos(t)/2), -- 0, -- math.rad(5*math.cos(t)/2) -- ) return ANG( 0, 0, 0 ) end function Animations.Stop() return ANG(0,0,0) end function Animations.Falling() return ANG(0,0,0) end local function maxmin(min: number, mid: number, max: number): number return math.max(min, math.min(mid, max)) end local function Camera_YArc(Camera: Camera): EulerValue --stop Euler gimbal lock when you're looking directly up or down local EulerY,_,_ = Camera.CFrame.Rotation:ToEulerAnglesYXZ() return math.abs(math.deg(EulerY)) end local function CameraAnimation(self, dt: deltatime) --crying Bobbing.Tick += 1 local Root: BasePart = self.HumanoidRootPart local Velocity: Vector3 = Root:GetVelocityAtPosition(Root.Position) local RootMagnitude: number = Velocity.Magnitude --go go boolean algebra local MaxMinY: boolean = Camera_YArc(self.CurrentCamera)>Bobbing.MaxGimbalLockY --TODO: instead, make this an equation so it will just subtract from the existing animation radians local AnimationType: string = Bobbing.ForceStop and "Stop" or RootMagnitude>1 and "Walk" or (not MaxMinY and "Idle" or "Stop") local CurrentAnimation: CFrame = Animations[AnimationType](Bobbing.Tick, dt) --"Lerp" so the transitions between walking and idling are smoothed instead of instant return Animation:Lerp(CurrentAnimation, Bobbing.AnimationAlpha) end function Bobbing:Frame(dt: number) local Camera = self.CurrentCamera local Humanoid = self.Humanoid local CameraCF = Camera.CFrame Animation = CameraAnimation(self, dt) --Lean the camera based on looking and moving direction(s) local MouseDelta = UIS:GetMouseDelta() local LeanDegree_Roll = -CameraCF.RightVector:Dot(Humanoid.MoveDirection)*Bobbing.LeanMultiplier --Jump?! --local LeanDegree_Pitch = -CameraCF.UpVector:Dot(Humanoid.MoveDirection)*Bobbing.LeanMultiplier local LeanMult_Roll = maxmin(-Bobbing.SwayMultiplier, LeanDegree_Roll-MouseDelta.X, Bobbing.SwayMultiplier) CameraLean = CameraLean:Lerp(ANG(0, 0, math.rad(LeanMult_Roll)), Bobbing.TurnAlpha) Camera.CFrame *= Animation*CameraLean end return Bobbing