Tutorial·2026-04-29·6 min

Building a Combat System with Forge AI in 42 Seconds

A walkthrough of generating a complete Roblox combat system — sword, health bar, damage numbers, respawn — from a single Forge AI prompt.

S

Sametcan Tasgiran

Founder & Developer · Forge AI

The prompt

build me a combat system with a sword, health bar, damage numbers, and respawn

42 seconds later: 5 files, 140 lines, one credit. Let's walk through what Forge AI actually produced and why it works on first try.

What Forge generated

Five scripts placed into the right Roblox services:

ServerScriptService/CombatServer.lua            (84 lines)
ServerStorage/Weapons/ClassicSword.rbxm         (mesh + Tool)
ReplicatedStorage/Remotes/CombatRemote          (RemoteEvent)
StarterGui/HealthBarUI                          (Frame + script)
StarterPlayerScripts/CombatClient.lua           (56 lines)
StarterGui/HUD/DamageNumbers                    (pooled labels)

Plus one wired-up Tool that ends up in every player's Backpack on join. Plus respawn handling on the server.

CombatServer.lua — server-authoritative damage

The most important script in any combat system is the server-side damage handler. Client-trusted damage is the #1 anti-exploit failure in Roblox games. Forge writes this with validation by default:

--!strict
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local CombatRemote = ReplicatedStorage:WaitForChild("Remotes"):WaitForChild("CombatRemote")

local cooldowns: { [number]: number } = {}
local DAMAGE = 18
local COOLDOWN = 0.45
local RANGE = 8

CombatRemote.OnServerEvent:Connect(function(player, targetUserId)
    local now = tick()
    if cooldowns[player.UserId] and now - cooldowns[player.UserId] < COOLDOWN then return end
    local attacker = player.Character and player.Character:FindFirstChild("HumanoidRootPart")
    local target = Players:GetPlayerByUserId(targetUserId)
    local victim = target and target.Character and target.Character:FindFirstChild("HumanoidRootPart")
    if not attacker or not victim then return end
    if (attacker.Position - victim.Position).Magnitude > RANGE then return end
    cooldowns[player.UserId] = now
    local humanoid = victim.Parent:FindFirstChildOfClass("Humanoid")
    if humanoid then humanoid:TakeDamage(DAMAGE) end
end)

Three checks before any damage applies: cooldown (no spam), valid targets (attacker and victim both have HumanoidRootPart), and range (attacker within 8 studs of target). The client only fires the remote with a target ID — all damage math runs server-side.

CombatClient.lua — input + animation

The client script handles the swing animation and fires the remote when the sword connects. Forge's version uses Tool.Activated as the trigger and a magnitude check to find the closest valid target:

--!strict
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local CombatRemote = ReplicatedStorage:WaitForChild("Remotes"):WaitForChild("CombatRemote")

local player = Players.LocalPlayer
local tool: Tool? = nil

local function onActivated()
    local char = player.Character
    local hrp = char and char:FindFirstChild("HumanoidRootPart")
    if not hrp then return end
    -- Find closest other player within 8 studs
    local closest, closestDist = nil, math.huge
    for _, p in Players:GetPlayers() do
        if p == player then continue end
        local theirChar = p.Character
        local theirHrp = theirChar and theirChar:FindFirstChild("HumanoidRootPart")
        if theirHrp then
            local d = (hrp.Position - theirHrp.Position).Magnitude
            if d < closestDist and d <= 8 then closest, closestDist = p, d end
        end
    end
    if closest then CombatRemote:FireServer(closest.UserId) end
end

player.CharacterAdded:Connect(function(char)
    char.ChildAdded:Connect(function(child)
        if child:IsA("Tool") and child.Name == "ClassicSword" then
            tool = child
            child.Activated:Connect(onActivated)
        end
    end)
end)

Why it works on first try

Three things keep this from being a "compile in tester, fail in Studio" wall of code:

  • **Type-safe.** Both scripts ship --!strict with proper type annotations on tables and parameters. luau-lsp catches mistakes before they ship.
  • No client trust. Damage, cooldown, range — all validated server-side. The client only sends "I want to hit this target."
  • **Real services referenced.** Forge reads your hierarchy on every prompt. ReplicatedStorage:WaitForChild("Remotes") works because Forge created the folder and RemoteEvent in the same generation.

What you'd add manually after

  • **Animation.** Tool ships without a swing animation by default; load rbxassetid://507770239 or your own.
  • Sound effects. Hit / miss / equip sounds.
  • Visual hit feedback. Forge ships pooled DamageNumbers but doesn't add hit sparks.
  • Balance. Damage and cooldown numbers are starter values.

These are 30-minute polish jobs, not core architecture. The combat skeleton is in.

Run cost

ItemCreditsUSD equivalent
Sonnet generation (5 files)5~$0.05
Total5~$0.05

A combat system for under a dime. Free tier ships with 20 credits — that's four full systems for free.

Try it

Sign up for 20 free credits. Install the Forge AI plugin inside Roblox Studio. Type the same prompt above, get the same five-file output. Compare with whatever you'd get from copy-paste-ChatGPT after 30 minutes of integration work.

For more on the broader plugin, see the Luau generator overview or the game builder page.

Related system guides

Browse all 12 system guides.

Want to try Forge AI?

20 credits on signup, no credit card. Plugin installs in two minutes.

Start free →