Part movement System

  1. What do you want to achieve? Keep it simple and clear!
    I’m writing a movement system for a part. I move the part using a bodyVelocity. My client code is this:
local player = game.Players.LocalPlayer
local cells = workspace:WaitForChild(player.UserId .. "_cells")
local mainBCell = cells.mainBCell
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local UpdateCellEvent = ReplicatedStorage:WaitForChild("UpdateCellEvent")
-- In order to use the InputBegan event, the UserInputService service must be used
local UserInputService = game:GetService("UserInputService")
--Toggle bool to prevent mass firing
local a = false
-- A sample function providing multiple usage cases for various types of user input
-- right = 0,0, 2 left is 0,0,-2
--down = -2,0,0
UserInputService.InputBegan:Connect(function(input, gameProcessed)
	if input.UserInputType == Enum.UserInputType.Keyboard then
		a = true
		if input.KeyCode == Enum.KeyCode.W then
			UpdateCellEvent:FireServer("W")
		elseif input.KeyCode == Enum.KeyCode.A then
			UpdateCellEvent:FireServer("A")
		elseif input.KeyCode == Enum.KeyCode.S then
			UpdateCellEvent:FireServer("S")
		elseif input.KeyCode == Enum.KeyCode.D then
			UpdateCellEvent:FireServer("D")
		end
	end
 
end)

UserInputService.InputEnded:Connect(function(input)
	local ReplicatedStorage = game:GetService("ReplicatedStorage")
	local UpdateCellEvent = ReplicatedStorage:WaitForChild("UpdateCellEvent")
	--Fire Server
	if a == true then
		--Stop movement
		if input.UserInputType == Enum.UserInputType.Keyboard then
			if input.KeyCode == Enum.KeyCode.W then
				UpdateCellEvent:FireServer("STOPW")
			elseif input.KeyCode == Enum.KeyCode.A then
				UpdateCellEvent:FireServer("STOPA")
			elseif input.KeyCode == Enum.KeyCode.S then
				UpdateCellEvent:FireServer("STOPS")
			elseif input.KeyCode == Enum.KeyCode.D then
				UpdateCellEvent:FireServer("STOPD")
		end
	end
		a = false
	end
end)

My server code is this:

--Update Cell Event
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local UpdateCellEvent = Instance.new("RemoteEvent", ReplicatedStorage)
UpdateCellEvent.Name = "UpdateCellEvent"
 --								  class,  Vector3,  bool
local function onUpdateCellFired(player, input)
	local cells = workspace:WaitForChild(player.UserId .. "_cells")
	local mainBCell = cells.mainBCell
	local bodyVelocity = mainBCell.BodyVelocity
	local stop = false
	local cord = ""
	local newVelocity = Vector3.new(0,0,0)
		if input == "W" then
			newVelocity = Vector3.new(2, 0, 0)
		elseif input == "STOPW" then
			newVelocity = Vector3.new(-2, 0, 0)
			cord = "X"
			stop = true
		end
		if input == "A" then
			newVelocity = Vector3.new(0, 0, -2)
		elseif input == "STOPA" then
			newVelocity = Vector3.new(0, 0, 2)
			cord = "Z"
			stop = true
		end
		if input == "S" then
			newVelocity = Vector3.new(-2, 0, 0)
		elseif input == "STOPS" then
			newVelocity = Vector3.new(2, 0, 0)
			cord = "X"
			stop = true
		end
		if input == "D" then
			newVelocity = Vector3.new(0, 0, 2)
		elseif input == "STOPD" then
			newVelocity = Vector3.new(0, 0, -2)
			cord = "Z"
			stop = true
		end
		bodyVelocity.Velocity = bodyVelocity.Velocity + newVelocity
		if stop == true then
			if cord == "X" then
				bodyVelocity.Velocity = bodyVelocity.Velocity * Vector3.new(0,1,1)
			elseif cord == "Z" then
				bodyVelocity.Velocity = bodyVelocity.Velocity * Vector3.new(1,1,0)
			end
		end
end
 
UpdateCellEvent.OnServerEvent:Connect(onUpdateCellFired)
  1. What is the issue?
    Essentially, I want the player to be able to move Northeast (Up and right) using keys (Or a mobile joystick, but I don’t know how to do that yet.). In my current setup, this is possible, however it does have one issue. Lets say the player presses W, and then A. His/Her part moves Northeast. Then let’s say the said player lets go of A. Next they let go of W. Their part is still traveling upwards (on the screen). Please note:
    I am using a top-down camera and the part’s orientation is 0,0,90.
    This issue leads to the player being able to travel an infinite speed, because when the player in the situation above presses W down again, they travel double the speed infinitely.
  2. What solutions have you tried so far?
    I have tried to implement a “KeysDown” count, but that didn’t work (and it’s also not clean enough).

My original system stopped completely when a key (one of WASD) went up, but that didn’t let them go Northeast and then just North.

What can I do to fix this, but still let it be that the movement above (Last sentence) is still possible?

Thanks Everyone!
Someperson576

I have a recording too if this helps: https://gyazo.com/299a4fc5c0c3a6604c5176e3dd3fc0cb. Hopefully someone has ideas!

Instead of sending individual events for which key is pressed or not, then perhaps send the client’s “combined movement choice”.

That is, have multiple variables / fields on the client for each individual movement direction, which is then combined into “one single value” that is send to the server.

The “easiest” way I know of to do that, is by using bit-sets (bit array). So for example “bit 0” specifies if key-W is pressed or not, then “bit 1” specifies if key-S is pressed or not, “bit 2” key-A, “bit 3” key-D, etc.

Each bit value is then combined into an integer value, and this single integer value, if it has changed, is sent to the server.

The server then decodes the integer value, taking into account any conflicting movement directions - i.e. pressing key-A and key-D will result in no movement for the left and right direction.

Below I have made an example of a LocalScript and a server Script, to better illustrate my suggestion.

-- Client-side
local UserInputService = game:GetService("UserInputService")
local UpdateCellEvent = game:GetService("ReplicatedStorage"):WaitForChild("UpdateCellEvent")

local MovementValues = {
	[Enum.KeyCode.W]		= 2^0,	-- 1, Forward, North
	[Enum.KeyCode.S]		= 2^1,	-- 2, Backward, South
	[Enum.KeyCode.A]		= 2^2,	-- 4, Left, West
	[Enum.KeyCode.D]		= 2^3,	-- 8, Right, East
	[Enum.KeyCode.Space]	= 2^4,	-- 16, Jump, Up
	[Enum.KeyCode.C]		= 2^5,	-- 32, Crouch, Down
}

local currentPressedKeys = 0
local sentPressedKeys = 0

UserInputService.InputBegan:Connect(function(input, gameProcessed)
	local value = MovementValues[input.KeyCode]
	if nil ~= value then
		currentPressedKeys = bit32.bor(currentPressedKeys, value)
		if sentPressedKeys ~= currentPressedKeys then
			UpdateCellEvent:FireServer(currentPressedKeys)
			sentPressedKeys = currentPressedKeys
		end
	end
end)

UserInputService.InputEnded:Connect(function(input, gameProcessed)
	local value = MovementValues[input.KeyCode]
	if nil ~= value then
		currentPressedKeys = bit32.band(currentPressedKeys, bit32.bnot(value))
		if sentPressedKeys ~= currentPressedKeys then
			UpdateCellEvent:FireServer(currentPressedKeys)
			sentPressedKeys = currentPressedKeys
		end
	end
end)
-- Server-side
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local UpdateCellEvent = Instance.new("RemoteEvent")
UpdateCellEvent.Name = "UpdateCellEvent"
UpdateCellEvent.Parent = ReplicatedStorage

--
local MovementValues = {
	Forward		= 2^0,	-- 1, Forward, North
	Backward	= 2^1,	-- 2, Backward, South
	Left		= 2^2,	-- 4, Left, West
	Right		= 2^3,	-- 8, Right, East
	Jump		= 2^4,	-- 16, Jump, Up
	Crouch		= 2^5,	-- 32, Crouch, Down
}

local function onUpdateCellFired(player, inputBits)
	local debugMovements = {}
	
	local forwardBackward = bit32.band(inputBits, MovementValues.Forward + MovementValues.Backward)
	if forwardBackward == MovementValues.Forward then
		table.insert(debugMovements, "Forward")
	elseif forwardBackward == MovementValues.Backward then
		table.insert(debugMovements, "Backward")
	else
		-- Neither of the two above
	end
	
	local leftRight = bit32.band(inputBits, MovementValues.Left + MovementValues.Right)
	if leftRight == MovementValues.Left then
		table.insert(debugMovements, "Left")
	elseif leftRight == MovementValues.Right then
		table.insert(debugMovements, "Right")
	else
		-- Neither of the two above
	end
	
	local jumpCrouch = bit32.band(inputBits, MovementValues.Jump + MovementValues.Crouch)
	if jumpCrouch == MovementValues.Jump then
		table.insert(debugMovements, "Jump")
	elseif jumpCrouch == MovementValues.Crouch then
		table.insert(debugMovements, "Crouch")
	else
		-- Neither of the two above
	end
	
	print("Movement is: " .. table.concat(debugMovements, " + "))
end

UpdateCellEvent.OnServerEvent:Connect(onUpdateCellFired)
2 Likes

Thank you for the reply! I’ll be testing in the next few days and will get back to you as soon as possible!
I do have two questions though:
How do I stop the movement? I.e. remove from debug movement.
Also, would I use table.foreach() to iterate through and add movement to the body velocity?

Ehh. No, you should definitively not use the debugMovements array directly. What I show in my example code, is basically an attempt for you to “think differently”.

Try to think in the individual axes values X, Y, Z. Why would you explicitly create a new Vector3 object for each received input-type?

How about “just” set each of the 3 individual axes to; neutral (not moving), positive or negative - and then afterwards combine those three axes into a Vector3 object, that you could use for the next calculations.

-- Pseudo code for illustration, this is NOT real Lua code
local x,y,z
if is-moving-forward then
  z = -1
elseif is-moving-backwards then
  z = 1
else
  z = 0
end
if is-moving-left then
  x = -1
elseif is-moving-right then
  x = 1
else
  x = 0
end
if is-moving-up then
  y = 1
elseif is-moving-down then
  y = -1
else
  y = 0
end
bodyVelocity.Velocity = Vector3.new(x,y,z)
...
1 Like

Thank you! This perspective has been very helpful, I will mark your first post as the solution after we finish this conversation. My final questions for you are:

  1. What is the problem specifically with the debugMovements array?

  2. In the way your example code works, it updates the movement whenever there is input regarding those keys right?

  3. How would I convert mobile joystick movement to these directions (I. e. Forward, backward, left, right, forward and right, etc.) I know this is off-topic, but the post is named Part Movement System. If you would like to continue this conversation in messages, I will simply mark this thread as solved.

Thank you very much for your time and ideas, you have been very helpful!
Someperson576

  1. Its ‘debug’. It is not meant to be used for ‘actual running game-code’. - You, as a developer, should know the difference between “what I use to debug, trace and understand what my own code does, that it does it correctly” and “code that is only needed for the game vs. code that is only used for debugging”.

  2. Yes. - It sounds like you have not yet tried running the server-side and client-side scripts I made. You can do it in a new clean ‘base plate’ game, to see how it actually works.

  3. “Mobile joystick”? What is that? - If you are referring to ‘mobile buttons’, then it should be no different than using keyboard input.
    I have not made anything for mobile devices, so cannot say how to “create it”. - But if a ‘mobile button’ is basically a ‘on / off’-button (same as keyboard: inputBegan, inputEnded), then you could apply the same strategy on how to capture input from the player and transfer to the server.

Though if ‘mobile joystick’ is not just a digital ‘on / off’, but instead an analog adjustable floating-point value between 0.0 and 1.0, then you should re-think your strategy of how to handle player input. Because an analog value will change much more often, than a simple ‘on / off’ value.

For analog input that changes often, it would be bad to constantly transmit these changes from client to server. Instead the client-code would handle the movement itself client-side, and then let the Roblox normal replication of ‘network-owned’ parts from the-client-to-server-thats-then-broadcasted-to-all-clients do its job. - Though then it is your job, in your server-side code, to verify that players do not do something “odd”, that can result in exploits in your game.

Always remember the “do not trust the client” mantra. - Make sure you implement server-side checks and validations, of the data that the server receives from the clients.

I understand the difference between debugging and regular code. However, I have decided to store the values in an array, as this helps me to implement sanity checks on the player input. (I.e. The most the client can send normally is three keys, up, right, and “E”, so any more means exploiting.) As for “Mobile Joystick”, I would like to convert mobile “thumbstick” (I believe this is the right word.) to these directions. If possible, I would even like to have the direction the thumbstick is moving be the way the “mainBCell” is moving.

You have been very helpful!
Someperson576

Thank you @Decker004! Your help has been invaluable!