Sonic Slope Tilt & Momentum

I’ve spent the past few months working on creating a Sonic game with a friend of mine and my movement system is practically complete. It involves multiple stages of speed with increases to WalkSpeed over time and ties into several other systems I’ve completed (Many of which use AlignPosition and do not alter WalkSpeed). The last thing that I was hoping to achieve was some form of momentum based on the slopes that the player is running up or down but I’m running into an issue where I’ll run full speed into a slope and slow down horribly despite none of these angles surpassing 89 degrees (The default and maximum value for MaxSlopeAngle.) This post will be lengthy, but I want to give as much context as humanly possible.

I have two objectives:

  1. To allow the character models to appropriately tilt without sinking into the surfaces they’re standing upon.
  2. To use WalkSpeed as a basis for the character’s momentum up or down hills and reduce physics interruptions.

Starting with Objective #1:

My hope was to have the player’s HumanoidRootPart rotate according to the surface they’re standing on and I used this post as reference to do so:

I use HumanoidRootPart instead of LowerTorso because our custom character models don’t have LowerTorso parts. This is what the inside of the character models roughly look like. Though each is mildly different for customization, the main R15 parts remain the same throughout:


With this, I was able to create a functioning system that would tilt the character model. This generally works in most scenarios, but I’ll show how this quite quickly becomes less effective as your angle increases:

At a simple, low angle, the character model responds nearly perfectly to the rotation.

Once you continue upwards, however, you quickly begin to sink inwards towards the slope that you’re standing on despite the angle being completely accurate.

This is my version of the code from the linked forum chain above. Note that, as I said earlier, I chose to use the HumanoidRootPart as the basis for this as these models do not have LowerTorso objects. When “rootPart.UpperTorso.C0” is referenced, “UpperTorso” is the name of the Motor6D object connecting the HumanoidRootPart to the UpperTorso part, not the UpperTorso itself.

local rs = game:GetService("ReplicatedStorage")
local event = rs.PlayerCustomMovementEvent

event:FireServer(game:GetService("Players").LocalPlayer)

local char = game:GetService("Players").LocalPlayer.Character or game:GetService("Players").LocalPlayer.CharacterAdded:Wait()
local upperTorso, rootPart = char:WaitForChild("UpperTorso"), char:WaitForChild("HumanoidRootPart")

local params = RaycastParams.new()
params.FilterDescendantsInstances = {char}
params.FilterType = Enum.RaycastFilterType.Blacklist

game:GetService("RunService").Heartbeat:Connect(function()
	local ray = workspace:Raycast(rootPart.Position, Vector3.new(0, -char.Humanoid.HipHeight - (rootPart.Size.Y / 2) - 2, 0), params)
	if ray then
		local vector = rootPart.CFrame:VectorToObjectSpace(ray.Normal)
		--lowerTorso.Root.C0 = CFrame.new(0,-1,0,1,0,0,0,1,0,0,0,1) * CFrame.Angles(vector.z, 0, -vector.x)
		rootPart.UpperTorso.C0 = CFrame.new(0,-0.2,0,1,0,0,0,1,0,0,0,1) * CFrame.Angles(vector.z, 0, -vector.x)
    end
end)

I believe that the solution to my problem would be to increase the -0.2 value depending on the angle upon which the player is standing but I’m not sure how to calculate that.

I need guidance on how to calculate when this value needs increased.

Objective #2:

My game involves the usage of a wide variety of slopes and gameplay mechanics from throughout the Sonic series. Additionally, custom animations are used across the board. While my gameplay mechanics themselves work and the movement itself feels good and does not require much tweaking, I’m running into two very specific issues with Roblox’s physics.

  1. When moving downhill at high speeds, the character is unable to stick to the ground they’re standing on, having a sort of forward hop at random intervals.

Downhill Gif

  1. When moving uphill at a certain angle, the player character is unable to maintain their speed and momentum despite WalkSpeed being incredibly high. This has minimal GUI enabled to display that WalkSpeed is maintained during uphill movement.

Uphill Gif

I need some way to ensure that when players are moving downhill quickly, they’re not losing control of their character. I also need some way to also ensure that when moving uphill quickly, they can actually complete that task with the proper speed and their momentum is not completely halted.

NOTE: It would be absolutely ideal NOT to have to reformat my movement system around a constant BodyVelocity & BodyGyro format. The movement is based entirely on WalkSpeed.

Would I need some way to increase gravity on the path down? Do I have to somehow introduce a degree of BodyVelocity pushing with a parallel vector to the slope being stood on on the way up? How would I do these things, and what is the most efficient way?

I want to be able to do both of these things and prevent the player from sinking into the ground while doing so. I sincerely appreciate any guidance and help in accomplishing this, as I’ve spent the last many hours trying to research to no avail.

6 Likes

Yes, the sinking of the character into the part they’re standing on is something I haven’t spent much time attempting to resolve (in the post you referenced, you can see it a little in the GIF that I posted). Just something I was going to live with until I have time to try and resolve it; will definitely let you know if I come up with something.

1 Like

Thanks for the reply. I actually looked into that part myself and seem to have come to a solution though it requires a lot of manual tweaking to get right. Here’s what mine currently looks like:

game:GetService("RunService").Heartbeat:Connect(function()
	local ray = workspace:Raycast(rootPart.Position, Vector3.new(0, -char.Humanoid.HipHeight - (rootPart.Size.Y / 2) - 2, 0), params)
	if ray then
		local vector = rootPart.CFrame:VectorToObjectSpace(ray.Normal)
		--lowerTorso.Root.C0 = CFrame.new(0,-1,0,1,0,0,0,1,0,0,0,1) * CFrame.Angles(vector.z, 0, -vector.x)
		if vector.Y >= 0.96 then
			rootPart.UpperTorso.C0 = CFrame.new(0,-0.2,0,1,0,0,0,1,0,0,0,1) * CFrame.Angles(vector.z, 0, -vector.x)
		elseif vector.Y >= 0.94 and vector.Y < 0.96 then
			rootPart.UpperTorso.C0 = CFrame.new(0,-0.1,0,1,0,0,0,1,0,0,0,1) * CFrame.Angles(vector.z, 0, -vector.x)
		elseif vector.Y < 0.94 and vector.Y >= 0.85 then
			rootPart.UpperTorso.C0 = CFrame.new(0,0.4,0,1,0,0,0,1,0,0,0,1) * CFrame.Angles(vector.z, 0, -vector.x)
		elseif vector.Y < 0.85 and vector.Y >= 0.75 then
			rootPart.UpperTorso.C0 = CFrame.new(0,0.6,0,1,0,0,0,1,0,0,0,1) * CFrame.Angles(vector.z, 0, -vector.x)
		elseif vector.Y < 0.75 and vector.Y >= 0.6 then
			rootPart.UpperTorso.C0 = CFrame.new(0,1.75,0,1,0,0,0,1,0,0,0,1) * CFrame.Angles(vector.z, 0, -vector.x)
		elseif vector.Y < 0.6 and vector.Y >= 0.52 then
			rootPart.UpperTorso.C0 = CFrame.new(0,2.3,0,1,0,0,0,1,0,0,0,1) * CFrame.Angles(vector.z, 0, -vector.x)
		elseif vector.Y < 0.52 and vector.Y >= 0.4 then
			rootPart.UpperTorso.C0 = CFrame.new(0,2.9,0,1,0,0,0,1,0,0,0,1) * CFrame.Angles(vector.z, 0, -vector.x)
		elseif vector.Y < 0.4 and vector.Y >= 0.3 then
			rootPart.UpperTorso.C0 = CFrame.new(0,3.4,0,1,0,0,0,1,0,0,0,1) * CFrame.Angles(vector.z, 0, -vector.x)
		elseif vector.Y < 0.3 and vector.Y >= 0.2 then
			rootPart.UpperTorso.C0 = CFrame.new(0,3.7,0,1,0,0,0,1,0,0,0,1) * CFrame.Angles(vector.z, 0, -vector.x)
		elseif vector.Y < 0.2 and vector.Y >= 0.1 then
			rootPart.UpperTorso.C0 = CFrame.new(0,4,0,1,0,0,0,1,0,0,0,1) * CFrame.Angles(vector.z, 0, -vector.x)
		else
			rootPart.UpperTorso.C0 = CFrame.new(0,4.3,0,1,0,0,0,1,0,0,0,1) * CFrame.Angles(vector.z, 0, -vector.x)
		end

	end
end)

The higher the vector’s Y component is, the more I increase that value to compensate. I printed the vector’s values out and tested running around on a few ramps to see what the Y component looks like (It doesn’t go negative when going downwards so you don’t have to account for that). I manually created ranges for certain rotations and went from there.

5 Likes

I’m making something similar and I’m having the 2 same issues with slopes, and I haven’t figured either out yet

1 Like