Eight seconds of beam, aimed with the camera.
Warning
Sword Masters was abandoned in October 2024, and most of its animations, sounds and images belong to the group that owned it, so the game can’t run as it did and this system can’t be shown on video.
Abstract. Laser beam is the Legendary ability in Sword Masters and the longest one. When you press E, the sword shrinks until it disappears, the character pulls a laser device out of its arm and mounts it on its chest, and the game waits for a click. Then the beam fires for 8 seconds. While it’s on, the player moves slowly, can jump once every 3 seconds, and aims by turning the camera, because the ability forces shift lock and the character turns with it. Every frame, a box cast against the map measures how far the beam reaches, up to 135 studs, and a query with a box aligned to the beam finds who it touches. Then the beam retracts, the device goes back into the arm and the sword grows back. It’s a long ability in the sense of the ability system: the whole sequence is a single Load function on the server. This article goes through the sequence phase by phase, the aiming, how the beam is measured and how its hits are found, the damage per second (which isn’t what the configured number suggests), and the leftovers.
1. The problem
A continuous beam has two problems a projectile doesn’t. The first is aiming: the player has to be able to sweep the beam over a group of enemies, and the server has to know where it’s pointing every frame without asking the client. The second is that what you see has to match what it hits. If the beam goes through a wall on screen but the damage stops at the wall, or the other way around, the ability feels broken. I solved both by deriving everything, the visual and the damage, from the same piece: the device on the character’s chest.
2. The sequence
Load runs these phases, checking at every wait whether the character died, in which case it cleans up and leaves:
- Checks and sets
Busy, lowersMoreSpeedto 0.25 and welds the sword’s handle to the right arm. - Swaps the walk, run, idle and jump animations of the character’s
Animatescript for the laser versions, saving the originals. - Plays the takeout animation and shrinks the sword with
ScaleToover 20 frames, from 0.95 down to almost 0. - Waits for the animation to reach 0.95 s and mounts the device on the right arm, then waits for 1.95 s and moves it to the chest.
- Asks the client to wait for a click (
Callback:InvokeClient(plr, "Laser beam", "getch")). - Plays the activation animation, forces shift lock on the client, drops the walk speed to 4 and, when the animation ends, fires the beam.
- Runs the beam for 8 seconds (sections 4 and 5).
- Retracts the beam over one second and restores speed, jump and shift lock.
- Plays the closing animation, puts the device back in the arm at 2.111 s, destroys it at 3.666 s and grows the sword back over 20 frames.
- Restores the original animations, clears
Busyand returns.
The times in steps 4 and 9 are the points where the animations have markers, and the comments still name them (LaserSwitch, LaserInTorso, LaserRips, SwordbackLaser). Instead of listening for the markers, the code polls the track’s TimePosition every frame. It works, but it ties the code to the exact length of each animation: if an animator moves a marker, the code doesn’t notice.
The sword’s shrink loop has a small detail:
for i = 95,0,-5 do
runSvc.Heartbeat:Wait();
i += 0.01
sword:ScaleTo(i/100);
end
The extra 0.01 means the last step scales the sword to 0.0001 and not to exactly 0, so the model never reaches a zero scale.
3. Aiming with the camera
The beam comes out of the device on the chest, and the device is welded to the torso, so the beam points wherever the character faces. Shift lock makes the character face the camera’s horizontal direction. Forcing it on for the duration of the ability turns the camera into the aiming control: the player moves the mouse and the beam sweeps with it. The server doesn’t need to ask anything, because the character’s rotation replicates on its own.
The beam stays horizontal. The torso doesn’t tilt up or down with the camera, so you aim in yaw and not in pitch.
4. Measuring the beam
Every frame, the server casts a 1×1×1 stud box [1] from the device along its look vector, up to the maximum distance:
local ray = workspace:Blockcast(chest.CFrame, Vector3.one,
chest.CFrame.LookVector * Settings[self].MaxDistance, lib.TheParams2)
ray = ray and ray.Distance or Settings[self].MaxDistance;
laser.Out.Position = (laser.CFrame:ToWorldSpace(CFrame.new(0,0,-ray))).Position;
The cast only sees the map (the lobby and the round’s map, with RespectCanCollide), so characters don’t cut the beam. The length of the beam is
and the beam’s end piece, Out, is placed studs along the device’s . I used a box cast instead of a ray because the beam has volume. A ray would slip through cracks narrower than a stud that a one-stud-thick beam shouldn’t go through, and with a box the cast has the same thickness as the hit volume in section 5.
5. Finding hits
With the beam measured, the server looks for what’s inside it with a box query:
local parts = workspace:GetPartBoundsInBox(
laser.CFrame:Lerp(laser.Out.CFrame, 0.5),
Vector3.new(1, 1, (laser.Position - laser.Out.Position).Magnitude),
lib.TheParams
)
The box is centered halfway between the start and the end, with the rotation of the beam, and measures . It’s the beam itself as a volume: an oriented box [2] whose long axis is the beam. This query excludes the map, so it only returns characters and loose objects. GetPartBoundsInBox compares the box against each part’s bounding box and not against its exact shape, which is cheaper and, for the blocky parts of an R6 character, nearly the same.
Since both queries come out of the device’s CFrame in the same frame, the beam that’s drawn and the area that hurts always match: if the beam stops at a wall, the damage stops too.
6. Damage per second
The configured damage is 0.4. It looks small, and it isn’t per second. The loop goes through every part the query returned and, for every part that belongs to a character with a Humanoid, applies . Since it runs every frame and counts parts and not characters, the damage per second to a character with parts inside the beam is
where is the server’s frame rate. At 60 Hz:
| Parts in the beam () | DPS | Total in 8 s |
|---|---|---|
| 1 | 24 | 192 |
| 2 | 48 | 384 |
| 3 | 72 | 576 |
| 4 | 96 | 768 |
A one-stud-thick beam at chest height can cross two or three parts of a character (torso, arms, root). With , the beam kills any regular zombie (20 to 75 health) in under two seconds and takes down the Crystal Guard (250) in a little over five. The Victorious Zomdiator (450) survives a beam that only touches two of its parts. In PvP the same applies to players, and outside PvP players are ignored.
There’s a detail in how the damage arrives. The loop waits a tenth of a second before each hit and another tenth after, inside the Heartbeat callback. Each frame starts a new call to the callback in its own thread, so the waits don’t slow the beam’s rate, but they delay the damage: the hit on the -th part of a frame lands seconds later, and the damage keeps landing for a moment after the beam goes off. The kill is credited once per character, with a DeadByLaser tag.
7. Moving while beaming
While the beam is on, the walk speed drops from 16 to 4 and the walk, idle and jump animations are the laser versions. The first jump is allowed, and right after it the jump power goes to zero for 3 seconds, so a player can jump at most once every 3 seconds while beaming.
The ability also welds a 20-stud cube to the root, invisible and with collisions, touch and queries disabled. It isn’t Massless, though, so it adds mass to the character’s assembly: units at the density of default plastic, against about 13 for a standard R6 body. While the beam is on, the character weighs a few hundred times more, and forces that push it barely move it.
8. Limitations
The damage depends on the frame rate and the number of parts, two things the person tuning the ability doesn’t see in the number 0.4. It should be a damage per second multiplied by the frame’s deltaTime, applied once per character per frame.
Waiting inside the Heartbeat callback works, but it creates a thread per frame that lives for a few tenths of a second, and it makes the damage arrive late. The phases tied to TimePosition depend on the exact length of the animations. And the click goes through InvokeClient, with the same risk the ability system article describes: if the client never answers, the server keeps waiting with the device on the chest.
9. What I took away
Deriving the visual and the damage from the same CFrame in the same frame was what made the ability feel right, and it’s a rule I’ve kept: if two things have to match on screen, compute them from the same source and not from two that are supposed to agree. Aiming with the camera through shift lock also saved me a remote, since the server reads the aim from the character’s rotation, which already replicates. What I’d change is the damage model, which should be per second and per character, and the markers, which should be listened for and not polled by time.
References
- Roblox Creator Documentation. “Raycasting” and “Spatial queries” (
WorldRoot:Blockcast,WorldRoot:GetPartBoundsInBox). - C. Ericson. Real-Time Collision Detection. Morgan Kaufmann, 2004.
This text by Mastedore. All rights reserved.