How can i make my skateboard balance properly (PID controller)

Ok so I’ve made a skateboard, all good but it just keeps on tipping over! I’m not quite sure what im doing wrong :\ I’m using a PID controller to attempt to keep the player balanced but like how the video shows, it just tips over. BTW the parts where i go upright are using a bodygyro, i dont want to switch to a bodygyro as they limit the players movement too much. I am using RoPID by BRicey763.

local PID = require(game:GetService("ReplicatedStorage").RoPID)
local rs = game:GetService('RunService')
local tune = require(PID.Util.Tuner)

local primary = script.Parent.PrimaryPart


local hrp = primary.Parent.Parent:WaitForChild("HumanoidRootPart")
local attach = Instance.new("Attachment")
attach.Parent = hrp
local vf = Instance.new("VectorForce")
vf.Parent = attach
vf.RelativeTo = Enum.ActuatorRelativeTo.Attachment0
vf.Attachment0 = attach
vf.Force = Vector3.new(0,0,0)

local bpid = PID.new(20, 10, 10 , -1000 ,1000)
local tuner = tune.new("bpid", bpid)
local tiltaim = 0
rs.Stepped:Connect(function(et, dt)
	local output = bpid:Calculate(tiltaim, hrp.Orientation.Z,dt)
	vf.Force = Vector3.new(output, 0 ,0)
end)
2 Likes

You should debug it further by visualizing the force as a vector. Then make sure is the direction of the force reasonable? Is the amount of force/magnitude sufficient?

1 Like

But why would it be applying force even if its upright?

1 Like

Sorry for the bump but im like literally stuck on this and have been for a week :\

1 Like

I tested it out on my own.

the fix was to make the output force negative to make sure it is in the right direction forcing the part up rather than pushing the part further downards.

	vf.Force = Vector3.new(-balanceForce,0,0)*mass

the PID coefficients could be better but yeah here reproduction file with a dummy part. Simply run and rotate the part.

Edit: Here is a version with the force visualization I was talking about to better see the force and the direction.
image

PIDMotorcycleTest.rbxl (39.4 KB)

1 Like

ooft editting these values is really annoying, thanks for the code example, i really needed that

1 Like

Might be easier to just use align orientation with the primary axis property.

1 Like

How do i set like a default orientation for it to aim for

1 Like

align orientation only seems to work if you have 2 attachments? im only using one

Yeah, you can just create an attachment in Terrain then with the primary axis property it will align the skateboard attachment0 with the terrain attachment0 on the y axis only.

I think i got the force working, instead of using a serverscript i put it on a local script but it still gets confused when youre upright and keeps giving you force, also i have to ask if you know how to make it more responsive. Im not sure if i want to mess with using the orientation because that seems like it is more instant and sort of clunky / not dynamic idk i could be wrong

heres a good example of it ocellating but really slowly and sluggishly

Here is an example of aligning the y axises only with align orientation. There is also no oscillation because align orientation solves the dampening problem already:

local attachTerrain = Instance.new("Attachment",workspace.Terrain)
attachTerrain.WorldCFrame = CFrame.fromOrientation(0,0,math.rad(90))

attachment.CFrame = CFrame.fromOrientation(0,0,math.rad(90))
local alignOrientation = Instance.new("AlignOrientation")
alignOrientation.MaxTorque = math.huge/2
alignOrientation.PrimaryAxisOnly = true
alignOrientation.Attachment0 = attachment
alignOrientation.Attachment1 = attachTerrain
alignOrientation.Parent = part

Align orientation sort of works like a bodygyro which isnt what im look for, in this video you can see that the player wont be placed on the ramp correctly

vs using the vectorforce

Now we are adding ramps to the equation?

Well the current method of setting the target orientation goal to 0 wouldn’t work too well, because the person will be tilting on the ramp like so:

image

In the meanwhile you can more readily fix the align orientation method by applying less force or even raycasting downwards to get the new surface normal.

I’m not sure if you’re still having trouble with this, but if you used Rays, you might be able to solve your ramp problem. BodyGyros are absolutely the way to go, but you’d need to fire a ray down from the character to set it.

One way you could do this is like this:

local origin = root.Position
local direction = Vector3.new(0, -4, 0)
local params = RaycastParams.new()
params.FilterType = Enum.RaycastFilterType.Blacklist
params.FilterDescendantsInstances = {character}
local result = workspace:Raycast(origin, direction, params)

if result then
bodyGyro.CFrame = CFrame.new(Vector3.new(), result.Normal) * CFrame.Angles(math.rad(-90), 0, 0)
end

Just replace the variables with whatever you’re using in your script and it should work. If not, I have a few other ideas that might work. I’ve never worked with RoPID before, so I don’t know much about that. I’ll try to learn it, so I can help you if the BodyGyro doesn’t work.

Oh, I forgot one other thing. You’ll want to set the BodyGyro’s MaxTorque to Vector3.new(10000, 0, 10000) or some other big numbers. The middle value’s gotta be 0, though, so you can turn.