Tutorial · Game systems

How to make a sword in Roblox Studio

A sword in Roblox is a Tool with three jobs split across two scripts: the player's device notices the click and plays the swing, and the server decides who was hit and how much health they lose. This guide builds both, with a cooldown and a hitbox players cannot fake, and shows where an animation fits.

Updated 13 October 202619 min readBeginnerLuau

In short

Put a Tool named Sword with a part named Handle in StarterPack. A LocalScript in the Tool listens for Tool.Activated, plays the swing animation and fires a RemoteEvent with no arguments. A Script in ServerScriptService checks the cooldown and that the player is alive and holding the sword, finds Humanoids in a box in front of them with workspace:GetPartBoundsInBox, and calls Humanoid:TakeDamage once per target.

  • Do not deal damage from the blade's Touched event: the player's device controls its own character and can fire Touched from anywhere, which Roblox's network ownership page gives as its sword example.
  • The client sends no target and no damage. The server owns the cooldown, the hitbox and the damage amount.
  • TakeDamage does nothing while the target has a ForceField, such as the 10 seconds after spawning at a SpawnLocation.
On this page
  1. How it fits together
  2. 1. Build the Tool
  3. 2. Server script
  4. 3. LocalScript
  5. The hitbox
  6. Swing animation
  7. Older tutorials
  8. Exploit limits
  9. 4. Test it
  10. Extending it
  11. Common errors
  12. With RoCode
  13. Questions
  14. Sources

How a working sword fits together

IllustrationSwings at least 0.6 seconds apart take 20 health each, a click inside the cooldown does nothing, and the fifth hit knocks the rig out.

A sword that works in a live game is split between the player's device and the server. Roblox's tools page says tool input belongs in LocalScripts, because only the player's device knows when input happens, and that most tools need a LocalScript, a server Script and a RemoteEvent between them. This guide uses exactly that split. One swing goes like this:

  1. The player clicks, taps or presses the gamepad trigger with the Sword equipped. Tool.Activated fires in the LocalScript.
  2. The LocalScript plays the swing animation, if there is one, and fires the SwordSwing RemoteEvent. It sends nothing else.
  3. The server Script checks the cooldown, that the player is alive and that the Sword is in their hand.
  4. The server builds its own hitbox in front of the player's character and finds every Humanoid inside it.
  5. Each of those Humanoids loses health once, through TakeDamage.
The pieces of the sword
PieceWhere it livesWhat it does
Sword ToolStarterPackWhat the player holds. Roblox copies everything in StarterPack into the player's Backpack each time their character spawns.
Handle PartInside the ToolThe part the character grips. The blade is welded to it.
SwordClient LocalScriptInside the ToolHears the click, plays the swing animation and fires the remote.
SwordSwing RemoteEventReplicatedStorage (SwordService creates it)Carries "I swung" from the player's device to the server, with no arguments.
SwordService ScriptServerScriptServiceChecks each swing, finds who is in front of the player and deals the damage. The only place a hit can happen.
Explorer when the sword is finished. SwordService adds a RemoteEvent named SwordSwing to ReplicatedStorage when the game runs.
ServerScriptService
  SwordService Script new
StarterPack
  Sword Tool new
    Handle Part new
    Blade Part new
      WeldConstraint WeldConstraint new
    SwordClient LocalScript new

Step 1: Build the sword Tool

  1. In the Explorer, hover over Workspace, click the + button and insert a Tool. Rename it Sword. The server script looks for this exact name. You build the sword in the Workspace, as Roblox's tools page suggests, because that is where you can see its parts and move and resize them. The last step moves it to StarterPack.
  2. Insert a Part into the Tool and name it Handle. This is the hilt, where the character holds the sword. It must be a direct child of the Tool, and it must be the only part with that name: with two Handles, Roblox picks one at random, and the character may hold the sword by its blade.
  3. Insert a second Part into the Tool, name it Blade, and shape and place it so it sticks out of the top of the Handle.
  4. Insert a WeldConstraint into Blade. In the Properties window, set its Part0 to Handle and its Part1 to Blade, so the sword stays in one piece while the player carries it.
  5. Select both parts and make sure Anchored is off. An anchored part in a tool pins the character in place when they equip it. Also turn off CanCollide on both, so the blade does not bump into players and walls as you swing.
  6. Select the Tool and untick CanBeDropped. When it is ticked, pressing Backspace drops the sword into the Workspace.
  7. Drag the finished Sword from the Workspace into StarterPack. A Tool left in the Workspace is a pickup that one player collects, not a sword that starts in every player's Backpack.

A sword made from a single MeshPart works too: name that MeshPart Handle and skip the weld. If the character holds the sword at a strange angle when you test it, select the Tool and adjust its grip properties (GripPos, GripForward, GripRight and GripUp) in the Properties window.

Step 2: Add the server script

Hover over ServerScriptService, insert a Script named SwordService and paste the code below. It is the only place a sword hit can happen. The settings at the top are the lines you are meant to change.

Script in ServerScriptService named SwordService
-- Script in ServerScriptService named SwordService.
-- The only place a sword hit can happen: it checks the swing, finds targets in front
-- of the attacker with its own hitbox, and deals the damage.

local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")

-- Settings
local SWORD_NAME = "Sword" -- the Tool's name in StarterPack
local DAMAGE = 20
local COOLDOWN_SECONDS = 0.6 -- keep the same value in SwordClient
local HIT_RANGE = 6 -- studs in front of the attacker
local HIT_WIDTH = 5
local HIT_HEIGHT = 6
local FRIENDLY_FIRE = false -- false: players on the same team cannot hurt each other
-- Swings sent exactly COOLDOWN_SECONDS apart can arrive a little closer together after a
-- network delay, so the server allows this much slack instead of dropping a real swing
local NETWORK_SLACK = 0.1

-- The remote the sword's LocalScript fires. It carries no arguments: the server
-- decides who is hit, so there is nothing for a client to fake.
local swingEvent = Instance.new("RemoteEvent")
swingEvent.Name = "SwordSwing"
swingEvent.Parent = ReplicatedStorage

local lastSwing: { [Player]: number } = {}

local function isTeammate(attacker: Player, target: Player?): boolean
	if FRIENDLY_FIRE or not target then
		return false
	end
	-- Neutral players (no team) can always hit each other
	return not attacker.Neutral and not target.Neutral and attacker.Team == target.Team
end

local function onSwing(player: Player)
	-- 1. One swing per cooldown, however often the remote is fired
	local now = os.clock()
	local last = lastSwing[player]
	if last and now - last < COOLDOWN_SECONDS - NETWORK_SLACK then
		return
	end

	-- 2. The attacker must be alive and holding the sword
	local character = player.Character
	local humanoid = character and character:FindFirstChildOfClass("Humanoid")
	local rootPart = humanoid and humanoid.RootPart
	if not (character and humanoid and rootPart) or humanoid.Health <= 0 then
		return
	end
	local sword = character:FindFirstChild(SWORD_NAME)
	if not (sword and sword:IsA("Tool")) then
		return
	end
	lastSwing[player] = now

	-- 3. The server's own hitbox: a box in front of the attacker's root part
	local params = OverlapParams.new()
	params.FilterType = Enum.RaycastFilterType.Exclude
	params.FilterDescendantsInstances = { character }
	local boxCFrame = rootPart.CFrame * CFrame.new(0, 0, -HIT_RANGE / 2)
	local parts = workspace:GetPartBoundsInBox(boxCFrame, Vector3.new(HIT_WIDTH, HIT_HEIGHT, HIT_RANGE), params)

	-- 4. Damage each Humanoid in the box once, even if several of its parts are inside
	local alreadyHit: { [Humanoid]: boolean } = {}
	for _, part in parts do
		local model = part:FindFirstAncestorOfClass("Model")
		local target = model and model:FindFirstChildOfClass("Humanoid")
		if model and target and not alreadyHit[target] and target.Health > 0 then
			alreadyHit[target] = true
			if not isTeammate(player, Players:GetPlayerFromCharacter(model)) then
				target:TakeDamage(DAMAGE) -- does nothing while the target has a ForceField
			end
		end
	end
end

swingEvent.OnServerEvent:Connect(onSwing)

Players.PlayerRemoving:Connect(function(player: Player)
	lastSwing[player] = nil
end)

SwordService creates the SwordSwing RemoteEvent itself when the game starts, so there is nothing to insert in ReplicatedStorage by hand. If you already made a RemoteEvent with that name, delete it: the LocalScript could find yours, which nothing on the server listens to.

The settings at the top of SwordService
SettingValue hereWhat it changes
SWORD_NAME"Sword"The name of the Tool the server expects in the player's hand. Change it if your Tool has another name.
DAMAGE20Health each hit takes. A Humanoid starts with 100 health, so five hits knock out the test rig. Player characters regain 1% of their MaxHealth every second by default, so a player hit at full swing speed survives the fifth hit and needs a sixth.
COOLDOWN_SECONDS0.6The time between swings. Set the same value in SwordClient.
HIT_RANGE, HIT_WIDTH, HIT_HEIGHT6, 5, 6The hitbox in studs: how far it reaches in front of the player, how wide it is and how tall.
FRIENDLY_FIREfalseWhen false, players on the same team cannot hurt each other. Players with no team can always hit each other, and NPCs can always be hit.
NETWORK_SLACK0.1How much sooner than the cooldown the server still accepts a swing. Two clicks 0.6 seconds apart can arrive a little closer together after a network delay, and without this allowance the server would drop a real swing that the player already saw.

Step 3: Add the LocalScript in the Tool

Insert a LocalScript into the Sword Tool, name it SwordClient and paste this in:

LocalScript in the Sword Tool in StarterPack named SwordClient
-- LocalScript inside the Sword Tool, named SwordClient.
-- Tool.Activated fires on the player's device, so this script listens for the click,
-- tap or trigger, plays the swing animation and asks the server to swing.

local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local tool = script.Parent :: Tool
local swingEvent = ReplicatedStorage:WaitForChild("SwordSwing") :: RemoteEvent

local COOLDOWN_SECONDS = 0.6 -- the same value as in SwordService
local SWING_ANIMATION_ID = "" -- optional: "rbxassetid://" followed by your animation's id

local player = Players.LocalPlayer
local lastSwing = 0
local swingTrack: AnimationTrack? = nil
local trackCharacter: Model? = nil

-- Load the animation once per character. Animations started on your own character
-- from your device are shown to every player.
local function loadSwing()
	local character = player.Character
	if SWING_ANIMATION_ID == "" or not character or character == trackCharacter then
		return
	end
	local humanoid = character:FindFirstChildOfClass("Humanoid")
	local animator = humanoid and humanoid:FindFirstChildOfClass("Animator")
	if animator then
		local animation = Instance.new("Animation")
		animation.AnimationId = SWING_ANIMATION_ID
		swingTrack = animator:LoadAnimation(animation)
		trackCharacter = character
	end
end

tool.Equipped:Connect(loadSwing)

tool.Activated:Connect(function()
	local now = os.clock()
	if now - lastSwing < COOLDOWN_SECONDS then
		return -- the server enforces this too; checking here avoids wasted requests
	end
	lastSwing = now
	if swingTrack then
		swingTrack:Play()
	end
	swingEvent:FireServer()
end)

Leave SWING_ANIMATION_ID empty for now. The sword works without an animation, because the server decides the hit either way. Add a swing animation shows how to fill it in.

Activated fires for a mouse click, a tap on a phone and the trigger on a gamepad, so the same script works on every device. Roblox copies the Tool, and the LocalScript inside it, into each player's Backpack when their character spawns, so every player runs their own copy.

The LocalScript keeps its own cooldown only to avoid sending requests the server would ignore anyway. It is not a security check: a cheater can delete it, edit it or fire SwordSwing from their own code. The RemoteEvents guide explains why every remote has to be checked on the server, and Script vs LocalScript vs ModuleScript shows what a LocalScript can and cannot change.

How the server decides who is hit

When SwordSwing arrives, SwordService asks the client nothing. It builds a box in front of the attacker's character, where the server sees it, and asks the engine which parts are inside with workspace:GetPartBoundsInBox. That method replaces the older FindPartsInRegion3, which Roblox marks deprecated.

The box is centred 3 studs in front of the attacker's root part. That is the CFrame.new(0, 0, -HIT_RANGE / 2) offset: a CFrame faces along its negative Z axis, its LookVector. So the box reaches from the middle of the character to 6 studs ahead, and it is 5 studs wide and 6 studs tall. The query compares each part's bounding box, a quick box-shaped approximation of its volume, so round parts and meshes count as slightly bigger than they look. For a sword, that small margin helps.

What SwordService does with the parts in the box
StepCodeWhy
Skip the attackerOverlapParams with Exclude and the attacker's characterThe attacker's own arms, legs and sword are inside the box too.
Find the characterpart:FindFirstAncestorOfClass("Model")A part in the box can be a limb, a hat or a held tool. FindFirstAncestorOfClass matches the exact class name, so it passes over a hat (class Accessory) and a held tool (class Tool, even though a Tool is a kind of Model) and stops at the character model.
Find the Humanoidmodel:FindFirstChildOfClass("Humanoid")Anything with a Humanoid can be hit: players, NPCs and test dummies. Walls and floors are skipped.
Once per Humanoidthe alreadyHit tableSeveral parts of one character are usually in the box. Without the table, each of them would deal the damage again.
Only living targetstarget.Health > 0A character that is already knocked out is not hit again.
TeamsisTeammateWith FRIENDLY_FIRE false, players on the same team are skipped. A player whose Neutral property is true has no team.
Damagetarget:TakeDamage(DAMAGE)Lowers Health unless a ForceField protects the target.

The alreadyHit table is made fresh on every swing, so the same player can be hit again on the next swing once the cooldown has passed. That is the "once per target per swing" rule that Touched-based swords struggle with.

The script uses TakeDamage rather than setting Health. Roblox's reference says TakeDamage leaves a Humanoid alone while a ForceField protects it, and that setting Health directly ignores ForceFields. A player who spawns at a SpawnLocation gets a ForceField for 10 seconds by default (the SpawnLocation's Duration), so new arrivals cannot be hit the moment they appear.

Add a swing animation (optional)

SwordClient already plays an animation once you give it an ID. You make the animation once, in Studio's Animation Editor:

  1. Insert a rig to animate. On the Avatar tab, click Character to open the Rig Builder and pick a rig that matches your game's avatars, for example R15 and then Block Avatar.
  2. On the Avatar tab, click Clip Editor to open the Animation Editor, select the rig, and pose a short swing of the right arm.
  3. Set the priority to Action: click the … button in the editor's playback controls, hover over Set Animation Priority and choose Action. Action ranks above Movement, Idle and Core, so the swing plays over the walking and standing animations instead of losing to them.
  4. From the same … menu, choose Publish to Roblox. If your game belongs to a group, select that group in the Creator field before you submit.
  5. Copy the animation's asset ID from the Toolbox, and in SwordClient set SWING_ANIMATION_ID to "rbxassetid://" followed by that number.

SwordClient loads the swing through the character's Animator, not the older Humanoid:LoadAnimation, which Roblox marks deprecated. Roblox's Animator reference says animations that a player's device starts on that player's own character replicate to the server and to every other player, so the LocalScript can play the swing and everyone sees it. It also says the Animator must be created on the server, so never create one in a LocalScript. Player characters normally have one inside the Humanoid, which is where Roblox's own animation example looks for it.

The animation is only for show. Hits do not depend on it, so a slow, missing or edited animation never changes who is hit.

What older sword tutorials get wrong

If you are comparing this guide with a video or a forum post, these are the differences that matter, with the reason each pattern is a problem.

Common patterns in sword tutorials, and what this guide does instead
The tutorialThe problemThis guide
Deals damage in the blade's Touched eventThe attacker's device can fire Touched on its own sword from anywhere (Roblox's network ownership page), and Touched fires several times in quick succession for one contact (Roblox's debounce page).The server finds targets with its own box, once per Humanoid per swing.
Lowers Health in a LocalScriptA change a LocalScript makes to another character stays on that one device.All damage happens in SwordService.
Uses wait(0.5) for the cooldownRoblox marks the global wait() deprecated in favour of task.wait(), and a cooldown that only exists on the client stops nobody.Compares os.clock() times on the server. Nothing waits.
Plays the swing with Humanoid:LoadAnimationDeprecated in favour of Animator:LoadAnimation.Loads the swing through the Animator.
Finds targets with Region3 and FindPartsInRegion3Deprecated. Roblox says to use GetPartBoundsInBox with OverlapParams for new work.Uses GetPartBoundsInBox.
Puts a server Script inside the Tool that listens to ActivatedEvery player's sword runs its own copy of the server code, and these scripts often deal damage in Touched (the first row). With default settings a Script runs only under Workspace or ServerScriptService, so a Script in a Tool does not start until the sword is first equipped. Roblox's tools page describes most tools as a LocalScript for input, a server Script for the effect and a RemoteEvent between them.One SwordService in ServerScriptService handles every sword in the game.

What an exploiter can and cannot do

Assume a cheater controls everything on their own device. They can read and change your LocalScripts, and Roblox's security guide says they can fire RemoteEvents "at any frequency with arbitrary arguments". Here is what that means for this sword.

What a cheater can try, and what SwordService does about it
A cheater tries toResultWhy
Fire SwordSwing as fast as their code can, or use an auto-clickerAt most one swing every 0.5 seconds countsThe server cooldown is 0.6 seconds minus the 0.1 second network allowance. That allowance is the most a cheater gains.
Send a target, a damage number or a position with the remoteIgnoredonSwing reads only the player, which Roblox fills in itself. Anything else sent with the remote is never read.
Edit SwordClient, for example to remove its cooldownNo effect on the serverSwordService keeps its own settings in ServerScriptService, which is never sent to players.
Swing while dead, or without the sword in their handIgnoredSwordService checks that their Humanoid is alive and that a Tool named Sword is inside their character, which is where Roblox moves a tool when it is equipped.
Hit someone from across the mapNothing happensThe box is built around the attacker's character where the server sees it, and reaches only 6 studs ahead.
Teleport next to a player, then swingPossibleEach device controls its own character's movement, and Roblox lists teleporting to any position among the things a client can do with its own character. A server-side movement check can flag it.
Swing through a thin wallPossible inside the 6-stud boxThe box query does not look for walls. See No hits through walls for the fix.
Play any animation, or noneNo effect on hitsHits never depend on the animation. Roblox notes that cheaters can play arbitrary animations on their own character.

Two rows say "Possible", and that is honest. This sword stops a cheater from choosing who they hit, how hard and how often. It cannot stop them moving their own character. Roblox's security docs say clients control their character's movement and physics, and that movement checks have to be tuned to each game. For a competitive game, a server-side movement check is the next layer (the sprint guide builds one that warns about crude speed hacks and long teleports), or Roblox's server authority, which the docs described as a beta when we checked on 13 October 2026.

Step 4: Test it

  1. Add something to hit. On the Avatar tab, click Character to open the Rig Builder, choose a rig (for example R15, then Block Avatar) and it is inserted into the Workspace. Move it a few studs away from your SpawnLocation.
  2. Press Play, equip the Sword from the hotbar, walk up to the rig and click. A health bar appears over it after the first hit, because by default a Humanoid shows its bar only once its Health is below its MaxHealth. A Humanoid starts with 100 health, so the fifth hit knocks it out.
  3. Click as fast as you can. Only one swing every 0.6 seconds does anything.
  4. Step back about 8 studs from the rig, or turn your back to it, and swing. Nothing happens, because no part of it is inside the box.
  5. Test against a real player. Choose Server & Clients in the test mode dropdown, set 2 clients and press Play. Hit the other player within 10 seconds of them spawning: nothing happens, because of the spawn ForceField. After that, each hit takes 20. By default, player characters also regain 1% of their MaxHealth every second, so their health creeps back up between fights.
  6. If your game uses Teams, put both test players on the same team. With FRIENDLY_FIRE false, their swings do nothing to each other.

Keep the Output window open (Window menu, then Output) the whole time. These scripts print nothing when they work, so any red error from SwordService or SwordClient is a real problem. See Common errors.

Extending it into a combat system

Combos

Keep a swing counter per player on the server, next to lastSwing. If the previous swing was recent, add 1; otherwise start again at 1. Then pick the damage from the count, for example more damage on every third hit, and clear the counter on PlayerRemoving the same way. The client can keep its own count to choose which animation to play, because the animation never decides a hit. The box and the checks stay exactly the same.

Knockback

After TakeDamage, SwordService can push the target away with ApplyImpulse on the target's RootPart, in the direction of the attacker's rootPart.CFrame.LookVector. Roblox's reference says the push depends on the target's mass, and that a server Script can call it on a part the server owns or a part a client owns automatically. Each player's device simulates their own character, so test knockback on players with Server & Clients, not only on a rig.

Kill credit for a leaderboard

Just before target:TakeDamage(DAMAGE), record who hit last with target:SetAttribute("LastHitBy", player.UserId). In a server Script, connect each character Humanoid's Died event, read that attribute, find the attacker with Players:GetPlayerByUserId and add 1 to a Kills IntValue in their leaderstats. Create it next to Coins and Wins as in the leaderboard guide's Step 1, and change it on the server as in Step 2.

Blocking

Add a second RemoteEvent that the client fires when the block button goes down and up, and have the server set a Blocking attribute on that player's character, with its own rate limit. In SwordService, lower or skip the damage when the target's character has Blocking set. A cheater still chooses when they block, so keep the block short or give it a cooldown.

No hits through walls

The box ignores walls, so a swing can reach someone behind a thin wall inside the 6-stud box. To close that gap, cast a ray from the attacker's root part to the target's root part with workspace:Raycast, excluding the attacker's character, and skip the target if the ray hits something that is not part of the target.

Selling the sword or giving it out each round

StarterPack gives every player the sword on every spawn. To sell it instead, put it in the ShopItems folder from the shop GUI guide. That guide's catalogue already sells a Tool named ClassicSword, so either name this Tool ClassicSword and set SWORD_NAME to match, or change the catalogue entry. To hand it out only during a round, use the GiveRoundTool script from the round system guide: keep the Sword in ServerStorage instead of StarterPack, and change "RoundTool" in that script to "Sword".

Enemies to fight, and a ranged weapon

For an NPC that chases players, follow the NPC pathfinding guide. The sword damages it like any other Humanoid. For a weapon that hits from a distance with the same server-side rules, see the gun guide.

Common errors and fixes

Open the Output window (Window menu, then Output) before you press Play. Most sword problems either print an error there or come from a name that does not match.

Infinite yield possible on 'ReplicatedStorage:WaitForChild("SwordSwing")'

SwordClient is waiting for the remote that SwordService creates, so SwordService is not running. Check that it is a Script, not a LocalScript, directly inside ServerScriptService, that it is not disabled, and that Output shows no error from it.

I swing but nothing takes damage

Check these in order: SwordClient is a LocalScript, not a Script, directly inside the Sword Tool; the Tool is named exactly Sword, or whatever SWORD_NAME says; the target is within 6 studs in front of you; the target spawned more than 10 seconds ago, so its spawn ForceField is gone; and, if you use Teams, that you are not on the same team while FRIENDLY_FIRE is false.

The sword hits players but never my NPC

Spatial queries such as GetPartBoundsInBox never return a part whose CanQuery is off (CanQuery only takes effect while CanCollide is off too). Turn CanQuery back on for the NPC's body parts, and check that its Humanoid is a direct child of the NPC model.

My character gets stuck when I equip the sword

A part of the sword is anchored. Select every part inside the Tool and untick Anchored.

The character holds the sword by the blade

There is more than one part named Handle, so Roblox picked the wrong one. Keep exactly one Handle, the hilt, and rename the others. Then adjust the Tool's grip properties if the angle is still wrong.

The blade stays behind or falls when I equip the sword

The blade is not welded to the Handle. Add a WeldConstraint with Part0 set to Handle and Part1 set to the blade.

Damage shows on my screen but the other player's health does not change

The damage is dealt from a LocalScript, which changes only your own copy of the game. Deal damage only in SwordService, as in Step 2.

Build it with RoCode

If you would rather not wire this up by hand, RoCode can build it in the place you have open. It is an independent AI coding agent, not made by Roblox: you describe the feature in its web chat, and its Roblox Studio plugin creates the Tool, the parts and the scripts in your Explorer, so you are not copying code out of a chat window.

You type

Make a sword: a Tool named Sword in StarterPack with a Handle and a blade welded to it, a LocalScript that fires a RemoteEvent on Activated, and a server Script that checks a 0.6 second cooldown and damages Humanoids in a box in front of the player for 20.

RoCode does
  • Creates the Tool and its parts with Create Instance and sets their properties with Set Property
  • Writes SwordService and SwordClient with Create Script, and can first look for existing combat scripts with Search Scripts
  • Is told to compile-check each script it writes with Check Script, which compiles the code in Studio without running it (it does not type-check it)
  • Sets an undo point before and after each batch of changes, so Ctrl+Z steps back one batch at a time

RoCode cannot make the swing animation or upload one: make it in the Animation Editor and paste its ID. With plugin 3.9 or newer RoCode can run a short playtest and read the errors, but it does not move a character or click, so you test the swings yourself. Making a whole Roblox game with RoCode, system by system shows where a weapon fits. How RoCode connects to Studio.

Questions

Why does my sword hit the same player several times in one swing?

Because it deals damage from Touched. Roblox's own debounce page notes that Touched fires several times in quick succession for one contact, and every part of the target can touch the blade. This guide finds targets with one box query per swing and keeps a list, so each Humanoid loses health once per swing.

Should sword damage be in a LocalScript or a Script?

A Script on the server. Damage dealt from a LocalScript changes only that player's own copy of the game, and anything on the player's device can be changed by a cheater. The LocalScript should only hear the click, play the animation and fire the RemoteEvent.

How do I add a swing animation to my sword?

Make it in the Animation Editor, set its priority to Action, publish it to Roblox, and put its asset ID into SWING_ANIMATION_ID as rbxassetid:// followed by the number. SwordClient plays it with the character's Animator. Animations a player's device starts on that player's own character are shown to everyone.

Why does my sword animation not play?

Check three things. The priority: Action ranks above Movement, Idle and Core, so an Action swing plays over walking and standing, while a lower priority may lose to them. The owner: if the game belongs to a group, publish the animation with that group selected as the Creator. The ID: SWING_ANIMATION_ID must be the full rbxassetid:// text with your animation's number, not an empty string.

Can exploiters still cheat with this sword?

They cannot choose who is hit, how much damage a hit does or how often they swing, because the server decides all three; the most they gain is the 0.1 second network allowance on the cooldown. They can still move their own character, for example right next to someone, because each device controls its own character's movement. A server-side movement check is the next layer.

How do I make a combat system with combos?

Keep a swing counter per player on the server, reset it after a short pause, and pick the damage and the animation from the count. The hit check stays the same. Extending it also covers knockback, kill credit and blocking.

Sources and further reading

How the code was checked: every script on this page passes Luau's strict type checker against Roblox's API definitions (luau-lsp, 13 October 2026). A type check catches misspelt APIs and wrong types, not game logic, so playtest in Studio before you publish.

Try RoCode on your own place.

Free every day, no card required. Install the plugin, describe the feature, and review what lands in Studio.