Move relative to camera

I am trying to make a scooter move and rotate appropriately relative to the camera given some move vector, but I just can’t get it to work! I’ve been at this for days so I am just so frustrated with this. I made it work using a LinearVelocity constraint but that gave me many physics-related problems and so I’ve been trying to use a VectorForce constraint but I’m not sure how to make it work.

NOTICE: I have edited the original post on January 7, 2024 to include the latest code

Just to be clear, I am open to suggestions on other ways to accomplish this because I am having a difficult time with my methods.

These are the relevant game objects:

-- get local player
local player = Players.LocalPlayer

-- get the master control module script which roblox puts into player scripts
-- by default
local PlayerModule = require(player.PlayerScripts.PlayerModule)
local MasterControl = PlayerModule:GetControls()

-- get scooter model
local scooter: Model = workspace:WaitForChild("Scooter-" .. player.Name)

-- get the physics box which is the part of the scooter that interacts with
-- the environment; basically the hitbox
local scooterPhysicsBox: Part = scooter:WaitForChild("ScooterPhysicsBox")

-- get the LinearVelocity constraint
local linearVelocity: LinearVelocity = scooter:WaitForChild("ScooterLinearVelocity")

I have some configurable values:

local maxMoveSpeed = 55

-- this should be interpreted as reaching the maxMoveSpeed from rest in 1.3 seconds
-- it is also the rate at which the scooter should decelerate
local acceleration = maxMoveSpeed / 1.3

-- this should be interpreted as a :Lerp() alpha of 0.05 at 60 FPS
local lerpInterpolationAlpha = 0.05 * 60

Every frame, I calculate some target linear velocity as well as the projected move vector, where the move vector is just the value I get from the MasterControl module’s :GetMoveVector(). By projecting it relative to the camera, I mean making the move vector’s Z component align with the camera’ look vector and the move vector’s X component align with the camera’ right vector. Note that the VectorForce is set to be relative to the Attachment0 so it is always inline with the scooter’s look vector.

local movement = {
  throttle = 0, -- represents whether the scooter should move forward, backward, or neither
  projectedMoveVector = Vector3.zero -- the projected move vector
}

local function calculateMovement()
  local cameraLookVector = workspace.CurrentCamera.CFrame.LookVector
  local cameraRightVector = workspace.CurrentCamera.CFrame.RightVector

  local scooterLookVector = scooterPhysicsBox.CFrame.LookVector

  local moveVector = MasterControl:GetMoveVector()

  -- project move vector in 3D space to align with camera look and right vector
  projectedMoveVector = -moveVector.X * cameraRightVector + moveVector.Z * cameraLookVector

  -- determine whether scooter should move forward (1), backward (-1), or become at rest (0)
  movement.throttle = -math.sign(projectedMoveVector:Dot(scooterLookVector))

  -- update projected move vector
  movement.projectedMoveVector = projectedMoveVector
end

Based on the calculated projectedMoveVector, I want to rotate the scooter. This is also done every frame. In case you are interested, I first align the scooter with the surface it is on with the method I learned from this post on the DevForum:

-- helper function to align with surface as per the post
local function calculateReorientationAngle(alignment: Vector3): number
  return math.acos(scooterPhysicsBox.CFrame.UpVector:Dot(alignment))
end

-- helper function to align with surface as per the post
local function determineReorientationAxis(alignment: Vector3, angle: number): Vector3
  local axis: Vector3

  if angle < 0.00001 or angle > math.pi - 0.00001 then
    axis = Vector3.xAxis
  else
    axis = scooterPhysicsBox.CFrame.UpVector:Cross(alignment)
  end

  return axis
end

Using those methods, I attempt to rotate the scooter:

local function updateScooterOrientation(deltaTime: number, surfaceNormal: Vector3?)
  -- declare some target CFrame
  local targetCFrame: CFrame

  -- align with surface as per the post
  if surfaceNormal ~= nil then
    local angle = calculateReorientationAngle(surfaceNormal)
    local axis = determineReorientationAxis(surfaceNormal, angle)

    targetCFrame = CFrame.fromAxisAngle(axis, angle) * scooterPhysicsBox.CFrame.Rotation + scooterPhysicsBox.Position
  else
    local targetUpVector = Vector3.new(0, 1, 0)

    local angle = calculateReorientationAngle(targetUpVector)
    local axis = determineReorientationAxis(targetUpVector, angle)

    targetCFrame = CFrame.fromAxisAngle(axis, angle) * scooterPhysicsBox.CFrame.Rotation + scooterPhysicsBox.Position
  end

  -- change target CFrame to be the CFrame after a lerp
  targetCFrame = scooterPhysicsBox.CFrame:Lerp(targetCFrame, lerpInterpolationAlpha * deltaTime)

  if movement.projectedMoveVector ~= movement.projectedMoveVector or movement.projectedMoveVector.Magnitude == 0 then
    -- if projected move vector is NaN or has magnitude of zero, just align with the surface. don't rotate any more
    alignOrientation.CFrame = targetCFrame
  else
    -- rotate the scooter using the projected move vector
    local lookVectorYComponent = (-movement.projectedMoveVector.X * targetCFrame.UpVector.X - movement.projectedMoveVector.Z * targetCFrame.UpVector.Z) / targetCFrame.UpVector.Y
    local lookVector = Vector3.new(movement.projectedMoveVector.X, lookVectorYComponent, movement.projectedMoveVector.Z)
    local rightVector = targetCFrame.UpVector:Cross(lookVector)

    alignOrientation.CFrame = CFrame.fromMatrix(
	  scooterPhysicsBox.Position, -- position
	  rightVector, -- right vector
	  targetCFrame.UpVector, -- up vector
	  -lookVector -- -look vector
    )
  end
end

However, the results are completely off as seen in this clip and I have no idea how to fix it:

4 Likes

bumping since it’s been an hour; currently rewriting the code to see if I can figure out a solution but im mainly bumping in case this post gets buried

Bumping once more; still trying to get it to work

You should consider setting up the scooter with an AlignOrientation on the relevant axis for the balancing problem.

2 Likes

I agree with @Den_vers , align orientation will also allow for physics to be applied to the scooter.

1 Like

Thank you for your response! I’m going to try the AlignOrientation with my overall-working code that utilizes the LinearVelocity constraint. Rewriting to use a VectorForce has been particularly challenging for me. But one more thing: I read the documentation and it seems like for AlignOrientation, the main thing is setting some target CFrame that the constraint will attempt to match. How can I find that CFrame using the following knowledge:

  • the scooter’s target up vector (the surface normal or just the world up vector if the scooter is in the air)
  • the projected move vector (the vector I want to essentially attempt to align the scooter’s look vector with)

So what I want to do is essentially change the scooter’s look vector to have the same X and Z components of the projected move vector, but I don’t know what the Y component what have to be in order for it to still match the target up vector.

local function updateTargetOrientation(targetUpVector: Vector3, projectedMoveVector: Vector3)
  local lookVectorYComponent = -- TODO: calculate the y component of the new look vector to be perpendicular to the up vector  
  local targetLookVector = Vector3.new(projectedMoveVector.X, lookVectorYComponent, projectedMoveVector.Z)

  local targetCFrame = -- TODO: Calculate target CFrame
  alignOrientation.CFrame = targetCFrame
end

When I did the math, I ended up with some very hairy terms so I probably did something quite wrong especially considering that when I plugged them in via CFrame.fromMatrix I didn’t get the expected results :sweat_smile: (no need to concern yourself if you’re not interested; just showing this to show that I tried doing it on my own)

Note that here, the variable Y represents the target Y component of the look vector. r_x is the right vector X component. r_y is the right vector Y component. r_z is the right vector Z component. m_x is the projected move vector X component. m_z is the projected move vector Z component. u_x is the target up vector X component. u_y is the target up vector y component. u_z is the target up vector z component. I ended up with these expressions using the following relationships of dot products and cross products:
u • r = 0
u • l = 0
r • l = 0

u X r = l
u X l = r
r X l = u

where:
u = <u_x, u_y, u_z>
r = <r_x, r_y, r_z>
l = <m_x, y, m_z>

Bumping bc this topic got buried overnight for me

I redid the math to find the y component of the new look vector. The dot product of two perpendicular vectors is zero. And I know that the look vector should be perpendicular to the up vector.
Let’s say u is the up vector, l is the look vector, and m is the projected move vector:
u = <ux, uy, uz>
l = <mx, y, mz>
So I have this equation:
ul = 0
which can be rewritten as:
ux • mx + uy • y + uz • mz = 0
then I can isolate the variable y to solve for it in terms of the other knowns:
y = (-ux • mx -uz • mz) / uy

In code, this looks like:

local function updateTargetOrientation(targetUpVector: Vector3, projectedMoveVector: Vector3)
  local lookVectorYComponent = (-targetUpVector.X * projectedMoveVector.X - targetUpVector.Z * projectedMoveVector.Z) / targetUpVector.Y
  local targetLookVector = Vector3.new(projectedMoveVector.X, lookVectorYComponent, projectedMoveVector.Z)

  local targetCFrame = -- TODO: Calculate CFrame
  alignOrientation.CFrame = targetCFrame
end

But as you see above I’m still not sure how to correctly get the targetCFrame for the AlignOrientation constraint. I tried:

local targetCFrame = CFrame.fromMatrix(
  scooterPhysicsBox.Position, -- position
  targetUpVector:Cross(lookVector), -- right vector
  targetUpVector, -- up vector
  -lookVector -- -look vector
)

But it does not work as expected:

I updated the original post to include the latest, most relevant code and the above clip

it seems that part of the issue was with attachment orientations. I changed them up and the result is a lot better: it moves in the correct direction, but it faces the opposite way that it should

I will attach a quick video soon

You are making this way too complicated, why don’t you use alignposition and alignorientation like the guy above said, it works really smooth.

I am using align orientation and you’re right; it works beautifully. The reason I need that math is because I need to figure out every frame what exact direction the scooter should face so that I can set the align orientation to be in that direction

Well the alignorientation just aligns one part with an other or an attachment. In my mind I just imagine that you put the part where the camera should be in the character so it is relative to him without any code and then you just use the math to turn the scooter and the part with alignorientation does the rest with the camera without any code
Correct me if im wrong

here’s the quick video; you will see it moves in the direction expected, but it turns the opposite way ;-;. I am still using the same code; I just changed attachment orientations

Easy fix, just turn the attachment or part by 180 degrees (i hope its that easy).

turning it around for some reason makes the scooter just spin forever

Could you provide a video or something?

I have an idea; reversing the right vector