How can I further improve this combat system?

Hey devs!

I recently added a combat system in into my roblox game. Instead of using fists, though, the player will use swords to fight other players.

Here is a little demo:

Here is the code that I have for the system right now:

Tool = script.Parent
Handle = Tool:WaitForChild("Handle")

local debounce = false
local walkAnimationId = "rbxassetid://ID"
local walkTrack

local idleAnimationId = "rbxassetid://ID"
local idleTrack

local swingIndex = 1
local swingAnimations = {
	"rbxassetid://ID", -- DPEM1
	"rbxassetid://ID", -- DPEM2
	"rbxassetid://ID", -- DPEM3
	"rbxassetid://ID"  -- DPEM4
}

local baseDamage = 10
local bigSwingDamage = 30
local isSwinging = false
local swingConnection = nil

function Create(ty)
	return function(data)
		local obj = Instance.new(ty)
		for k, v in pairs(data) do
			if type(k) == 'number' then
				v.Parent = obj
			else
				obj[k] = v
			end
		end
		return obj
	end
end

local BaseUrl = "rbxassetid://"

Players = game:GetService("Players")
Debris = game:GetService("Debris")
RunService = game:GetService("RunService")

Damage = baseDamage

Sounds = {
	Slash = Handle:WaitForChild("SwordSlash"),
	Unsheath = Handle:WaitForChild("Unsheath")
}

ToolEquipped = false

for i, v in pairs(Handle:GetChildren()) do
	if v:IsA("ParticleEmitter") then
		v.Rate = 20
	end
end

Tool.Enabled = true

function IsTeamMate(Player1, Player2)
	return (Player1 and Player2 and not Player1.Neutral and not Player2.Neutral and Player1.TeamColor == Player2.TeamColor)
end

function TagHumanoid(humanoid, player)
	local Creator_Tag = Instance.new("ObjectValue")
	Creator_Tag.Name = "creator"
	Creator_Tag.Value = player
	Debris:AddItem(Creator_Tag, 2)
	Creator_Tag.Parent = humanoid
end

function UntagHumanoid(humanoid)
	for i, v in pairs(humanoid:GetChildren()) do
		if v:IsA("ObjectValue") and v.Name == "creator" then
			v:Destroy()
		end
	end
end

local function GivePushback(character, magnitude)
	local bv = Instance.new("BodyVelocity", character:FindFirstChild("HumanoidRootPart"))
	bv.MaxForce = Vector3.new(10000000,10000000,10000000)
	bv.Velocity = character.HumanoidRootPart.CFrame.LookVector * -magnitude
	Debris:AddItem(bv, 0.1)
end

local touchCooldown = 0.2
local lastTouchTime = 0

function Blow(Hit)
	if not Hit or not Hit.Parent or not CheckIfAlive() or not ToolEquipped or not isSwinging then
		return
	end

	local currentTime = tick()
	if currentTime - lastTouchTime < touchCooldown then
		return
	end

	lastTouchTime = currentTime

	local character = Hit.Parent
	local humanoid = character:FindFirstChildOfClass("Humanoid")

	if Hit:IsDescendantOf(character) and humanoid then
		if character == Character then
			return
		end
		if humanoid then
			local player = Players:GetPlayerFromCharacter(character)
			if player and (player == Player or IsTeamMate(Player, player)) then
				return
			end

			if Player:WaitForChild("IsSafe").Value == true then
				return
			end

			UntagHumanoid(humanoid)
			TagHumanoid(humanoid)
			humanoid:TakeDamage(Damage)

			local knockbackMagnitude = swingIndex == 4 and 100 or 50
			GivePushback(character, knockbackMagnitude)
		end
	end
end

function Attack()
	Sounds.Slash:Play()

	local swingAnim = swingAnimations[swingIndex]
	local anim = Instance.new("Animation")
	anim.AnimationId = swingAnim
	local track = Humanoid:LoadAnimation(anim)
	track:Play()

	isSwinging = true

	if swingIndex == 4 then
		Damage = bigSwingDamage
	else
		Damage = baseDamage
	end

	swingIndex = swingIndex % #swingAnimations + 1

	if swingConnection then
		swingConnection:Disconnect()
	end

	swingConnection = Handle.Touched:Connect(function(hit)
		if isSwinging then
			Blow(hit)
			isSwinging = false
		end
	end)
end

Tool.Enabled = true
LastAttack = 0

function Activated()
	if debounce == false then
		debounce = true
		if not Tool.Enabled or not ToolEquipped or not CheckIfAlive() then
			return
		end
		Tool.Enabled = false
		Attack()
		LastAttack = RunService.Stepped:Wait()
		Damage = baseDamage
		Tool.Enabled = true

		task.wait(0.75)
		debounce = false
	end
end

function CheckIfAlive()
	return (((Player and Player.Parent and Character and Character.Parent and Humanoid and Humanoid.Parent and Humanoid.Health > 0 and Torso and Torso.Parent) and true) or false)
end

function Equipped()
	Character = Tool.Parent
	Player = Players:GetPlayerFromCharacter(Character)
	Humanoid = Character:FindFirstChildOfClass("Humanoid")
	Torso = Character:FindFirstChild("Torso") or Character:FindFirstChild("HumanoidRootPart")
	if not CheckIfAlive() then
		return
	end
	ToolEquipped = true
	Sounds.Unsheath:Play()

	local walkAnimation = Instance.new("Animation")
	walkAnimation.AnimationId = walkAnimationId
	walkTrack = Humanoid:LoadAnimation(walkAnimation)

	local idleAnimation = Instance.new("Animation")
	idleAnimation.AnimationId = idleAnimationId
	idleTrack = Humanoid:LoadAnimation(idleAnimation)

	if Humanoid.MoveDirection.Magnitude > 0 then
		walkTrack:Play(0.2)
	end

	Humanoid.Running:Connect(function(speed)
		if speed > 0 and walkTrack and not walkTrack.IsPlaying then
			idleTrack:Stop(0.2)
			walkTrack:Play(0.2)
		elseif speed == 0 and walkTrack and walkTrack.IsPlaying then
			walkTrack:Stop(0.2)
			idleTrack:Play(0.2)
		end
	end)
end

function Unequipped()
	ToolEquipped = false
	if walkTrack then
		walkTrack:Stop(0.2)
		walkTrack = nil
	end

	if idleTrack then
		idleTrack:Stop(0.2)
		idleTrack = nil
	end

	if swingConnection then
		swingConnection:Disconnect()
	end

	if Humanoid and Humanoid.RigType == Enum.HumanoidRigType.R6 then
		local Animator = Humanoid:FindFirstChildOfClass("Animator")
		if Animator then
			Humanoid:ChangeState(Enum.HumanoidStateType.Jumping)
			Humanoid:ChangeState(Enum.HumanoidStateType.Freefall)
		end
	end
end

Tool.Activated:Connect(Activated)
Tool.Equipped:Connect(Equipped)
Tool.Unequipped:Connect(Unequipped)

As you probably saw in the video, the system is a little glitchy. The player needs to swing multiple times to hit. Do you guys know a way I can fix this? Maybe a way I can improve the system (the hitbox)?

Thanks

-DarkPurpleElixr.

Check out this module.

If I’m reading the script correctly, you’re using the tool’s handle to register hits. I would recommend you would handle hitboxes independently from the tool’s model. Using .Touched on a small part like a handle combined with the movement latency just leads to very unresponsive hit registration. I would recommend you use workspace:GetPartBoundsInBox(), but of course there are other solutions so don’t feel limited to this one in particular. The hit registration part of the code would look something like this:

local HumanoidRootPart = Character:FindFirstChild("HumanoidRootPart")
local HitboxCFrame = HumanoidRootPart.CFrame * CFrame.new(Vector3.new(0, 0, -3)) -- The Vector3 is the offset from the player.
local HitboxSize = Vector3.new(6, 6, 6)

local Parameters = OverlapParams.new()
Parameters.FilterDescendantsInstances = {Character}
Parameters.FilterType = Enum.RaycastFilterType.Exclude

local Hitbox = workspace:GetPartBoundsInBox(HitboxCFrame, HitboxSize, Parameters)
for _,Hit in pairs(Hitbox) do
	if Hit.Name == "HumanoidRootPart" and Hit.Parent:FindFirstChild("Humanoid") then
		Blow(Hit)
	end
end

There are a a lot of ways to go about this, I just provided one of the easier ones so you can get a better grasp of how it works. The module Cozmic posted will work as well but it may take a bit more effort to get it working how you want it to.

1 Like

I will try your code out in the future. For now, I found @Cozmicweb 's answer to work out the best.

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.