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