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