PcoWSkbVqDnWTu_dm2ix
Hit Detection with Lasers
Part 4 - Client to Server Communication
Hit Detection with Lasers
Part 4 - Client to Server Communication

Damaging the Player

Clients cannot damage other clients directly; the server needs to be responsible for issuing damage when a player is hit.

Clients can use a RemoteEvent to tell the server that a character has been hit. These should be stored in ReplicatedStorage, where they are visible to both client and server.

  1. Create a Folder in ReplicatedStorage named Events.
  2. Insert a RemoteEvent into the Events folder and name it DamageCharacter.
  3. In ToolController, create variables at the start of the script for ReplicatedStorage and the Events folder.
    local UserInputService = game:GetService("UserInputService")
    local Players = game:GetService("Players")
    local ReplicatedStorage = game:GetService("ReplicatedStorage")
    
    local LaserRenderer = require(Players.LocalPlayer.PlayerScripts.LaserRenderer)
    
    local tool = script.Parent
    local eventsFolder = ReplicatedStorage.Events
    
    local MAX_MOUSE_DISTANCE = 1000
    local MAX_LASER_DISTANCE = 500
  4. Replace the "Player hit" print statement in fireWeapon with a line of Lua to fire the DamageCharacter remote event with the characterModel variable as an argument.
        local characterModel = weaponRaycastResult.Instance:FindFirstAncestorOfClass("Model")
        if characterModel then
            local humanoid = characterModel:FindFirstChild("Humanoid")
            if humanoid then
                eventsFolder.DamageCharacter:FireServer(characterModel)
            end
        end
    else
        -- Calculate the end position based on maximum laser distance
        hitPosition = tool.Handle.Position + directionVector
    end

The server needs to deal damage to the player who has been hit when the event is fired.

  1. Insert a Script into ServerScriptService and name it ServerLaserManager.

  2. Declare a variable named LASER_DAMAGE and set it to 10, or a value of your choice.

  3. Create a function named damageCharacter with two parameters called playerFired and characterToDamage.

  4. Inside the function, find the character’s Humanoid and subtract LASER_DAMAGE from its health.

  5. Connect the damageCharacter function to the DamageCharacter remote event in the Events folder.

    local ReplicatedStorage = game:GetService("ReplicatedStorage")
    local eventsFolder = ReplicatedStorage.Events
    local LASER_DAMAGE = 10
    
    function damageCharacter(playerFired, characterToDamage)
        local humanoid = characterToDamage:FindFirstChild("Humanoid")
        if humanoid then
            -- Remove health from character
            humanoid.Health -= LASER_DAMAGE
        end
    end
    
    -- Connect events to appropriate functions
    eventsFolder.DamageCharacter.OnServerEvent:Connect(damageCharacter)
  6. Test the blaster with 2 players by starting a local server. When you shoot the other player, their health will decrease by the number assigned to LASER_DAMAGE.

Rendering Other Player’s Laser Beams

Currently, the laser beam is created by the client firing the weapon, so only they will be able to see the laser beam.

If the laser beam was created on the server then everyone would be able to see it. However, there would be a small delay between the client shooting the weapon and the server receiving the information about the shot. This would mean the client shooting the weapon would see a delay between when they activate the weapon and when they see the laser beam; the weapon would feel laggy as a result.

To solve this issue, every client will create their own laser beams. This means the client shooting the weapon will see the laser beam instantly. Other clients will experience a small delay between when another player shoots and a beam appears. This is the best case scenario: there is no way to communicate one client’s laser to other clients any faster.

Shooter’s Client

First, the client needs to tell the server it has fired a laser and provide the end position.

  1. Insert a RemoteEvent into the Events folder in ReplicatedStorage and name it LaserFired.

  2. Locate the fireWeapon function in the ToolController script. At the end of the function, fire the LaserFired remote event using hitPosition as an argument.

            hitPosition = tool.Handle.Position + directionVector
        end
    
        timeOfPreviousShot = tick()
    
        eventsFolder.LaserFired:FireServer(hitPosition)
        LaserRenderer.createLaser(tool.Handle, hitPosition)
    end

The Server

The server now must receive the event that the client has fired and tell all clients the start and end position of the laser beam so they can also render it.

  1. In the ServerLaserManager script, create a function named playerFiredLaser above damageCharacter with two parameters called playerFired and endPosition.
  2. Connect the function to the LaserFired remote event.
    -- Notify all clients that a laser has been fired so they can display the laser
    local function playerFiredLaser(playerFired, endPosition)
     
    end
    -- Connect events to appropriate functions
    eventsFolder.DamageCharacter.OnServerEvent:Connect(damageCharacter)
    eventsFolder.LaserFired.OnServerEvent:Connect(playerFiredLaser)

The server needs the start position of the laser. This could be sent from the client, but it’s best to avoid trusting the client where possible. The character’s weapon handle position will be the start position, so the server can find it from there.

  1. Create a function getPlayerToolHandle above the playerFiredLaser function with a parameter called player.

  2. Use the following code to search the player’s character for the weapon and return the handle object.

    local LASER_DAMAGE = 10
    
    -- Find the handle of the tool the player is holding
    local function getPlayerToolHandle(player)
        local weapon = player.Character:FindFirstChildOfClass("Tool")
        if weapon then
            return weapon:FindFirstChild("Handle")
        end
    end
    
    -- Notify all clients that a laser has been fired so they can display the laser
    local function playerFiredLaser(playerFired, endPosition)

The server can now call FireAllClients on the LaserFired remote event to send the information required to render the laser to the clients. This includes the player who fired the laser (so the client for that player does not render the laser twice), the handle of the blaster (which acts as a starting position for the laser) and the end position of the laser.

  1. In the playerFiredLaser function, call the getPlayerToolHandle function with playerFired as an argument and assign the value to a variable named toolHandle.

  2. If toolHandle exists, fire the LaserFired event for all clients using playerFired, toolHandle and endPosition as arguments.

    -- Notify all clients that a laser has been fired so they can display the laser
    local function playerFiredLaser(playerFired, endPosition)
        local toolHandle = getPlayerToolHandle(playerFired)
        if toolHandle then
            eventsFolder.LaserFired:FireAllClients(playerFired, toolHandle, endPosition)
        end
    end

Rendering on the Clients

Now FireAllClients has been called, each client will receive an event from the server to render a laser beam. Each client can reuse the LaserRenderer module from earlier to render the laser beam using the tool’s handle position and end position value sent by the server. The player that fired the laser beam in the first place should ignore this event otherwise they’ll see 2 lasers.

  1. Create a LocalScript in StarterPlayerScripts called ClientLaserManager.

  2. Inside the script, require the LaserRenderer module.

  3. Create a function named createPlayerLaser with the parameters playerWhoShot, toolHandle and endPosition.

  4. Connect the function to the LaserFired remote event in the Events folder.

  5. In the function, use an if statement to check if playerWhoShot does not equal the LocalPlayer.

  6. Inside the if statement, call the createLaser function from the LaserRenderer module using toolHandle and endPosition as arguments.

    local Players = game:GetService("Players")
    local ReplicatedStorage = game:GetService("ReplicatedStorage")
    
    local LaserRenderer = require(script.Parent:WaitForChild("LaserRenderer"))
    
    local eventsFolder = ReplicatedStorage.Events
    
    -- Display another player's laser
    local function createPlayerLaser(playerWhoShot, toolHandle, endPosition)
        if playerWhoShot ~= Players.LocalPlayer then
            LaserRenderer.createLaser(toolHandle, endPosition)
        end
    end
    
    eventsFolder.LaserFired.OnClientEvent:Connect(createPlayerLaser)
  7. Test the blaster with 2 players by starting a local server. Position each client on different sides of your monitor so you can see both windows at once. When you shoot on one client you should see the laser on the other client.

Sound Effects

The shooting sound effect currently only plays on the client that’s shooting the projectile. You’ll need to move the code to play the sound so that other players will hear it too.

  1. In the ToolController script, navigate to the toolActivated function and remove the line which plays the Activate sound.
     local function toolActivated()
         if canShootWeapon() then
                                 
             fireWeapon()
         end
     end
  2. At the bottom of the createLaser function in LaserRenderer, declare a variable named shootingSound and use the FindFirstChild method of toolHandle to check for the Activate sound.
  3. Use an if statement to check if shootingSound exists; if it does, call its Play function.
        laserPart.Parent = workspace
    
        -- Add laser beam to the Debris service to be removed & cleaned up
        game.Debris:AddItem(laserPart, SHOT_DURATION)
    
        -- Play the weapon's shooting sound
        local shootingSound = toolHandle:FindFirstChild("Activate")
        if shootingSound then
            shootingSound:Play()
        end
    end

Previous Page Rendering the Laser Beam Next Page Securing Remotes using Validation