How can I optimize this melee combat script

:question: How can I optimize this melee combat script:

  • :mad: What does the code do and what are you not satisfied with?
    • It is very laggy and the hitbox debounce (when hit you cannot be hit again until animation is done) is not consistent and sometimes doesn’t do any damage.
    • The code isn’t very optimized.
  • :thinking: What potential improvements have you considered?
    • I do not know how to fix my hitbox debounce. Maybe I’m just overcomplicating it. I just need someone coming from another perspective to help.
  • :slightly_smiling_face: How (specifically) do you want to improve the code?
    • I want the hit debounce consistent all the time and the code to run smoothly if possible.

:scroll: Here is the code (it is a module script in serverScriptService):

If there is anything unclear please ask!

local module = {name="MeleeFramework"}
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local function len(t)
	local n = 0
	for _ in pairs(t) do n=n+1 end
	return n
end

function module:new(tool, stat)
	local hitbox = tool:WaitForChild("HitBox")
	local data = {
		equipped = false,
		canSwing = true,
		swinging = false,
		animations = {},
		attacks = stat.animations.Attacks,
		currentAttack = 1,
		hitHumans = {},
	}
	
	function data:stopAnimations()
		for _, v in pairs(self.animations) do
			v:Stop()
		end
	end
	function data:touchedHitBox(hit, callback)
		if self.equipped and self.swinging then
			local humanoid = hit.Parent:FindFirstChild("Humanoid")
			if humanoid then
				if not table.find(self.hitHumans, humanoid) then
					table.insert(self.hitHumans, humanoid)
					humanoid:TakeDamage(stat.damage)
					hitbox.hit:Play()
					if callback then coroutine.wrap(callback)(humanoid) end
				end
			end
		end
	end
	function data:equip()
		local humanoid = tool.Parent:WaitForChild("Humanoid")
		self:stopAnimations()
		self.animations.Idle = humanoid:LoadAnimation(stat.animations.Idle)
		
		for i, v in pairs(stat.animations.Attacks) do
			self.animations[i] = humanoid:LoadAnimation(v[1])
		end
		
		self.animations.Idle:Play()
		self.equipped = true
	end
	function data:unequip()
		self.equipped = false
		self:stopAnimations()
	end
	function data:playAttackAnimation()
		local anim = self.animations["Attack" .. self.currentAttack]
		local rootAnim = stat.animations.Attacks[anim.Name]
		anim:Play()
		
		if self.currentAttack < len(stat.animations.Attacks) then
			self.currentAttack+=1
		else
			self.currentAttack=1
		end
		
		coroutine.wrap(function()
			task.wait(rootAnim[3])
			self.canSwing = true
		end)()
		coroutine.wrap(function()
			task.wait(rootAnim[2])
			for i, v in pairs(self.hitHumans) do table.remove(self.hitHumans, i) end
			self.swinging = false
			hitbox.Trail.Enabled = false
		end)()
		if rootAnim[2]>rootAnim[3] then
			warn("MeleeFramework_Warning: Never make attackLength longer than cooldown!")
		end
	end
	function data:swing(player, callback)
		if self.equipped and self.canSwing then
			self.swinging = true
			self.canSwing = false
			self:playAttackAnimation()
			
			hitbox.swing:Play()
			hitbox.Trail.Enabled = true
			if player then ReplicatedStorage.Events.EditStamina:FireClient(player, false, 0.05) end
			if callback then coroutine.wrap(callback)() end
		end
	end
	
	return data
end

function module:effect(effect, args)
	if effect=="burn" then
		local burn = script.burnScript:Clone()
		burn.damage.Value = args.Damage
		burn.length.Value = args.Length
		burn.Parent = args.Character
		burn.Disabled = false
	end
end

return module

So not really entirely sure what the sword effect is doing or how it manages debounce in general.

		coroutine.wrap(function()
			task.wait(rootAnim[3])
			self.canSwing = true
		end)()
		coroutine.wrap(function()
			task.wait(rootAnim[2])
			for i, v in pairs(self.hitHumans) do table.remove(self.hitHumans, i) end
			self.swinging = false
			hitbox.Trail.Enabled = false
		end)()

That might be part of the issue regarding hitbox debounce

Generally speaking here’s how I manage my combat system

-- Variable to store the previous attack
SwordCore.currentAttack = 1
-- Attack folder
SwordCore.attacks = {
    {Animation = Animations["Attack1"], Cooldown = 5},
    {Animation = Animations["Attack2"]},
}
SwordCore.swinging = false
function SwordCore:Swing()
    if self.swinging then return end -- Guard clause
    self.swinging = true

    local animation = self.attacks[self.currentAttack]
    animation:Play()
    animation.Completed:Wait() -- Wait until the animation has been completed

    self.currentAnimation += 1
    self.swinging = false
end)
1 Like