We use cookies on this site to enhance your user experience

Adding Player Controls - Client vs Server

Adding Player Controls - Client vs Server

Prerequisites Have completed Arcade Action - Creating a Custom Character lesson.
Lesson Time 10 - 15 minutes
Optional Handouts N/A
Learning Objectives
  • Learn the difference between Local and Server Scripts in client/server relationships
  • Use a VectorForce to apply force to the player so they can move
  • Create a local script that makes a player move by pressing a keyboard key
  • Typically, it’s not considered a good thing when ships just fall from the sky. Since this game uses a custom character, the way that character moves needs to be created. This specific ship will move whenever the player presses a keyboard button and always point at their mouse.

    Set Up Controls

    To get the ship moving, you’ll use a VectorForce object to act as the engine of your ship. VectorForce objects push whatever they are attached to in a straight line. If you attach one to the HumanoidRootPart, it can be scripted to push the ship towards the player’s mouse.

    Adding VectorForce and Attachment

    1. Under the HumanoidRootPart, add a VectorForce object and an Attachment.
    1. Set the VectorForce’s Attachment0 to the attachment created last step.

    If you test right now, the ship won’t move because the controls need to be added through scripting.

    Create the PlayerShipHandler

    By default, players can only control the normal Humanoid avatar. Everything else is controlled by Roblox’s servers. Who controls an object, whether it’s the server or the player’s computer is referred to as network ownership. Players won’t be able to pilot the ship themselves until the server is told to give network ownership to the player, which the script below will do for you.

    1. Inside of ServerScriptService, add a Script.
    2. Rename the script PlayerShipHandler.
    3. Copy and paste the code below into the script.
    --This script handles ship behavior on the server-side of the game
    
    -- Called when the character is added
    local function onCharacterAdded(character)
    	local rootPart = character:WaitForChild("HumanoidRootPart")
    
    	-- Wait until rootPart is part of the workspace		
    	while not rootPart:IsDescendantOf(workspace) do
    		wait()
    	end
    	-- Gives control of the ship to the player
    		rootPart:SetNetworkOwner(game.Players:GetPlayerFromCharacter(character))
    end
    
    -- Called when a player is added to the game
    local function onPlayerAdded(player)
    	player.CharacterAdded:Connect(onCharacterAdded)
    end
    
    -- Connect onPlayerAdded() to the PlayerAdded event.
    game.Players.PlayerAdded:Connect(onPlayerAdded)
    
    Why Use IsDescendantOf()?

    Roblox’s networking API can’t change the owner of a part unless it is in the workspace. Performing the while not loop will stop the server from setting the network owner until the player is fully loaded into the game.


    Creating Player Controls

    Before we start coding the controls, now is a good time to start talking about client vs server. There are two places a script can run from. One place is the Roblox server and the other is the client, a player’s computer or device.

    Script objects run on Roblox servers which helps make them more secure. Important game play features that you don’t want hacked, like awarding points, should happen on the server. The downside is that there can be lag while waiting for the server, and too many scripts running on the server will make the lag worse.

    Things that need to run lag-free, like player controls, should run on the player’s computer using a LocalScript. In general, if the code is something that only the player should see, code it in a LocalScript. If all players should see it, write the code in a Script.

    Set Up the ControlScript

    This LocalScript will allow players to control the ship by holding down a button and aiming the ship with their mouse.

    1. In StarterPlayer > StarterPlayerScripts add a LocalScript named ControlScript.
    1. In ControlScript, add the following variables:
    --This script determines what functions to call when a player presses a button when playing.
    
    -- Roblox Services
    local ContextActionService = game:GetService("ContextActionService")
    local Players = game:GetService("Players")
    
    -- Variables for the player, camera, and player’s mouse
    local player = game.Players.LocalPlayer
    local camera = workspace.CurrentCamera	
    local mouse = player:GetMouse()
    The script uses ContextActionService for player controls. ContextActionService is a service that lets scripts connect a player's input, like a keyboard press, to a specific action in-game like moving. Using this service also makes it easy to make a game playable on consoles or mobile devices.

    1. At the bottom of the script, add the variables for ship speed and controls.
    -- Configuration variables
    -- Sets the player's speed: The larger the negative number, the faster the ship will go.
    local PLAYER_SPEED = -45000
    local FORWARD_KEY = Enum.KeyCode.W
    
    -- Creates a force vector that uses player's speed
    local forwardForceVector = Vector3.new(0,0,PLAYER_SPEED)
    Using Different Controls

    To change player controls, change the FORWARD_KEY variable. Type Enum.Keycode. to see a set of possible keys.


    Make Players Move and Aim

    The forward key act likes a gas pedal. While the player is pressing it, the ship’s vector force will go faster and faster. Releasing the key will stop the acceleration, but not the ship’s forward motion, just like a car.

    1. The function below handles the ship movement. On the next line in ControlScript, copy and paste:
    -- Movement function that is called when the player presses the move forward button
    local function onMove(actionName, inputState)
    	if inputState == Enum.UserInputState.Begin then
    		-- If the player presses down the button, move forward
    		player.Character.HumanoidRootPart.VectorForce.Force = forwardForceVector
    	elseif inputState == Enum.UserInputState.End then
    		-- If the player releases the button, stop moving 
    		player.Character.HumanoidRootPart.VectorForce.Force = Vector3.new(0,0,0)
    	end
    end
    1. To make the player’s ship point towards the mouse, copy and paste:
    -- Function that is called when the player moves their mouse or finger
    local function onAim()
    	-- Make sure the character exists
    	if player.Character then
    		-- Finds the position of the mouse in the world and rotates the character model to face it
    		local rootPart = player.Character:FindFirstChild("HumanoidRootPart")
    		local mouseLocation = Vector3.new(mouse.Hit.X, rootPart.Position.Y, mouse.Hit.Z)
    		rootPart.CFrame = CFrame.new(rootPart.Position, mouseLocation)
    	end
    end
    1. Type the code below to connect the functions to ContextActionService:
    -- Set up control bindings
    ContextActionService:BindAction("Aim", onAim, false, Enum.UserInputType.MouseMovement)
    ContextActionService:BindAction("Move", onMove, false, FORWARD_KEY)
    
    1. Test your code, press W to move the ship towards your mouse. The ship is alive!
    Ship Moving Too Slow or Too Fast

    Your ship may move differently depending on the material you picked. If the material of your ship is something other than “Granite”, you will want to experiment with the value of PLAYER_SPEED. Granite parts are very heavy compared to other materials.


    --This script determines what functions to call when a player presses a button when playing.
    
    -- Roblox Services
    local ContextActionService = game:GetService("ContextActionService")
    local Players = game:GetService("Players")
    
    -- Variables for the player, camera, and player’s mouse
    local player = Players.LocalPlayer
    local camera = workspace.CurrentCamera	
    local mouse = player:GetMouse()
    
    -- Configuration variables
    -- Sets the player's speed: -200000 will go fast, -20000 will go slow
    local PLAYER_SPEED = -45000
    local FORWARD_KEY = Enum.KeyCode.W
    
    -- Creates a force vector that uses player's speed
    local forwardForceVector = Vector3.new(0,0,PLAYER_SPEED)
    
    -- Movement function that is called when the player presses the move forward button
    local function onMove(actionName, inputState)
    	if inputState == Enum.UserInputState.Begin then
    		-- If the player presses down the button, move forward
    		player.Character.HumanoidRootPart.VectorForce.Force = forwardForceVector
    	elseif inputState == Enum.UserInputState.End then
    		-- If the player releases the button, stop moving 
    		player.Character.HumanoidRootPart.VectorForce.Force = Vector3.new(0,0,0)
    	end
    end
    
    -- Function that is called when the player moves their mouse or finger
    local function onAim()
    	-- Make sure the character exists
    	if player.Character then
    		-- Finds the position of the mouse in the world and rotates the character model to face it
    		local rootPart = player.Character:FindFirstChild("HumanoidRootPart")
    		local mouseLocation = Vector3.new(mouse.Hit.X, rootPart.Position.Y, mouse.Hit.Z)
    		rootPart.CFrame = CFrame.new(rootPart.Position, mouseLocation)
    	end
    end
    
    -- Set up control bindings
    ContextActionService:BindAction("Aim", onAim, false, Enum.UserInputType.MouseMovement)
    ContextActionService:BindAction("Move", onMove, false, FORWARD_KEY)


    These documents are licensed by Roblox Corporation under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. Roblox, Powering Imagination, and Robux are trademarks of Roblox Corporation, registered in the United States and other countries.