Vectorial inertia cancellation

Backstory
Let’s say you want to make a hoverboard that allows the player to levitate above the ground, being able to move freely throughout the place.

The plan
First off, you make a script that passes the network ownership to the player that will control the hoverboard. This script will also take care of the hovering feature, as the localscript has to control movement only.
You make the local script which will link player’s control inputs to hoverboard’s movement.
Instead of using tedious BodyPosition object to control the movement, you want to use the classic BodyForce, which will update constantly to match the desired location.
Inputs/movement keys: classic A, S, D, W, in addition to E(up) and Q(down).

The localscript
To make the hoverboard move, however, we will declare our variable “move” as our BodyForce instance that will move around our vehicle, as shown:

local direction = cfr.lookVector
move.Force = direction * -- direction
3000 * ((pressedButton['W'] and 1 or 0) - (pressedButton['S'] and 1 or 0)) + -- fw/bw relative to direction
3000 * Vector3.new(0,(pressedButton['E'] and 1 or 0) - (pressedButton['Q'] and 1 or 0),0) - --up/down
((pressedButton['W']==false and pressedButton['S']==false) and (function() -- no key = stop
    return Vector3.new(vel.X,0,vel.Z)*200
 end)() or Vector3.new(0,0,0))

It may look complicated, so let’s break it down a bit.
Firstly, we create our “direction” variable which represents the forward-faced vector of our main part, where cfr represents our part’s rotation matrix.
We also have a dictionary of pressed buttons, where values are true/false, depending by the buttons are pressed or not. We use it in this structure as so: (pressedButton['W'] and 1 or 0), which spits out 1 if the button is pressed, and 0 if it isn’t.
Next, we want to set the movement force to part’s forward, following by the amount of thrust (3000) on the forward/backward direction, so we do the differention: ((pressedButton['W'] and 1 or 0) - (pressedButton['S'] and 1 or 0)), which will give us 1 (for forward), -1 (for backward), or 0 (if both are pressed or released).
Similarly, we do the same for up and down movement types: (0,1,0) for going up, (0,-1,0) for going down.
Even though it may not seem that obvious what’s going on, the third part consists of a function which is being called and gives off in reverse of part’s velocity, multiplied by the thrust of 200, to prevent it from sliding when no keys are pressed, on X and Z axes (excluding up and down).
Note: A, D keys only rotate the board cw or acw, so we’re not discussing it here, as it’s not the point of the tutorial.

The problem
Great, it works! Now what ?
Well, you see, when the player turns its hoverboard to either left or right, an inertia makes it slide off, which isn’t too good.
We need to fix that!

The approach
To cancel out he inertia, we need to calculate the inertia component, from the part’s velocity, which coincidentally is made from our right vector component, grabbed from the part’s rotation matrix, as shown:

local vel = board.PrimaryPart.AssemblyLinearVelocity
local rightVector = cfr.rightVector

Next, form the large formula above, we subtract the side inertia component, making use of a function call:

(function() -- side inertia cancellation
    if pressedButton['W']==false and pressedButton['S']==false then return Vector3.new() end
    local result = vel:Dot(rightVector) * rightVector * 700
    return result
end)()

This function only works when the player wants to turn the board, so any of those keys(W, S) have to be pressed.
Our rightVector is a unit. It gets multiplied by the dot product of the part’s velocity with it, which results in our side inertia component of velocity. In addition, we multiply 700 as the “strength” of our cancellation. The bigger this value is, the stronger the effect will be.

Bonus
Let’s say the player makes a 180 degree turn while moving and then he wants to accelerate forward. Instead of sliding back, willing to go forward, we need to make a change to our formula, so we can minimize the back inertia, by subtracting it, just like we did with side inertia.

(function() -- backward inertia cancellation
    pressedButton['W']==false then return Vector3.new() end
    local dot = vel:Dot(direction)
    if dot < 0 then
        return dot * direction * 700
    end
    return Vector3.new()
end)()

This time, the dot production is negative when going backwards and positive when forward, so we test if the player wants to go forward, but he is sliding backwards. So this is where return dot * direction * 700 comes into play and cancels out the inertia just like previously.

Done!
There you have it. A control movement snippet, able to make corrections over inertia on both sideways and back directions.
The full snippet looks like this, when put together:

local cfr = board.PrimaryPart.CFrame
local direction, rightVector = cfr.lookVector, cfr.rightVector
local vel = board.PrimaryPart.AssemblyLinearVelocity
move.Force = direction * -- direction
    3000 * ((pressedButton['W'] and 1 or 0) - (pressedButton['S'] and 1 or 0)) + -- fw/bw relative to direction
    3000 * Vector3.new(0,(pressedButton['E'] and 1 or 0) - (pressedButton['Q'] and 1 or 0),0) - -- up/down

    ((pressedButton['W']==false and pressedButton['S']==false) and (function() -- no key = stop
        return Vector3.new(vel.X,0,vel.Z)*200
    end)() or Vector3.new(0,0,0)) -

    (function() -- side inertia cancellation
        if pressedButton['W']==false and pressedButton['S']==false then return Vector3.new() end
        local result = vel:Dot(rightVector) * rightVector * 700
        return result
    end)() -

    (function() -- backward inertia cancellation
        if pressedButton['W']==false then return Vector3.new() end
        local dot = vel:Dot(direction)
        if dot < 0 then
            return dot * direction * 700
        end
        return Vector3.new()
    end)()

Hope it helps,
-Whos :slight_smile:

10 Likes

This tutorial was great, a thing I would like to say is that I really liked the way you used the “and” ans “or”, because I nearly never see people using it and just recently I found out about it.

If anyone even has a doubt about that, its pretty much like this:

“and” searches for the false values, so if the first one is true then the second value will be returned regardless of if it is true or not;

Meanwhile “or” does the inverse, it searches for the true values and if the first one is false then the second value will be returned regardless of if it is true or not.

And since they work only with 2 values then to make it simplier, it would look like this (the OP pretty much explained this):

local value = (pressedButton['W'] and 1) or 0

You dont need to use it like this since doing pressedButtom[‘W’] and 1 or 0 does the same, as explained, its because the “or” and the “and” works with only 2 values so the or will take in account the value returned by the pressedButton[‘W’] and 1 instead of the 0.

2 Likes