Roblox Wiki Arcade Tutorial Code Not Working

Hi! The Roblox wiki has a series on creating a Space Arcade Game and I’ve been having many errors as I try to make the game. I’m not sure if I’m making mistakes (I’m copying code word for word and doing all instructions). I’m trying to use this to help with VectorForce and creating a game prototype. I would appreciate if anyone has done this successfully, or if anyone has/can review the code to see if it should work. The bugs happen when enemies appear, and server to client communication seems to be the culprit.

Here is the code and explorer:

074a3eb0f0d9206f3c4528e7c67cd70a
074a3eb0f0d9206f3c4528e7c67cd70a.png434x804 26.3 KB

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")

-- 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)

BlasterRemotes -
–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

Enemy Handler -
– Randomly spawns the number of enemies set in ENEMY_COUNT

local ENEMY_COUNT = 100

local SAFE_ZONE_SIZE = 50

local MAX_ENEMY_SPEED = 20

local RESPAWN_DURATION = 3

local ServerStorage = game:GetService("ServerStorage")

local function onTouched(other)

if other.Name == "HumanoidRootPart" or other.Name == "Blaster" then

local character = other.Parent

character.Parent = game:GetService("ReplicatedStorage")

local explosion = Instance.new("Explosion", workspace)

explosion.Position = character:GetPrimaryPartCFrame().p

wait(RESPAWN_DURATION)

-- Reveal the character after they are knocked out and stop their movement

character.Parent = workspace

character.HumanoidRootPart.Velocity = Vector3.new(0,0,0)

character.HumanoidRootPart.VectorForce.Force = Vector3.new(0,0,0)

end

end

local function spawnEnemies(count)

local baseplateSize = workspace.Arena.Baseplate.Size

-- Loops until there are the amount of enemies set in ENEMY_COUNT

for i = 1, count do

-- Makes a copy of EnemyBall

local enemy = ServerStorage.Enemies.EnemyBall:Clone()

enemy.Parent = workspace.Enemies

-- Places EnemyBall randomly on the baseplate

local randomX = math.random(baseplateSize.X / -2 - SAFE_ZONE_SIZE, baseplateSize.X / 2 + SAFE_ZONE_SIZE)

local randomZ = math.random(baseplateSize.Z / -2 - SAFE_ZONE_SIZE, baseplateSize.Z / 2 + SAFE_ZONE_SIZE)

enemy.Position = Vector3.new(randomX , 9, randomZ)

-- Assigns a random velocity to EnemyBall

local randomVX = math.random(-1 * MAX_ENEMY_SPEED, MAX_ENEMY_SPEED)

local randomVZ = math.random(-1 * MAX_ENEMY_SPEED, MAX_ENEMY_SPEED)

enemy.Velocity = Vector3.new(randomVX, 0, randomVZ)

enemy.Touched:Connect(onTouched)

end

end

spawnEnemies(ENEMY_COUNT)

PlayerShipHandler -
–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)

Blaster Launcher -
– 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)

CameraScript -
–Creates a top-down camera for each player. Should be used as a LocalScript

--Get service needed for events used in this script

local RunService = game:GetService("RunService")

-- Variables for the camera and player

local camera = workspace.CurrentCamera

local player = game.Players.LocalPlayer

-- Constant variable used to set the camera’s offset from the player

local CAMERA_OFFSET = Vector3.new(-1,90,0)

-- Enables the camera to do what this script says

camera.CameraType = Enum.CameraType.Scriptable

-- Called every time the screen refreshes

local function onRenderStep()

-- Check the player's character has spawned

if player.Character then

local playerPosition = player.Character.HumanoidRootPart.Position

local cameraPosition = playerPosition + CAMERA_OFFSET

-- make the camera follow the player

camera.CoordinateFrame = CFrame.new(cameraPosition, playerPosition)

end

end

RunService:BindToRenderStep("Camera", Enum.RenderPriority.Camera.Value, onRenderStep)

ControlScript -
–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 = -100000

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)

The %quot seems to be a result of code being too big for the frame, please note that it is a " in the code. Thank you!

Errors -
[19:15:34.692 - ServerScriptService.BlasterHandler:15: attempt to index global ‘projectileTemplate’ (a nil value)

[19:15:33.892 - ServerScriptService.BlasterHandler:59: attempt to index global ‘currentPing’ (a nil value)]

Also, no error was given to this part, but on death the player could no longer move. They could rotate, but on the server it was not visible.

2 Likes

Heya,

It seems you might have forgotten a line from https://developer.roblox.com/resources/education/arcade/blasters ? Under the heading “Creating Projectiles on the Server”, step 3 includes the definition of the currentPing table and the projectileTemplate reference. I don’t see this in your code, so perhaps that is the issue regarding that error?

If you could provide the place file (RBXL) that would be helpful in determining the issue.

1 Like