"Rotating" a vector3 using another vector3

I am trying to make a “custom movement” system. It is meant to simply let you change directions instantly mid air through the use of HumanoidRootPart.Velocity . It uses the movement detection in PlayerModules to detect movement.

The problem I am having is with the maths. It is a pretty silly thing to post about however I am lost as to how I can fix it. The script mainly uses 3 parameters.
One is the magnitude (basically defining the speed / what the final velocity is multiplied by.) The player’s CFrame.LookVector and finally the direction of the pressed button.

Expanding on the last parameter it is a vector3 value which is determined as seen below

--[[
		N
   NW		NE

W				E

   SW		SE
		S
]]--

local E = Vector3.new(1,0,0)
local W = Vector3.new(-1,0,0)
local N = Vector3.new(0,0,-1)
local S = Vector3.new(0,0,1)
local NW = N + W
local NE = N + E
local SE = S + E
local SW = S + W

^^ These are sent to the serverscript as “dir” and are chosen by controlModule:GetMoveVector()

This is the math for the velocity

local replicatedStorage = game:GetService("ReplicatedStorage")
local movementEvent = replicatedStorage:WaitForChild("Movement")
local runService = game:GetService("RunService")

local mag = 40

movementEvent.OnServerEvent:Connect(function(player, veloPart, dir)
	runService.Heartbeat:Wait()
	local lookVec = veloPart.CFrame.LookVector
	local final = (mag * dir)
	print(final)
	if dir ~= Vector3.new(0, 0, 0) then
		veloPart.Velocity = final
	end
end)

I have tried the obvious dir * LookVector but got a weird phenomenon. As simple as I can describe it is depending on the way you look the different keys made the character go different ways. (Not random but weird.)
I have also tried dividing, adding, subtracting, modding ("%" , just gave an error, not even sure what it does.)

Any other extra information I could give:
If the character is stationary the vector3 given is simply 0,0,0.
The movement speed is set to 0 but this does not effect the detection.
Jumping is pretty crooked but I intend to fix that later.

Hopefully I was as descriptive as I need to be and thanks in advance.
Edit: Fixed some spelling. Sorry :smile:

3 Likes

Couldn’t you use Humanoid.MoveDirection instead of “dir”?

movementEvent.OnServerEvent:Connect(function(player, veloPart, MoveDirection)
    runService.Heartbeat:Wait()
    veloPart.Velocity = veloPart.CFrame.LookVector * MoveDirection * mag
end)
1 Like

This is significantly closer to what I am looking for! However the movement still seems to be broken. Forward and backward are switched (though I am not concerned about that right now) and left right seems to be broken completely. They fluctuate again with rotation of the camera. I can attach a video if you wanted me to. I will continue to experiment with humanoid.MoveDirection instead of dir. This has been very helpful, thank you so much!

Edit: expanding on the forward and backward being switched, it seems to change depending on the way you are looking. Very similar to the left and right. The forward and backward switch every 90 degrees of turning with very uneven reactions with everything in between.

Edit 2: I believe this is because the MoveDirection practically creates a closed loop. The velocity effects the move direction so it ends up feeding itself. The reason I used “dir” was because it is more keystroke based than movement.

Edit 3: I keep on editing but
If its of any use, this is how I determined “dir”

-- The vars from above in this post and services
local dir = Vector3.new(0, 0, 0)

RunService:BindToRenderStep("checkdir",Enum.RenderPriority.Last.Value + 1,function()
	local moveVec = controlModule:GetMoveVector()
	if moveVec == N then
		print("Forward")
		dir = N

	elseif moveVec == W then
		print("Left")
		dir = W

	elseif moveVec == S then
		print("Backward")
		dir = S

	elseif moveVec == E then
		print("Right")
		dir = E

	elseif moveVec == NW then
		print("Front + Left")
		dir = NW

	elseif moveVec == NE then
		print("Front + Right")
		dir = NE

	elseif moveVec == SW then
		print("Back + Left")
		dir = SW

	elseif moveVec == SE then
		print("Back + Right")
		dir = SE

	else	
		print("Stationary")
		dir = Vector3.new(0, 0, 0)
	end
	movementEvent:FireServer(dir, humanoid, humanoidRootPart)	
end)

It’s not the most efficient way surely but it works.

A custom movement system should really be performed on the client for several reasons, including but not limited to: values like Humanoid | Roblox Creator Documentation are not replicated to the server across the client-server boundary, the players character physics are controlled by the client by default, so any physics operation done on them must be performed by the client, you will have access to the clients input for better and easier control, you don’t have to deal with latency for character movement, and anything done to the client will be replicated to other clients anyways.

A system like you have will move the character in a direction relative to the world. If you don’t know what this means then you can check of the Object and World Space | Roblox Creator Documentation article. Anyways, like I was saying to account for the direction relative the players object space you can simply use subtract the position by the move direction. Like @Windsonnes mentioned, since you aren’t accounting for the vertical direction you can just use the Humanoid.MoveDirection. Also, you will need a way to detect when the player has landed and is no longer in mid-air. You can do this using Humanoid | Roblox Creator Documentation. You can run this code inside of your RunService | Roblox Creator Documentation loop like so:

local emptyVector = Vector3.new(0, 0, 0) -- defined so we don't create a new vector each render step

local movePower = 40

local function customAirMove()
	if humanoid.MoveDirection ~= emptyVector then
		local moveVector = ((humanoidRootPart.Position + humanoid.MoveDirection) - humanoidRootPart.Position).Unit -- gets the relative direction
		
		humanoidRootPart.AssemblyLinearVelocity = moveVector*movePower
	end
end

humanoid.StateChanged:Connect(function(_, new) -- listen for when the humanoid lands
	-- we are not checking for the `old` humanoidstatetype because we do not need to compare the new value to the old value in this context
	if new == Enum.HumanoidStateType.Freefall then
		RunService:BindToRenderStep("checkdir", Enum.RenderPriority.Last.Value+1, customAirMove) -- enable the custom move whenever the player is in mid-air
	elseif new == Enum.HumanoidStateType.Landed then
		RunService:UnbindFromRenderStep("checkdir") -- disable the custom move
		humanoidRootPart.AssemblyLinearVelocity = emptyVector -- disable any acting forces on the root part
	end
end)
  • Note: This script is meant to run on the client. Also, a movement system like this can be exploited, so you will have to perform sanity checks on the server.

Also, BasePart | Roblox Creator Documentation is deprecated, which is why I used BasePart | Roblox Creator Documentation in the code. You can use BasePart.AssemblyLinearVelocity or any of the other methods it mentions in the documentation for BasePart.AssemblyLinearVelocity.

On another note, the % operator is the modulus operator, aka the modulo operator. It an operation for finding the remainder of the division two numbers, a and n. It is useful as it returns the result you would get from either long division or synthetic division alike. One of the most common uses of it is to check whether a real number is odd or even (Although to check negative values you have to use the absolute value). This can be done by finding the remainder of a divided by n. For example, if a is 12, then 12/2=6. Because this divides evenly (meaning there are no decimal values) then the remainder is 0. However, lets say that a is equal to 13. Now, 13/2=6.5. Because the result is not evenly divided (the 0.5 at the end) this means that the remainder is 1. A function to check if a number is even could be written like this:

local function isEven(a)
	return math.abs(a)%2 == 0;
end
P.S.

Sorry if this reply seems out-of-order and is hard to follow. I was just writing things down as I noticed them.

4 Likes

Oh the humanity! How your fingers must ache!

Being serious, it is impossible to rotate a Vector3 with another Vector3 simply because of what a vector is: A Direction and a Length. If you where to rotate the direction you will realize that there are only 2 ways of rotating and not three.

It is possible to rotate a Vector but not with another Vector.

However, it is possible to rotate a CFrame using a Vector3 as an Axis of rotation. Quaternions can also do that.
There are 2 ways of creating Rotating using this in Roblox

CFrame.new(0,0,0,qX,qY,qZ,qW) -- Where qX,qY,qZ is the Vector for rotation and qW is the angle
-- or
CFrame.AxisAngle(Vector3,Angle)

I have created a bunch of Character Controllers in the past and found this piece of code.

local function GetMoveVector()
	return Vector3new(
		(UserInputService:IsKeyDown(Enum.KeyCode.A) and -1 or 0) + (UserInputService:IsKeyDown(Enum.KeyCode.D) and 1 or 0),
		0,
		(UserInputService:IsKeyDown(Enum.KeyCode.W) and -1 or 0) + (UserInputService:IsKeyDown(Enum.KeyCode.S) and 1 or 0)
	)
end

Which creates a movement Vector based on which key is pressed. Mapping keys like you did I found was very tiring for my fingers.

Usually you would have CharacterControllers client sided as having the server compute the movement for all players is unnecessary.

% Modulo is a remainder operator which gets the remainder of the division.

9 % 2 = 1 
-- Which is basically doing
9 / 2 = 8.5
9 - math.floor(8.5) = 1
1 Like

Thank you so much for this! Not only did you clear many things up for me but also solved my problem. I will implement this right away. The reason I was running a server script was partially the reason you mentioned which was the fact that local scripts are easily exploitable. I was thinking even if they managed to send the event through it wouldn’t matter too much since the math would’ve been done server side. Lastly, thank you for explaining how the “%” works, I will likely overuse this in every project now.
Thanks again, I know I said it so much but after many hair tugging hours I finally have a clear explanation. I never thought about checking if the player was on the floor - this will help alot with the new implementations I have made.

P.S.
For the record I did not think it was out-of-order or was hard to follow :smile:

As I was writing a reply to @BuilderBob25620 I saw another answer pop up, hahahaha.
Yeah I knew that it was a very long way of doing it but I wanted to get a working “prototype” ready before trying to optimise the code. This has been just as helpful, thank you!

1 Like