PcoWSkbVqDnWTu_dm2ix
We use cookies on this site to enhance your user experience

Prerequisites Have completed Arcade Action - Spawning Enemies
Lesson Time 10 - 20 minutes
Optional Handouts N/A
Learning Objectives
  • Write a local and server script to create and display projectiles for all players
  • Use Remote Events and Remote Functions to send messages between local and server scripts
  • Remember the blasters on the ship? Now that the ship can move around, it’s time to give players the ability to defend themselves from the enemies around them by making the blasters operational.

    Create the Blaster Projectiles

    The blasters will need something to use as a projectile. You can use a single basic part of any shape like a cube or a sphere.

    Examples of projectiles with different shapes, colors, and materials

    1. Create a new part named BlasterProjectile. Customize the part to fit your game. This example uses a bubble.
    Using Different Parts

    The scripts used in this tutorial might not work when using combinations of parts, unioned parts, or anything from the Toolbox.


    1. In the Properties > Behavior, uncheck CanCollide. This will prevent players from bouncing off their own projectiles.
    1. Add a VectorForce and Attachment to BlasterProjectile. Later on, you’ll add in scripts that will use these two objects.
    1. Now that the projectile is setup, move it to ReplicatedStorage so the server can find and create projectiles during the game.
    Why ReplicatedStorage?

    Only the server can get objects from ServerStorage while ReplicateStorage lets the client and server access objects there. Since this projectile will be used on both the client and server, it needs to use ReplicatedStorage.


    Scripting the Blaster Projectiles

    It’s important that players experience the least amount of delay possible between the time they press the firing key and when they see the blaster actually fire. Even slight delays can frustrate players. Since you want to eliminate lag for players, a LocalScript would make sense here.

    However, remember that LocalScripts only run on the player’s device. If you use just local scripts for the blasters, blaster shots won’t be visible on the server for everyone else. A second problem with LocalScripts is that the enemies are running on the server. The blaster shots will have to interact with the server scripts so that other players in the game know what enemies have been destroyed.

    Both a Local Script and a Server Script

    The solution is to have the blaster fire twice. Once with a local script, and again with a server script. The local script projectile will be visible only to the player. The server script will be visible to everyone else. Since the server script is also more secure, it’ll also be used to award points.

    Using Remote Events and Remote Functions

    The local script and server script will need to communicate with each other to create the projectiles and trigger the destruction of enemies. Normally, local scripts and server scripts don’t have a way to talk to each other.

    To do so, they have to use special objects called RemoteEvents and RemoteFunctions. RemoteEvents can send one way messages. RemoteFunctions can send and receive messages.

    Set Up the Script

    1. Create a new script in ServerScriptService named BlasterRemotes.
    2. Create a variable for getting the players, and a variable for getting ReplicatedStorage, where the projectiles are stored.
    --Place in ServerScriptService. Sets up remote events used by BlasterHandler (client) and BlasterLauncher(server)
    

    local ReplicatedStorage = game:GetService(“ReplicatedStorage”)

    Create the Remote Functions

    RemoteFunctions will be used for sending messages to the server to create projectiles as well as to measure the ping for each player. Like other object types, RemoteFunctions can be created as new instances. The new instances should be parented to Replicated Storage since both client and server scripts can access objects stored there.

    1. Create a new RemoteFunction instance. Name it LaunchProjectile, and parent it to ReplicatedStorage.
    --Place in ServerScriptService. Sets up remote events used by BlasterHandler (client) and BlasterLauncher(server)
    
    local ReplicatedStorage = game:GetService("ReplicatedStorage")
    
    -- RemoteFunction for when a projectile is launched
    local launchProjectile = Instance.new("RemoteFunction")
    launchProjectile.Name = "LaunchProjectile"
    launchProjectile.Parent = ReplicatedStorage
    
    1. Create a new RemoteFunction named PingChecker. Parent it to Replicated Storage.
    --RemoteFunction to measure the ping for all clients
    local pingChecker = Instance.new("RemoteFunction")
    pingChecker.Name = "Ping"
    pingChecker.Parent = ReplicatedStorage
    

    Create the Remote Events

    You’ll also need two RemoteEvents. One for detecting when an enemy ship should be destroyed, and one for detecting when projectiles should be destroyed once they’ve come into contact with an enemy ship.

    1. After pingChecker, create a new RemoteEvent named DestroyEnemy and parent it to ReplicatedStorage.
    -- RemoteEvent for when the client detects an enemy should be destroyed
    local destroyEnemy = Instance.new("RemoteEvent")
    destroyEnemy.Name = "DestroyEnemy"
    destroyEnemy.Parent = ReplicatedStorage
    
    1. On the next line, create a new RemoteEvent named DestroyProjectile and parent it to ReplicatedStorage.
    -- RemoteEvent for when the client detects a projectile should be destroyed
    local destroyProjectile = Instance.new("RemoteEvent")
    destroyProjectile.Name = "DestroyProjectile"
    destroyProjectile.Parent = ReplicatedStorage
    
    --Place in ServerScriptService. Sets up remote events used by BlasterHandler (client) and BlasterLauncher(server)
    
    local ReplicatedStorage = game:GetService("ReplicatedStorage")
    
    -- RemoteFunction for when a projectile is launched
    local launchProjectile = Instance.new("RemoteFunction")
    launchProjectile.Name = "LaunchProjectile"
    launchProjectile.Parent = ReplicatedStorage
    
    -- RemoteFunction to measure the ping for all clients
    local pingChecker = Instance.new("RemoteFunction")
    pingChecker.Name = "Ping"
    pingChecker.Parent = ReplicatedStorage
    
    -- RemoteEvent for when the client detects an enemy should be destroyed
    local destroyEnemy = Instance.new("RemoteEvent")
    destroyEnemy.Name = "DestroyEnemy"
    destroyEnemy.Parent = ReplicatedStorage
    
    -- RemoteEvent for when the client detects a projectile should be destroyed
    local destroyProjectile = Instance.new("RemoteEvent")
    destroyProjectile.Name = "DestroyProjectile"
    destroyProjectile.Parent = ReplicatedStorage
    

    Creating the Local Script

    The local script will create projectiles on the client-side (player device). The first variables are directly related to gameplay, controlling how fast the projectile moves and the blaster’s cooldown period before it can shoot again.

    Create and Setup BlasterLaunch

    1. In StarterPlayerScripts, add a new LocalScript named BlasterLauncher.
    2. To set up the variables, copy and paste the code below into BlasterLauncher.
    -- This script handles blaster events on the client-side of the game
    
    -- How fast the projectile moves
    local PROJECTILE_SPEED = 40
    -- How often a projectile can be made on mouse clicks (in seconds)
    local LAUNCH_COOLDOWN = 1
    -- How far away the projectile is created from the front of the player
    local PROJECTILE_OFFSET = 4
    
    1. The next set of variables gets the RemoteEvents and RemoteFunctions set up in BlasterRemotes as well as the projectile template. Copy and paste the below:
    -- Variables for RemoteEvents and Functions (see the BlasterRemotes Script)
    local launchProjectile = ReplicatedStorage:WaitForChild("LaunchProjectile")
    local destroyProjectile = ReplicatedStorage:WaitForChild("DestroyProjectile")
    local destroyEnemy = ReplicatedStorage:WaitForChild("DestroyEnemy")
    local pingChecker = ReplicatedStorage:WaitForChild("Ping")
    
    -- Variable to store the basic projectile object
    local projectileTemplate = ReplicatedStorage:WaitForChild("BlasterProjectile")
    -- Variable for the player object
    local player = Players.LocalPlayer
    
    local canLaunch = true
    

    Create Projectiles on the Client

    The code below will copy a projectile from ReplicatedStorage on the client and then send a message to the server to create a projectile on the server side and hide it from the player so they don’t see two projectiles. It also sets up a touched event that destroys the projectile when it hits either an enemy or a wall.

    1. In BlasterHandler, copy and paste the code below:
    -- Fires when the player clicks the mouse
    local function onLaunch()
    	-- Only launch if the player's character exists and the blaster isn't on cooldown
    	if player.Character and canLaunch then
    		-- Prevents the player from launching again until the cooldown is done
    		canLaunch = false
    		spawn(function()
    			wait(LAUNCH_COOLDOWN)
    			canLaunch = true
    		end)
    		
    		-- Create a new projectile
    		local projectile = projectileTemplate:Clone()
    		local playerCFrame = player.Character.PrimaryPart.CFrame
    		local direction = playerCFrame.LookVector
    		projectile.Position = playerCFrame.Position + direction * PROJECTILE_OFFSET
    		projectile.Velocity = direction * PROJECTILE_SPEED
    		
    		-- Zero out gravity on the projectile so it doesn't fall through the ground
    		local mass = projectile:GetMass()
    		projectile.VectorForce.Force = Vector3.new(0, 1, 0) * mass * game.Workspace.Gravity
    		
    		-- Puts the projectile in the workspace
    		projectile.Parent = game.Workspace
    		
    		-- RemoteFunction that tell the server to create a new projectile and send it back to us
    		local serverProjectile = launchProjectile:InvokeServer(projectile.Position, projectile.Velocity)
    		-- Hide the server copy of the projectile
    		serverProjectile.LocalTransparencyModifier = 1
    		
    		-- Remote Event for the projectile
    		projectile.Touched:Connect(function(other)
    			-- The only collisions we care about are those with enemies and with walls
    			if other.Name == "EnemyBall" or other.Name == "Wall" then
    				-- Hit an enemy or wall, destroy the projectile and tell the server to destroy its copy
    				projectile:Destroy()
    				destroyProjectile:FireServer(serverProjectile)
    				
    				-- Hit an enemy, destroy it and tell the server to destroy it as well
    				if other.Name == "EnemyBall" then
    					destroyEnemy:FireServer(other)
    					other:Destroy()
    				end
    			end
    		end)
    	end
    end
    
    1. At the bottom of BlasterLauncher, under onLaunch, connect the launch function to the player’s control using ContextActionService:
    -- Connect a function to the ping RemoteFunction. This can be empty because all
    -- the server needs to hear is a response
    ping.OnClientInvoke = function() end
    
    ContextActionService:BindAction("Launch", onLaunch, false, Enum.UserInputType.MouseButton1)
    
    Don't Test Yet

    If you test now, the game won’t fire any projectiles. To fire projectiles, the BlasterLauncher script needs some more functions from the BlasterHandler script you’ll create next.


    -- This script handles blaster events on the client-side of the game
    
    -- How fast the projectile moves
    local PROJECTILE_SPEED = 40
    -- How often a projectile can be made on mouse clicks (in seconds)
    local LAUNCH_COOLDOWN = 1
    -- How far away the projectile is created from the front of the player
    local PROJECTILE_OFFSET = 4
    
    -- Variables for Roblox services
    local ContextActionService = game:GetService("ContextActionService")
    local Players = game:GetService("Players")
    local ReplicatedStorage = game:GetService("ReplicatedStorage")
    
    -- Variables for RemoteEvents and Functions (see the BlasterHandler Script)
    local launchProjectile = ReplicatedStorage:WaitForChild("LaunchProjectile")
    local destroyProjectile = ReplicatedStorage:WaitForChild("DestroyProjectile")
    local destroyEnemy = ReplicatedStorage:WaitForChild("DestroyEnemy")
    local ping = ReplicatedStorage:WaitForChild("Ping")
    
    -- Variable to store the basic projectile object
    local projectileTemplate = ReplicatedStorage:WaitForChild("BlasterProjectile")
    -- Variable for the player object
    local player = Players.LocalPlayer
    
    local canLaunch = true
    
    -- Fires when the player clicks the mouse
    local function onLaunch()
    	-- Only launch if the player's character exists and the blaster isn't on cooldown
    	if player.Character and canLaunch then
    		-- Prevents the player from launching again until the cooldown is done
    		canLaunch = false
    		spawn(function()
    			wait(LAUNCH_COOLDOWN)
    			canLaunch = true
    		end)
    		
    		-- Create a new projectile
    		local projectile = projectileTemplate:Clone()
    		local playerCFrame = player.Character.PrimaryPart.CFrame
    		local direction = playerCFrame.LookVector
    		projectile.Position = playerCFrame.Position + direction * PROJECTILE_OFFSET
    		projectile.Velocity = direction * PROJECTILE_SPEED
    		
    		-- Zero out gravity on the projectile so it doesn't fall through the ground
    		local mass = projectile:GetMass()
    		projectile.VectorForce.Force = Vector3.new(0, 1, 0) * mass * game.Workspace.Gravity
    		
    		-- Put the projectile in the workspace
    		projectile.Parent = game.Workspace
    		
    		-- Tell the server to create a new projectile and send it back to us
    		local serverProjectile = launchProjectile:InvokeServer(projectile.Position, projectile.Velocity)
    		-- Hide the server copy of the projectile
    		serverProjectile.LocalTransparencyModifier = 1
    		
    		-- Set up touched event for the projectile
    		projectile.Touched:Connect(function(other)
    			-- The only collisions we care about are those with enemies and with walls
    			if other.Name == "EnemyBall" or other.Name == "Wall" then
    				-- Hit an enemy or wall, destroy the projectile and tell the server to destroy its copy
    				projectile:Destroy()
    				destroyProjectile:FireServer(serverProjectile)
    				
    				-- Hit an enemy, destroy it and tell the server to destroy it as well
    				if other.Name == "EnemyBall" then
    					destroyEnemy:FireServer(other)
    					other:Destroy()
    				end
    			end
    		end)
    	end
    end
    
    -- Connect a function to the ping RemoteFunction. This can be empty because all
    -- the server needs to hear is a response
    ping.OnClientInvoke = function() end
    
    ContextActionService:BindAction("Launch", onLaunch, false, Enum.UserInputType.MouseButton1)

    Creating Projectiles on the Server

    The next script will run on the server. This script will be listening for whenever a player creates a projectile so that it can create a server-side projectile for all players to see.

    1. In ServerScriptService, create a new Script named BlasterHandler.
    2. To create the variables for the remote events, copy and paste the below code into BlasterHandler.
    -- This script handles blaster projectiles on the server-side of the game
    
    local Players = game:GetService("Players")
    local ReplicatedStorage = game:GetService("ReplicatedStorage")
    
    -- Variables for RemoteEvents and Functions (see the BlasterHandler Script)
    local launchProjectile = ReplicatedStorage:WaitForChild("LaunchProjectile")
    local destroyProjectile = ReplicatedStorage:WaitForChild("DestroyProjectile")
    local destroyEnemy = ReplicatedStorage:WaitForChild("DestroyEnemy")
    local pingChecker = ReplicatedStorage:WaitForChild("Ping")
    
    1. To keep track of the player’s ping, copy and paste the code below:
    -- Variable to store the basic projectile object
    local projectileTemplate = ReplicatedStorage.BlasterProjectile
    -- Table to keep track of the ping of all connected clients
    local currentPing = {}
    
    Since players are all on different devices, they might have different pings. So all the projectiles are shown consistently to players, pingChecker will check a player's ping to figure out when to create projectiles for that player.

    1. To tell the server that a projectile launched, copy and paste the code below. This function will be used in the client script, BlasterLauncher, to make sure both scripts talk to each other when a projectile is created.
    -- This is fired every time as enemy touches another part
    local function onTouched(other)
    
    end
    
    1. To destroy the projectile and enemies, copy and paste the functions below.
    -- Called when the client detects a projectile should be destroyed
    local function onDestroyProjectile(player, projectile)
    	projectile:Destroy()
    end
    
    -- Called when the client detects an enemy should be destroyed
    local function onDestroyEnemy(player, enemy)
    	enemy:Destroy()
    end
    
    While the script just destroys an enemy now, you'll eventually add onto the onDestroyEnemy function so players can get points for destroying enemies.

    1. Copy and paste the code below to turn on the pingChecker for any players that join the game. These functions will keep track of the player’s ping so the server can create projectiles with minimal delay.
    -- Called when a player joins the game. This function sets up a loop that
    -- measures the ping of the player
    local function onPlayerAdded(player)
    	while player and wait(2) do
    		local start = tick()
    		pingChecker:InvokeClient(player)
    		local ping = tick() - start
    		currentPing[player] = ping
    	end
    end
    
    -- Called when a player leaves the game. Removes their entry from the
    -- ping table
    local function onPlayerRemoving(player)
    	currentPing[player] = nil
    end
    
    1. To connect the functions to server events and find all the players in the game, copy and paste this code at the bottom of the script:
    -- Set up event bindings
    destroyProjectile.OnServerEvent:Connect(onDestroyProjectile)
    destroyEnemy.OnServerEvent:Connect(onDestroyEnemy)
    Players.PlayerAdded:Connect(onPlayerAdded)
    Players.PlayerRemoving:Connect(onPlayerRemoving)
    
    1. Test the game and check that the player can fire projectiles.
    -- This script handles blaster projectiles on the server-side of the game
    
    local Players = game:GetService("Players")
    local ReplicatedStorage = game:GetService("ReplicatedStorage")
    
    -- Variables for RemoteEvents and Functions (see the BlasterHandler Script)
    local launchProjectile = ReplicatedStorage:WaitForChild("LaunchProjectile")
    local destroyProjectile = ReplicatedStorage:WaitForChild("DestroyProjectile")
    local destroyEnemy = ReplicatedStorage:WaitForChild("DestroyEnemy")
    local pingChecker = ReplicatedStorage:WaitForChild("Ping")
    
    -- This function is called when the client launches a projectile
    launchProjectile.OnServerInvoke = function(player, position, velocity)
    	-- Make a new projectile and set the velocity
    	local projectile = projectileTemplate:Clone()
    	projectile.Velocity = velocity
    	
    	-- Calculate where the projectile should be based on the latency
    	-- of the player who launched it
    	local ping = 0
    	if currentPing[player] then
    		ping = currentPing[player]
    	end
    	local offset = ping * velocity * 1.5
    	projectile.Position = position + offset
    	
    	-- Zero out gravity on the projectile so it doesn't fall through the ground
    	local mass = projectile:GetMass()
    	projectile.VectorForce.Force = Vector3.new(0, 1, 0) * mass * game.Workspace.Gravity
    	
    	-- Put the projectile in the workspace and make sure the server is the owner
    	projectile.Parent = game.Workspace
    	projectile:SetNetworkOwner(nil)
    	
    	-- Send the projectile back to the player who launched it
    	return projectile
    end
    
    -- Called when the client detects a projectile should be destroyed
    local function onDestroyProjectile(player, projectile)
    	projectile:Destroy()
    end
    
    -- Called when the client detects an enemy should be destroyed
    local function onDestroyEnemy(player, enemy)
    	enemy:Destroy()
    	-- Give the player a point for destroying an enemy
    	local enemyStat = player.leaderstats:WaitForChild("Score")
    	enemyStat.Value = enemyStat.Value + 1
    end
    
    -- Called when a player joins the game. This function sets up a loop that
    -- measures the ping of the player
    local function onPlayerAdded(player)
    	while player and wait(2) do
    		local start = tick()
    		pingChecker:InvokeClient(player)
    		local ping = tick() - start
    		currentPing[player] = ping
    	end
    end
    
    -- Called when a player leaves the game. Removes their entry from the
    -- ping table
    local function onPlayerRemoving(player)
    	currentPing[player] = nil
    end
    
    -- Set up event bindings
    destroyProjectile.OnServerEvent:Connect(onDestroyProjectile)
    destroyEnemy.OnServerEvent:Connect(onDestroyEnemy)
    Players.PlayerAdded:Connect(onPlayerAdded)
    Players.PlayerRemoving:Connect(onPlayerRemoving)
    


    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.


    Previous Spawning Enemies Next Player Stats