--- made by forsaken c: workspace.Gravity = 100 local Config = { --[[General]] VISUALIZE_FEET_HB = false, -- Visualize Feet Hitbox VISUALIZE_COLLIDE_AND_SLIDE = false, STEP_OFFSET = 1.2, MASS = 16, AIR_FRICTION = 0.4, FRICTION = 6, GRAVITY = 10, JUMP_VELOCITY = 30, --[[Accel/Deccel]] GROUND_ACCEL = 14, GROUND_DECCEL = 10, AIR_ACCEL = 52, --[[General Speed]] AIR_SPEED = 42, RUN_SPEED = 32, WALK_SPEED = 10, CROUCH_SPEED = 10, --[[Advanced Speed]] AIR_MAX_SPEED = 36.5, -- The speed at which AIR_MAX_SPEED_FRIC is applied. AIR_MAX_SPEED_FRIC = 3, -- The initial friction applied at max speed AIR_MAX_SPEED_FRIC_DEC = .5, -- Amount multiplied to current max speed friction per 1/60sec MIN_SLOPE_ANGLE = 40, MAX_SLOPE_ANGLE = 75, --[[Misc (Don't worry about these)]] LEG_HEIGHT = 1.9+.3, TORSO_TO_FEET = 3.1+1.9, FEET_HB_SIZE = Vector3.new(1,0.1,1), TORSO_HB_SIZE = Vector3.new(3,1,3), FOOT_OFFSET_AMOUNT = 1.2 } local RunService = game:GetService("RunService") local UserInputService = game:GetService("UserInputService") local ids = { "rbxassetid://7338942417", "rbxassetid://7338942345", "rbxassetid://7338942259", "rbxassetid://7338942180", "rbxassetid://7338942417", } local sounds = {} for _, i in ipairs(ids) do local sound = Instance.new("Sound") sound.SoundId = i sound.Volume = 2 sound.Parent = game.Workspace -- Or another appropriate parent table.insert(sounds, sound) end export type Movement = { Keys: {W: number, A: number, S: number, D: number, Space: number}, player: Player, character: Model, collider: Part, vispart: Part?, vis_coll_parts: {Part}, config: MovementConfig, mover: LinearVelocity, up_mover: LinearVelocity, dt: number, states: MovementStates, } export type MovementConfig = { VISUALIZE_FEET_HB: boolean, VISUALIZE_COLLIDE_AND_SLIDE: boolean, LEG_HEIGHT: number, FEET_HB_SIZE: Vector3, TORSO_HB_SIZE: Vector3, MASS: number, GRAVITY: number, FRICTION: number, AIR_FRICTION: number, GROUND_ACCEL: number, GROUND_DECCEL: number, AIR_ACCEL: number, RUN_SPEED: number, WALK_SPEED: number, CROUCH_SPEED: number, AIR_SPEED: number, AIR_MAX_SPEED: number, AIR_MAX_SPEED_FRIC: number, AIR_MAX_SPEED_FRIC_DEC: number, JUMP_VELOCITY: number, STEP_OFFSET: number, TORSO_TO_FEET: number, MIN_SLOPE_ANGLE: number, MAX_SLOPE_ANGLE: number, } export type MovementStates = { grounded: boolean, air_friction: number, input_vec: number, surfing: boolean, jumping: boolean, } export type Vec3Mod = {x: number, y: number, z: number, ToVector3: (Vec3Mod) -> Vector3} local Vec3Mod = {} Vec3Mod.__index = Vec3Mod function Vec3Mod.new(input: Vector3?) : Vec3Mod input = input or Vector3.zero return setmetatable({x = input.X, y = input.Y, z = input.Z}, Vec3Mod) :: Vec3Mod end function Vec3Mod:ToVector3() return Vector3.new(self.x, self.y, self.z) end local Movement: Movement = {} local Physics = {} :: Movement local Shared : Movement= {} local Trace : Movement= {} local Collisions = {} :: Movement local function visualizeRayResult(result: RaycastResult, origin: Vector3, direction: Vector3) local position = result and result.Position or (origin + direction) local distance = (origin - position).Magnitude local p = Instance.new("Part") p.Anchored = true p.CanCollide = false p.Size = Vector3.new(0.1, 0.1, distance) p.CFrame = CFrame.lookAt(origin, position)*CFrame.new(0, 0, -distance/2) p.Parent = workspace.Temp return p end local function flattenVectorAgainstWall(moveVector: Vector3, normal: Vector3) -- if magnitudes are 0 then just nevermind if moveVector.Magnitude == 0 and normal.Magnitude == 0 then return Vector3.zero end -- unit the normal (i its already normalized idk) normal = normal.Unit -- reflect the vector local reflected = moveVector - 2 * moveVector:Dot(normal) * normal -- add the reflection to the move vector = vector parallel to wall local parallel = moveVector + reflected -- if magnitude 0 NEVERMIND!!! if parallel.Magnitude == 0 then return Vector3.zero end -- reduce the parallel vector to make sense idk HorseNuggetsXD did all this thank u local cropped = parallel.Unit:Dot(moveVector.Unit) * parallel.Unit * moveVector.Magnitude return cropped end function Collisions:CollideAndSlide(wishedSpeed: Vector3) local mod = 1 if wishedSpeed.Magnitude == 0 then return wishedSpeed end -- get input vector local inputVec = self.states.input_vec local newSpeed = wishedSpeed local hrp = self.player.Character.HumanoidRootPart -- raycast var local params = Shared.GetMovementParams(self) -- wished speed modifier wishedSpeed *= 2 -- direction amount var local dirAmnt = 1.375 * (mod or 1) local mainDirAmnt = 1.55 * (mod or 1) -- stick var local isSticking = false local normals = {} local stickingDirections = {} local ldd = {dir = false, dist = false} -- lowest distance direction local partsAlreadyHit = {} -- destroy sticking visualizations if Config.VISUALIZE_COLLIDE_AND_SLIDE then for _, v in pairs(self.vis_coll_parts) do v:Destroy() end end local lookVecs = { Vector3.new(0, (-Config.TORSO_TO_FEET) + Config.STEP_OFFSET, 0), Vector3.new(0, 0, 0), Vector3.new(0, 2, 0) } for _, v in pairs(lookVecs) do local values = {} local hval = {} local currForDir local currSideDir local rayPos if typeof(v) == "Vector3" then rayPos = hrp.Position + v else rayPos = Vector3.new(hrp.Position.X, hrp.Parent[v].CFrame.Position, hrp.Position.Z) end -- right, front, back if inputVec.X > 0 then currForDir = hrp.CFrame.RightVector table.insert(values, currForDir) table.insert(hval, hrp.CFrame.LookVector * dirAmnt) table.insert(hval, -hrp.CFrame.LookVector * dirAmnt) -- left, front, back elseif inputVec.X < 0 then currForDir = -hrp.CFrame.RightVector table.insert(values, currForDir) table.insert(hval, hrp.CFrame.LookVector * dirAmnt) table.insert(hval, -hrp.CFrame.LookVector * dirAmnt) end -- back, left, right if inputVec.Z > 0 then currSideDir = -hrp.CFrame.LookVector table.insert(values, currSideDir) table.insert(hval, hrp.CFrame.RightVector * dirAmnt) table.insert(hval, -hrp.CFrame.RightVector * dirAmnt) -- front, left, right elseif inputVec.Z < 0 then currSideDir = hrp.CFrame.LookVector table.insert(values, currSideDir) table.insert(hval, hrp.CFrame.RightVector * dirAmnt) table.insert(hval, -hrp.CFrame.RightVector * dirAmnt) end if inputVec.Z == 0 and inputVec.X == 0 then values[1] = wishedSpeed.Unit table.insert(hval, CFrame.new(wishedSpeed.Unit).RightVector * dirAmnt) table.insert(hval, -CFrame.new(wishedSpeed.Unit).RightVector * dirAmnt) else table.insert(values, wishedSpeed.Unit * dirAmnt) end -- middle directions if currForDir and currSideDir then for a, b in pairs(values) do values[a] = b * mainDirAmnt end table.insert(values, (currForDir+currSideDir) * mainDirAmnt) else values[1] *= mainDirAmnt table.insert(values, (values[1] + hval[1]).Unit * mainDirAmnt) table.insert(values, (values[1] + hval[2]).Unit * mainDirAmnt) table.insert(values, hval[1]) table.insert(values, hval[2]) end for _, b in pairs(values) do if not b then continue end -- visualize ray using pos and direction if Config.VISUALIZE_COLLIDE_AND_SLIDE then table.insert(self.vis_coll_parts, visualizeRayResult(false, rayPos, b)) end local result = workspace:Raycast(rayPos, b, params) if not result then continue end if (not ldd.dir or not ldd.dist) or (ldd.dist and ldd.dist < result.Distance) then ldd.dir = b ldd.dist = result.Distance end -- don't collide with the same part twice if table.find(partsAlreadyHit, result.Instance) then continue end table.insert(partsAlreadyHit, result.Instance) -- get the movement direction compared to the wall local _v = newSpeed.Unit * result.Normal -- find active coordinate of comparison for _, c in pairs({_v.X, _v.Y, _v.Z}) do if math.abs(c) > 0 then _v = c break end end -- if we are moving AWAY from the normal, (positive) -- then do not flatten the vector. -- it's not necessary. -- you will stick. -- stick. if type(_v) == "number" and _v > 0 then continue end if not isSticking then isSticking = true end newSpeed = flattenVectorAgainstWall(newSpeed, result.Normal) newSpeed -= result.Instance.Velocity self.mover.PlaneVelocity = Vector2.new(newSpeed.X, newSpeed.Z) --self.collider.Velocity = Vector3.new(newSpeed.X, (self.collider.Velocity.Y), newSpeed.Z) -- anti sticking has to be applied on collider velocity as well (resolves head & in air collision) end end return newSpeed, isSticking and normals, isSticking and stickingDirections, isSticking and ldd.dir end export type Trace = { startPos: Vector3, endPos: Vector3, fraction: number, startSolid: boolean, hitPart: Instance, hitPoint: Vector3, planeNormal: Vector3, distance: number } local function HandleVisualization(self, cf, size) if not Config.VISUALIZE_FEET_HB then return end if not self.vispart then self.vispart = Instance.new("Part", self.character) self.vispart.CanCollide = false self.vispart.Anchored = true end self.vispart.CFrame = cf self.vispart.Size = size end function Shared.GetAngle(normal) return math.deg(math.acos(normal:Dot(Vector3.yAxis))) end function Shared:IsGrounded(dir): (boolean, boolean?, RaycastResult?, number?) local params = Shared.GetMovementParams(self) local cf = CFrame.new(self.collider.Position) - Vector3.new(0, self.collider.Size.Y/2, 0) local size = Config.FEET_HB_SIZE dir = dir or Vector3.new(0,-1 * self.collider.Size.Y-Config.FOOT_OFFSET_AMOUNT,0) local result = workspace:Blockcast(cf, size, dir, params) HandleVisualization(self, cf, size) if not result then return false end local steepness = Shared.GetAngle(result.Normal) local isSurfing = steepness >= Config.MIN_SLOPE_ANGLE and steepness <= Config.MAX_SLOPE_ANGLE if isSurfing then return false, true, result, steepness end return true, false, result end function Shared:RotateCharacter() local collider = self.collider local camera = workspace.CurrentCamera local rotationLook = collider.Position + camera.CoordinateFrame.lookVector collider.CFrame = CFrame.new(collider.Position, Vector3.new(rotationLook.x, collider.Position.y, rotationLook.z)) collider.RotVelocity = Vector3.new() end function Shared:GetMovementDirection(groundNormal) local forward = self.Keys.W + -self.Keys.S local side = self.Keys.A + -self.Keys.D groundNormal = groundNormal or Vector3.new(0,1,0) if forward == 0 and side == 0 then self.states.input_vec = Vector3.zero return Vector3.zero else self.states.input_vec = Vector3.new(-side, 0, -forward).Unit local forwardMove = groundNormal:Cross(self.collider.CFrame.RightVector) local sideMove = groundNormal:Cross(forwardMove) return (forwardMove * forward + sideMove * side).Unit end end function Shared:GetMovementParams() local params = RaycastParams.new() params.FilterDescendantsInstances = {self.character, workspace.CurrentCamera} params.FilterType = Enum.RaycastFilterType.Exclude params.RespectCanCollide = false params.CollisionGroup = "PlayerMovement" return params end function Shared:VectorMa(start: Vector3, scale: number, direction: Vector3) return Vector3.new( start.X + direction.X * scale, start.Y + direction.Y * scale, start.Z + direction.Z * scale ) end local Trace: Movement = {} local numBumps = 1 local maxClipPlanes = 5 local _planes = {} local numToCoord = {"X", "Y", "Z"} local function ClipVelocity(self, input, normal, output, overbounce) local newVelMod = Vec3Mod.new(output) Trace.ClipVelocity(self, input, normal, newVelMod, overbounce) return newVelMod:ToVector3() end function Trace:TraceBox(start: Vector3, destination: Vector3) local contactOffset = 0.1 -- idk what this is local longSide = math.sqrt(contactOffset * contactOffset + contactOffset * contactOffset) local direction = (destination - start).Unit local maxDistance = (start - destination).Magnitude + longSide local result: Trace = { startPos = start, endPos = destination } local _, _, hit = Shared.IsGrounded(self, direction*maxDistance) if hit then result.fraction = hit.Distance / maxDistance result.hitPart = hit.Instance result.hitPoint = hit.Position result.planeNormal = hit.Normal result.distance = hit.Distance else result.fraction = 1 end return result end function Trace:ClipVelocity(input: Vector3, normal: Vector3, output: Types.Vec3Mod, overbounce: number) local angle = normal.Y local blocked = 0x00 -- // Assume unblocked. if angle > 0 then -- // If the plane that is blocking us has a positive z component, then assume it's a floor. blocked = blocked or 0x01 end if angle == 0 then -- // If the plane has no Z, it is vertical (wall/step) blocked = blocked or 0x02 end -- // Determine how far along plane to slide based on incoming direction. local backoff = input:Dot(normal) * overbounce --// iterate once to make sure we aren't still moving through the plane for i = 1, 3, 1 do local ci = numToCoord[i] local change = normal[ci] * backoff output[ci] = input[ci] - change end local adjust = output:ToVector3():Dot(normal) if adjust < 0 then output = output:ToVector3()-(normal * adjust) end -- Return blocking flags. return blocked end function Trace:Reflect(velocity: Vector3, origin: Vector3) self = self :: Movement local d local newVelocity = Vector3.zero local blocked = 0 local numplanes = 1 local originalVelocity = velocity local primalVelocity = velocity local allFraction = 0 local timeLeft = self.dt for bumpcount = 1, numBumps, 1 do if velocity.Magnitude == 0 then break end -- Assume we can move all the way from the current origin to the -- end point. local endp = Shared.VectorMa(self, origin, timeLeft, velocity) local trace = Trace.TraceBox(self, origin, endp) allFraction += trace.fraction if trace.fraction > 0 then -- actually covered some distance originalVelocity = velocity numplanes = 0 end -- If we covered the entire distance, we are done -- and can return. if trace.fraction == 1 then break end -- moved the entire distance -- If the plane we hit has a high z component in the normal, then -- it's probably a floor if trace.planeNormal.Y > Config.MAX_SLOPE_ANGLE then blocked = blocked or 1 end -- If the plane has a zero z component in the normal, then it's a -- step or wall if trace.planeNormal.Y == 0 then blocked = blocked or 2 -- step/wall end -- Reduce amount of m_flFrameTime left by total time left * fraction -- that we covered. timeLeft -= timeLeft * trace.fraction -- Did we run out of planes to clip against? if numplanes >= maxClipPlanes then -- this shouldn't really happen -- Stop our movement if so. velocity = Vector3.zero break end -- Set up next clipping plane _planes[numplanes] = trace.planeNormal numplanes+=1 -- modify original_velocity so it parallels all of the clip planes -- -- reflect player velocity -- Only give this a try for first impact plane because you can get yourself stuck in an acute corner by jumping in place -- and pressing forward and nobody was really using this bounce/reflection feature anyway... if numplanes == 2 then if _planes[1].Y > Config.MAX_SLOPE_ANGLE then return blocked else newVelocity = ClipVelocity(self, originalVelocity, _planes[1], newVelocity, 1) end velocity = newVelocity originalVelocity = newVelocity else local _i = 0 for i = 1, numplanes-1, 1 do _i = i newVelocity = ClipVelocity(self, originalVelocity, _planes[i], newVelocity, 1) local _j = 0 for j = 1, numplanes-1, 1 do _j = j if j ~= i and velocity:Dot(_planes[j]) < 0 then break end end if _j == numplanes then break end end if _i ~= numplanes then else if numplanes ~= 3 then velocity = Vector3.zero break end local dir = _planes[1]:Cross(_planes[2]).Unit d = dir:Dot(velocity) velocity = dir * d end end d = velocity:Dot(primalVelocity) if d <= 0 then velocity = Vector3.zero break end end if allFraction == 0 then velocity = Vector3.zero end return velocity, blocked end local function ApplyMoverVelocity(self: Movement, velocity: Vector3): Vector2 local vel = Vector2.new(velocity.X, velocity.Z) self.mover.PlaneVelocity = vel return vel end function Physics:ApplyGroundVelocity(groundNormal: Vector3) local wishDir = Shared.GetMovementDirection(self, groundNormal) local wishSpeed = wishDir.Magnitude * Config.RUN_SPEED -- normal friction if self.states.air_friction <= 0 then Physics.ApplyFriction(self) else -- friction that is applied due to the player reaching max speed while bhopping local sub = Config.AIR_MAX_SPEED_FRIC_DEC * self.dt * 60 local curr = self.states.air_friction local fric = curr - sub if fric < 0 then fric = curr + fric end Physics.ApplyFriction(self, math.max(1, fric/Config.FRICTION)) self.states.air_friction = math.max(0, curr - sub) end Physics.ApplyGroundAcceleration(self, wishDir, wishSpeed) Collisions.CollideAndSlide(self, Vector3.new(self.mover.PlaneVelocity.X, 0, self.mover.PlaneVelocity.Y)) -- calculate & apply slope movement if Shared.GetAngle(groundNormal) < 5 then return end local curVel: Vector3 = Vector3.new(self.mover.PlaneVelocity.X, self.collider.Velocity.Y, self.mover.PlaneVelocity.Y) local forVel: Vector3 = groundNormal:Cross(CFrame.Angles(0,math.rad(90),0).LookVector * curVel) local yVel: number = 0 if forVel.Magnitude > 0 then yVel = forVel.Unit.Y * curVel.Magnitude end self.collider.Velocity = Vector3.new(curVel.X, yVel, curVel.Z) end function Physics:ApplyGroundAcceleration(wishDir: Vector3, wishSpeed: number) local addSpeed local accelerationSpeed local currentSpeed local currentVelocity = Vector3.new(self.mover.PlaneVelocity.X, 0, self.mover.PlaneVelocity.Y) local newVelocity = currentVelocity -- get current/add speed currentSpeed = currentVelocity:Dot(wishDir) addSpeed = wishSpeed - currentSpeed -- if we're not adding speed, dont do anything if addSpeed <= 0 then return end -- get accelSpeed, cap at addSpeed accelerationSpeed = math.min(Config.GROUND_ACCEL * self.dt * wishSpeed, addSpeed) -- you can't change the properties of a Vector3, so we do x, y, z newVelocity += (accelerationSpeed * wishDir) newVelocity = Vector3.new(newVelocity.X, 0, newVelocity.Z) -- clamp magnitude (max speed) if newVelocity.Magnitude > (Config.RUN_SPEED) then newVelocity = newVelocity.Unit * math.min(newVelocity.Magnitude, (Config.RUN_SPEED)) end -- apply acceleration ApplyMoverVelocity(self, newVelocity) end function Physics:ApplyAirVelocity(normal) normal = Vector3.new(0,1,0) local vel = Vector3.new(self.mover.PlaneVelocity.X, 0, self.mover.PlaneVelocity.Y) local wishDir = Shared.GetMovementDirection(self, normal) local wishSpeed = wishDir.Magnitude * Config.AIR_SPEED local currSpeed = vel.Magnitude -- initiate extra friction for max speed if currSpeed > Config.AIR_MAX_SPEED then self.states.air_friction = Config.AIR_MAX_SPEED_FRIC end -- apply extra friction if necessary if self.states.air_friction > 0 and not self.states.surfing then Physics.ApplyFriction(self, 0.01 * self.states.air_friction, false) end Physics.ApplyAirAcceleration(self, wishDir, wishSpeed) local refVel = Vector3.new(self.mover.PlaneVelocity.X, self.collider.Velocity.Y, self.mover.PlaneVelocity.Y) ApplyMoverVelocity(self, Trace.Reflect(self, refVel, self.collider.Position)) end function Physics:ApplyAirAcceleration(wishDir: Vector3, wishSpeed: number) local currentSpeed local addSpeed local accelerationSpeed local currentVelocity = Vector3.new(self.mover.PlaneVelocity.X, 0, self.mover.PlaneVelocity.Y) -- get current/add speed currentSpeed = currentVelocity:Dot(wishDir) addSpeed = wishSpeed - currentSpeed -- if we're not adding speed, dont do anything if addSpeed <= 0 then return end -- get accelSpeed, cap at addSpeed accelerationSpeed = math.min(Config.AIR_ACCEL * self.dt * wishSpeed, addSpeed) -- get new velocity local newVelocity = currentVelocity + accelerationSpeed * wishDir -- apply acceleration ApplyMoverVelocity(self, newVelocity) end function Physics:ApplyFriction(modifier: number?, inAir: boolean?) local vel if inAir then vel = self.collider.Velocity else vel = Vector3.new(self.mover.PlaneVelocity.X, 0, self.mover.PlaneVelocity.Y) end local speed = vel.Magnitude modifier = modifier or 1 local drop = 0 local fric = inAir and Config.AIR_FRICTION or Config.FRICTION local decel = Config.GROUND_DECCEL local newSpeed local control -- if we're not moving, don't apply friction if speed <= 0 then return vel end -- ??? control = speed < decel and decel or speed drop = control * fric * self.dt * modifier if type(drop) ~= "number" then drop = drop.Magnitude end -- ???????????? newSpeed = math.max(speed - drop, 0) if speed > 0 and newSpeed > 0 then newSpeed /= speed end vel *= newSpeed ApplyMoverVelocity(self, vel) if inAir then local nv = self.mover.VectorVelocity --self.collider.Velocity = Vector3.new(nv.X, self.collider.Velocity.Y * newSpeed, nv.Z) end end local function Init() Movement.Keys = {W = 0, S = 0, D = 0, A = 0, Space = 0} Movement.player = game.Players.LocalPlayer Movement.character = Movement.player.Character or Movement.player.CharacterAdded:Wait() Movement.collider = Movement.character:WaitForChild("HumanoidRootPart") Movement.vispart = false Movement.vis_coll_parts = {} Movement.config = Config local mover = Instance.new("LinearVelocity", Movement.collider) local a0 = Instance.new("Attachment", Movement.collider) a0.Name = "MovementAttachment" mover.Attachment0 = a0 mover.MaxForce = 10000000 mover.VelocityConstraintMode = Enum.VelocityConstraintMode.Plane mover.PrimaryTangentAxis = Vector3.new(1,0,0) mover.SecondaryTangentAxis = Vector3.new(0,0,1) Movement.mover = mover -- Will figure this out soon. --[[local upMover = Instance.new("LinearVelocity", Movement.collider) upMover.Attachment0 = Movement.collider:WaitForChild("MovementAttachment") upMover.ForceLimitMode = Enum.ForceLimitMode.PerAxis upMover.ForceLimitsEnabled = true upMover.MaxAxesForce = Vector3.new(0, 100000, 0) upMover.VelocityConstraintMode = Enum.VelocityConstraintMode.Vector Movement.up_mover = upMover]] Movement.states = {grounded = false, air_friction = 0, input_vec = Vector3.zero, surfing = false} end -- local function Gravity() local mod = Movement.config.GRAVITY * Movement.dt Movement.collider.AssemblyLinearVelocity = Vector3.new( Movement.collider.AssemblyLinearVelocity.X, Movement.collider.AssemblyLinearVelocity.Y - mod, Movement.collider.AssemblyLinearVelocity.Z ) end local function Air() Physics.ApplyAirVelocity(Movement) local vel = Vector3.new(Movement.mover.PlaneVelocity.X, Movement.collider.AssemblyLinearVelocity.Y, Movement.mover.PlaneVelocity.Y) vel = Trace.Reflect(Movement, vel, Movement.collider.CFrame.Position) Movement.mover.PlaneVelocity = Vector2.new(vel.X, vel.Z) Movement.collider.AssemblyLinearVelocity = vel end local function Ground(groundNormal: Vector3) Physics.ApplyGroundVelocity(Movement, groundNormal) end local function Jump() sounds[math.random(1, #sounds)]:Play() Movement.temp_jump_last = tick() Movement.states.jumping = true Movement.collider.AssemblyLinearVelocity = Vector3.new( Movement.collider.AssemblyLinearVelocity.X, Movement.config.JUMP_VELOCITY, Movement.collider.AssemblyLinearVelocity.Z ) end local function ProcessMovement() local isGrounded, isSurfing, result = Shared.IsGrounded(Movement) Movement.states.grounded = isGrounded or false Movement.states.surfing = isSurfing or false if Movement.collider.AssemblyLinearVelocity.Y < 0 then Movement.states.jumping = false end Shared.RotateCharacter(Movement) if Movement.states.jumping or not Movement.states.grounded then local groundNormal = false if isSurfing then groundNormal = result.Normal if Movement.collider.Velocity.Y > 0 then --Physics.ApplyFriction(Movement, false, true) end end Air(groundNormal) Gravity() elseif Movement.Keys.Space > 0 then Jump() Air() else Ground(result.Normal) end end -- local function InputBegan(input: InputObject, gp: boolean) if input.KeyCode and Movement.Keys[input.KeyCode.Name] then Movement.Keys[input.KeyCode.Name] = 1 end end local function InputEnded(input: InputObject, gp: boolean) if input.KeyCode and Movement.Keys[input.KeyCode.Name] then Movement.Keys[input.KeyCode.Name] = 0 end end local function Update(dt) Movement.dt = dt ProcessMovement() end Init() UserInputService.InputBegan:Connect(InputBegan) UserInputService.InputEnded:Connect(InputEnded) RunService.RenderStepped:Connect(Update)