---
title: "Implement blaster behavior"
url: /docs/en-us/tutorials/curriculums/gameplay-scripting/implement-blasters
last_updated: 2026-07-09T18:41:07Z
description: "Explains end-to-end how a blast mechanic works in a laser tag game."
---

# Implement blaster behavior

**Implementing blaster behavior** is the process of programming a blast mechanic in first-person shooter games. While players can blast with a single click or press of a button, creating a satisfying and accurate blast behavior is important because it enhances players' enjoyment of the overall gameplay.

Using the [sample laser tag game](https://www.roblox.com/games/14817965191/Laser-Tag-1A) as a reference, this section of the tutorial teaches you about the scripts behind implementing blaster behavior for two different types of blasters, including guidance on:

- Detecting when players press the blast button.
- Checking whether the player can use their blaster if they recently pressed the blast button.
- Generating blast data that tells the server who initiated the blast, where it came from, and what was each laser beam's final destination.
- Notifying the server of the blast data so it can perform the appropriate actions if the blast collided with another player.
- Resetting the blaster between each blast to give the blaster enough time to cool down before it can blast again.

After you complete this section, you will learn about the scripts that allow the blaster to detect when its blasts collide with other players, then deduct the corresponding amount of health according to each blaster type.

## Detect player input

The first step to implementing blaster behavior is to listen for when a player presses the blast button. The input type that players use to press the blast button depends on which device they're using to access the game. For example, the sample laser tag game supports mouse and keyboard controls, gamepads, and touch controls. You can see each of these input types in **ReplicatedStorage** ⟩ **UserInputHandler**.

This client script uses `Class.ContextActionService` to bind `MouseButton1` and `ButtonR2` to the blasting action. This means that every time a player either presses a left mouse button or a gamepad's R2 button, it triggers a laser beam to blast out of the blaster. Note that the **HUDGui** contains a button for blasting on mobile devices, which it connects to later in the script.

```lua
ContextActionService:BindAction("_", onBlasterActivated, false,
  Enum.UserInputType.MouseButton1,
  Enum.KeyCode.ButtonR2
)
```

Another important note is the use of `Enum.UserInputState.Begin` in the `onBlasterActivated()` definition. Many user interface interactions, such as choosing a blaster in this example, don't occur until after the mouse button comes up (`Enum.UserInputState.End`), which gives users a last-second chance to avoid the interaction. However, a blasting mechanic doesn't feel responsive unless it occurs the instant the button goes down.

To demonstrate, you can change `Enum.UserInputState.Begin` to `Enum.UserInputState.End`, then playtest to see how the responsiveness of the blast impacts the gameplay of the game. For example, if players can hold down the button without triggering the blast, how might that change the their experience while tagging other players?

```lua
local function onBlasterActivated(_actionName: string,
  inputState: Enum.UserInputState, _inputObject: InputObject)
    if inputState == Enum.UserInputState.End then  -- updated line, be sure to change back
        attemptBlastClient()
    end
end
```

## Check whether the player can blast

After `UserInputHandler` detects a button press or screen tap, it calls **ReplicatedStorage** ⟩ **Blaster** ⟩ **attemptBlastClient** to check whether the player can blast or not. Like most checks in the sample laser tag game, it occurs twice: first on the client, then later on the server. `attemptBlastClient` then calls **ReplicatedStorage** ⟩ **Blaster** ⟩ **canLocalPlayerBlast** to perform a simple check of the `blasterStateClient` player attribute:

```lua
local function canLocalPlayerBlast(): boolean
    return localPlayer:GetAttribute(PlayerAttribute.blasterStateClient) == BlasterState.Ready
end
```

If you examine **ReplicatedStorage** ⟩ **Blaster** ⟩ **BlasterState**, you can see that the game has three blaster states: `Ready`, `Blasting`, and `Disabled`. To see the effect of each of these states, you can playtest the game, select your player under the **Players** service, then observe the **blasterStateClient** attribute in the **Properties** window. Notice how it displays `Disabled` while you choose your blaster, `Ready` most of the time, and `Blasting` for less than a second after you press the button.

This slight pause prevents you from being able to blast as quickly as you can click. For example, if you change the function to always return true, you can rapidly blast your blaster without any delay, which is unrealistic for laser tag gameplay.

```lua
local function canLocalPlayerBlast(): boolean
    return true -- updated line, be sure to change back
end
```

## Generate blast data

After verifying that the player's blaster is in the `Ready` state, `attemptBlastClient` calls **ReplicatedStorage** ⟩ **attemptBlastClient** ⟩ **blastClient**. The first step that `blastClient` takes is to set the `blasterStateClient` player attribute to `Blasting`, which avoids the same rapid fire case from earlier.

> **Info:** Changing `BlasterState` to `Blasting` also plays a sound in **ReplicatedStorage** ⟩ **SoundHandler**.

The next step is to generate the blast data. If you review **ReplicatedStorage** ⟩ **Blaster** ⟩ **BlastData**, you can see that each blast consists of three pieces of information:

- The player who initiates the blast.
- A `DataType.CFrame` that represents the blast's point of origin.
- A `RayResult` table that contains each laser beam's final destination and the hit player, if hit another player.

To generate this data, `blastClient` calls **ReplicatedStorage** ⟩ **attemptBlastClient** ⟩ **blastClient** ⟩ **generateBlastData**, which you can review below.

```lua
local function generateBlastData(): BlastData.Type
    local blasterConfig = getBlasterConfig()

    local rayDirections = getDirectionsForBlast(
        currentCamera.CFrame, blasterConfig)
    local rayResults = castLaserRay(
        localPlayer, currentCamera.CFrame.Position, rayDirections)

    local blastData: BlastData.Type = {
        player = localPlayer,
        originCFrame = currentCamera.CFrame,
        rayResults = rayResults,
    }
    return blastData
end
```

This function starts by using `getBlasterConfig` to retrieve the player's blaster type. The sample provides two types of blasters: one that produces several beams with a wide, horizontal spread, and another that produces a single beam. You can find their configurations in **ReplicatedStorage** ⟩ **Instances** ⟩ **LaserBlastersFolder**.

The function then uses `currentCamera.CFrame` as the point of origin for the blast, passing it to `getDirectionsForBlast`. At this point, the code is no longer about the blaster, it's about the laser beam, which you will learn more about in the [detect hits](/docs/en-us/tutorials/curriculums/gameplay-scripting/detect-hits.md) section of the tutorial. Finally, after creating the `rayResults` table, `generateBlastData` has all the information it needs to return the blast data to `blastClient`.

## Notify the server

Once `blastClient` has complete data for the blast, it fires two events:

```lua
local laserBlastedBindableEvent = ReplicatedStorage.Instances.LaserBlastedBindableEvent
local laserBlastedEvent = ReplicatedStorage.Instances.LaserBlastedEvent

laserBlastedBindableEvent:Fire(blastData)
laserBlastedEvent:FireServer(blastData)
```

The `Class.BindableEvent` notifies other client scripts of the blast. For example, **ReplicatedStorage** ⟩ **FirstPersonBlasterVisuals** uses this event to know when to display visual effects, such as the blast animation and cooldown bar. Similarly, the `Class.RemoteEvent` notifies server scripts of the blast, which begins processing the blast in **ServerScriptService** ⟩ **LaserBlastHandler**.

```lua
local function onLaserBlastedEvent(playerBlasted: Player, blastData: BlastData.Type)
    local validatedBlastData = getValidatedBlastData(playerBlasted, blastData)
    if not validatedBlastData then
        return
    end

    if not canPlayerBlast(playerBlasted) then
        return
    end

    blastServer(playerBlasted)

    processTaggedPlayers(playerBlasted, blastData)

    for _, replicateToPlayer in Players:GetPlayers() do
        if playerBlasted == replicateToPlayer then
            continue
        end
        replicateBlastEvent:FireClient(replicateToPlayer, playerBlasted, blastData)
    end
end
```

To help prevent cheating, the server must verify all data that each client sends. These checks include:

1. Is `BlastData` a table? Does it contain a `Class.CFrame` and another table named `rayResults`?
2. Does the player have a blaster equipped?
3. Does the player have a character and a location within the world?
4. After sending the blast data, has the player moved an excessive distance away from where they blasted the laser beam?

This last check involves a judgment call, and according to server latency and player movement speed, you might decide that different values are excessive for your own game. To demonstrate how to make this judgment call, you can get a sense of the typical magnitude of positional change by adding a print statement in `getValidatedBlastData` and playtesting the game.

```lua
local distanceFromCharacterToOrigin = blastData.originCFrame.Position - rootPartCFrame.Position
print(distanceFromCharacterToOrigin.Magnitude) -- updated line, be sure to remove
if distanceFromCharacterToOrigin.Magnitude > ToleranceValues.DISTANCE_SANITY_CHECK_TOLERANCE_STUDS then
    warn(\`Player {player.Name} failed an origin sanity check while blasting\`)
    return
end
```

As you move and blast, note the output. It might look something like this:

```text
1.9019629955291748
3.1549558639526367
2.5742883682250977
4.8044586181640625
2.6434271335601807
```

If you increase the movement speed for players in **ReplicatedStorage** ⟩ **PlayerStateHandler** ⟩ **togglePlayerMovement**, then playtest again, you will likely encounter many failed checks due to excessive movement between blasts.

```lua
local ENABLED_WALK_SPEED = 60 -- updated line, be sure to change back
```

> **Info:** For this reason, if you decide to increase movement speed, consider adjusting `DISTANCE_SANITY_CHECK_TOLERANCE_STUDS` in **ServerStorage** ⟩ **ToleranceValues**. For more information on how to approach this problem, see [Movement Validation](/docs/en-us/scripting/security/security-tactics.md#movement-validation).

The server then does the following:

- Validates `rayResults`.
- Checks whether the player can blast.
- Resets the blaster state.
- Reduces health for any tagged players.
- Replicates the blast to all other players so that they can see third-person visuals.

For more information on these server operations, see the [detect hits](/docs/en-us/tutorials/curriculums/gameplay-scripting/detect-hits.md) section of the tutorial.

## Reset the blaster

In the sample laser tag game, blasters use a heat mechanic. Rather than reloading after a set number of blasts, they need time to "cool down" between each blast. This same cooldown delay occurs on both the client (`blastClient`) and the server (`blastServer`), with the server acting as the source of truth.

```lua
local blasterConfig = getBlasterConfig(player)
local secondsBetweenBlasts = blasterConfig:GetAttribute("secondsBetweenBlasts")

task.delay(secondsBetweenBlasts, function()
    local currentState = player:GetAttribute(PlayerAttribute.blasterStateServer)
    if currentState == BlasterState.Blasting then
        player:SetAttribute(PlayerAttribute.blasterStateServer, BlasterState.Ready)
    end
end)
```

The `secondsBetweenBlasts` attribute is part of the blaster configuration in **ReplicatedStorage** ⟩ **Instances** ⟩ **LaserBlastersFolder**. After the `secondsBetweenBlasts` delay passes, the player can blast again, and the entire process repeats. To help the player understand when they can blast again, the game includes a cooldown bar.

At this point, players can spawn and respawn, aim and blast, but the game still has to determine the results of each blast. In the next section of the tutorial, you will learn how to program the ability for the blaster to detect when the blast hits another player, then reduce the appropriate amount of player health according to blaster settings.