I just got back into Roblox programming after a long hiatus. These last few days I’ve spent a good amount of work and time to make this laser gun tool.
This laser gun has a starting bullet of 5 bullets, each shot delay is 1 second, and you must reload by pressing “R” on your keyboard. It is a very simple mechanism.
The only challenging part is my take on making it more secure so that it is harder for exploiters to take advantage and mess up the settings of the laser gun. The way on how I did this is by doing checks on the server side including the player’s laser gun’s bullet count, reload state and accuracy. I learnt all of these from the tutorial made by Roblox on the developer hub website.
Here are the scripts involved:
CLIENT
local UserInputService = game:GetService("UserInputService")
local ContextActionService = game:GetService("ContextActionService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")
local LaserRenderer = require(Players.LocalPlayer.PlayerScripts.LaserRenderer) -- a module designed to render the laser part itself
local eventsFolder = ReplicatedStorage:WaitForChild("Events")
local tool = script.Parent
local RELOAD_ACTION = "ReloadWeapon"
local RELOAD_TIME = 2
local RELOAD_DEBOUNCE = false
local MAX_MOUSE_DISTANCE = 100
local MAX_LASER_DISTANCE = 100
local FIRE_RATE = 1
local timeOfPreviousShot = 0
local function onAction(actionName, inputState, inputObject)
if not RELOAD_DEBOUNCE then
if actionName == RELOAD_ACTION and inputState == Enum.UserInputState.Begin then
RELOAD_DEBOUNCE = true
eventsFolder.ReloadWeapon:FireServer(tool)
tool.TextureId = "rbxassetid://6593020923"
task.wait(RELOAD_TIME)
tool.TextureId = "rbxassetid://92628145"
RELOAD_DEBOUNCE = false
end
end
end
local function canShootWeapon()
-- check timing
local currentTime = tick()
if currentTime - timeOfPreviousShot <= FIRE_RATE then
return false
end
-- check bullets
if tool:GetAttribute("Ammo") <= 0 then
return false
end
return true
end
local function getWorldMousePosition()
local mousePosition = UserInputService:GetMouseLocation()
local screenToWorldRay = workspace.CurrentCamera:ViewportPointToRay(mousePosition.X, mousePosition.Y)
local directionVector = screenToWorldRay.Direction * MAX_MOUSE_DISTANCE
local raycastResult = workspace:Raycast(screenToWorldRay.Origin, directionVector)
if raycastResult then
return raycastResult.Position
else
return tool.Handle.Position + directionVector
end
end
local function fireWeapon()
local mousePosition = getWorldMousePosition()
local targetPosition = (mousePosition - tool.Handle.Position).Unit
local directionVector = targetPosition * MAX_LASER_DISTANCE
local weaponRaycastParams = RaycastParams.new()
weaponRaycastParams.FilterDescendantsInstances = {Players.LocalPlayer.Character:GetDescendants()}
weaponRaycastParams.FilterType = Enum.RaycastFilterType.Blacklist
local weaponRaycastResult = workspace:Raycast(tool.Handle.Position, directionVector, weaponRaycastParams)
local hitPosition
if weaponRaycastResult then
hitPosition = weaponRaycastResult.Position
local characterModel = weaponRaycastResult.Instance:FindFirstAncestorOfClass("Model")
if characterModel then
local humanoid = characterModel:FindFirstChildWhichIsA("Humanoid")
if humanoid then
eventsFolder.DamageCharacter:FireServer(characterModel, hitPosition)
end
end
else
hitPosition = tool.Handle.Position + directionVector
end
timeOfPreviousShot = tick()
if not RELOAD_DEBOUNCE then
LaserRenderer.RenderLaser(tool.Handle, hitPosition)
end
eventsFolder.LaserFired:FireServer(hitPosition)
end
local function toolEquipped()
tool.Handle.Equip:Play()
ContextActionService:BindAction(RELOAD_ACTION, onAction, false, Enum.KeyCode.R)
end
local function toolActivated()
if canShootWeapon() then
fireWeapon()
else
end
end
local function toolUnequipped()
ContextActionService:UnbindAction(RELOAD_ACTION)
end
tool.Equipped:Connect(toolEquipped)
tool.Unequipped:Connect(toolUnequipped)
tool.Activated:Connect(toolActivated)
SERVER
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")
local eventsFolder = ReplicatedStorage.Events
local LASER_DAMAGE = 10
local MAX_AMMO = 5
local MAX_HIT_PROXIMITY = 10
local FIRE_RATE = 1
local playerInfoTable = {}
local function onPlayerAdded(player)
local key = player.UserId
local backpack
local character
local toolsLoaded = false
-- table format
playerInfoTable[key] = {
["IsLoaded"] = toolsLoaded,
["LastShot"] = 0,
["Reloading"] = false
}
-- set events
player.CharacterAdded:Connect(function(char)
backpack = player:WaitForChild("Backpack")
character = char
local blaster = backpack.Blaster
blaster:SetAttribute("Ammo", 5)
toolsLoaded = true
playerInfoTable[key]["IsLoaded"] = true
end)
repeat task.wait() until character
character:FindFirstChild("Humanoid").Died:Connect(function(char)
if playerInfoTable[key] then
playerInfoTable[key]["IsLoaded"] = false
end
end)
-- setup table
repeat task.wait() until toolsLoaded
playerInfoTable[key]["Ammo"] = backpack.Blaster:GetAttribute("Ammo")
end
local function onReloadingWeapon(playerReloaded, tool)
local key = playerReloaded.UserId
if not playerInfoTable[key]["Reloading"] then
playerInfoTable[key]["Reloading"] = true
task.wait(2)
tool:SetAttribute("Ammo", MAX_AMMO)
playerInfoTable[key]["Reloading"] = false
end
end
local function canShootWeapon(playerInfo, tool)
-- check timing
local currentTime = tick()
local timeDifference = currentTime - playerInfo["LastShot"]
if timeDifference <= FIRE_RATE then
return false
end
-- check bullets
if tool:GetAttribute("Ammo") <= 0 then
return false
end
playerInfo["LastShot"] = tick()
return true
end
local function getPlayerToolHandle(player)
local character = player.Character
if character ~= nil then
local tool = character:FindFirstChild("Blaster")
if tool then
return tool.Handle
end
end
return nil
end
local function isHitValid(playerFired, characterToDamage, hitPosition)
-- check distance
local characterHitProximity = (characterToDamage.HumanoidRootPart.Position - hitPosition).Magnitude
if characterHitProximity > MAX_HIT_PROXIMITY then
return false
end
-- check if shooting through walls
local toolHandle = getPlayerToolHandle(playerFired)
if toolHandle then
local rayLength = (toolHandle.Position - hitPosition).Magnitude
local rayDirection = (toolHandle.Position - hitPosition).Unit
local raycastParams = RaycastParams.new()
raycastParams.FilterDescendantsInstances = {playerFired.Character}
raycastParams.FilterType = Enum.RaycastFilterType.Blacklist
local raycastResult = workspace:Raycast(toolHandle.Position, rayDirection*rayLength, raycastParams)
if raycastResult and raycastResult.Instance:IsDescendantOf(playerFired.Character) then
return false
end
end
return true
end
local function playerFiredLaser(playerFired, endPosition)
local key = playerFired.UserId
local playerFiredCharacter = playerFired.Character
local toolHandle = getPlayerToolHandle(playerFired)
local canShoot = canShootWeapon(playerInfoTable[key], playerFiredCharacter.Blaster)
local isReloading = playerInfoTable[key]["Reloading"]
if (toolHandle and canShoot) and not isReloading then
eventsFolder.LaserFired:FireAllClients(playerFired, toolHandle, endPosition)
playerInfoTable[key]["Ammo"] -= 1
toolHandle.Parent:SetAttribute("Ammo", (toolHandle.Parent:GetAttribute("Ammo") - 1))
end
end
local function damageCharacter(playerFired, characterToDamage, hitPosition)
local validShot = isHitValid(playerFired, characterToDamage, hitPosition)
local humanoid = characterToDamage:FindFirstChildWhichIsA("Humanoid")
if validShot and humanoid then
humanoid.Health -= 10
end
end
eventsFolder.DamageCharacter.OnServerEvent:Connect(damageCharacter)
eventsFolder.LaserFired.OnServerEvent:Connect(playerFiredLaser)
eventsFolder.ReloadWeapon.OnServerEvent:Connect(onReloadingWeapon)
Players.PlayerAdded:Connect(onPlayerAdded)
If there are any mistakes or blunders I’ve made in the scripts, please let me know so I can be more cautious and careful in the future when making large systems. Thanks.