A better way to make this hitbox?

Well, I’m programming a skill system and I’m currently working on a Gojo skill, aka “Hollow Purple”, all vfx are replicated to the clients through a replicator module script, however the hitbox is 100% done on the server, the big problem is that since Hollow Purple is launched by the client, the server doesn’t really know the exact position it is in to move the hitbox along with it, so I ended up opting for the option of using the tweenservice to move the hitbox along with a loop, I wanted to know if there is a more optimized way to do this.

ServerCode:

["Hollow Purple"] = {
		SkillName = "Hollow Purple";
		Input = Enum.KeyCode.V;
		CooldownTime = 2; -->> Debug Only
		InCooldown = false;
		IsHoldSkill = false;

		Action = function(Character : Model)
			local Player = Players:GetPlayerFromCharacter(Character)
			local Humanoid = Character:WaitForChild("Humanoid")
			
			if Character:GetAttribute("UsingSkill") then
				return
			end

			--// Animations
			local PurpleAnimation = Humanoid:FindFirstChildWhichIsA("Animator"):LoadAnimation(LimitlessAnimations:FindFirstChild("HollowPurple"))
			
			--// Attributes
			Character:SetAttribute("CanRun",false)
			Character:SetAttribute("CanDash",false)

			Character:SetAttribute("IFrames",true)
			Character:SetAttribute("UsingSkill",true)

			Humanoid.WalkSpeed = 2
			Humanoid.JumpPower = 0
			
			--// Purple VFX 
			PurpleAnimation:Play()
			Events:FindFirstChild("CameraShaker"):FireClient(Player,"StartShake",1.5,3,0.1,1,6)
			
			PurpleAnimation:GetMarkerReachedSignal("Blue"):Wait()
			Replicator:FireAllDistanceClients(Character,500,{Folder = Modules:WaitForChild("VisualEffects"):FindFirstChild("Skills"),ModuleName = "Limitless_Effects",FunctionName = "HollowPurple_ShowBlue"},Player)
			
			PurpleAnimation:GetMarkerReachedSignal("Red"):Wait()
			Replicator:FireAllDistanceClients(Character,500,{Folder = Modules:WaitForChild("VisualEffects"):FindFirstChild("Skills"),ModuleName = "Limitless_Effects",FunctionName = "HollowPurple_ShowRed"},Player)
			--red
			
			PurpleAnimation:GetMarkerReachedSignal("Release"):Wait()
			Replicator:FireAllDistanceClients(Character,500,{Folder = Modules:WaitForChild("VisualEffects"):FindFirstChild("Skills"),ModuleName = "Limitless_Effects",FunctionName = "HollowPurple"},Player)
			
			local HumanoidRootPart = Character:FindFirstChild("HumanoidRootPart")
			if not HumanoidRootPart then return end
			
			local AllHitboxCharacters = {}

			local Hitbox = HitboxModule.RealTimeHitbox(
				CFrame.new((HumanoidRootPart.Position + Vector3.new(0,5,0)) + HumanoidRootPart.CFrame.LookVector * 20).Position,
				Character,
				35,
				{Character},
				false
			)

			local Direction = HumanoidRootPart.CFrame.LookVector
			Hitbox.PhysicPart.CFrame = CFrame.new(Hitbox.PhysicPart.Position, Hitbox.PhysicPart.Position + Direction)
			local TweenCompleted = false

			local HitboxTween = TweenService:Create(
				Hitbox.PhysicPart,
				TweenInfo.new(2.5),
				{CFrame = Hitbox.PhysicPart.CFrame + (Direction * 400)}
			)

			HitboxTween:Play()
			task.spawn(function()
				HitboxTween.Completed:Wait()
				TweenCompleted = true
			end)

			repeat
				local PartsInside = Hitbox.GetPartsInside()

				for index, Parts in PartsInside do
					if Parts and Parts.Parent then
						local Humanoid = Parts.Parent:FindFirstChild("Humanoid")
						if Humanoid then
							local HitCharacter = Parts.Parent
							if HitCharacter:IsA("Model") and not table.find(AllHitboxCharacters, HitCharacter) then
								table.insert(AllHitboxCharacters, HitCharacter)

								RagdollModule:Ragdoll(HitCharacter)
								CombatModule.LocalFunctions:StunCharacter(HitCharacter, 5)

								local KnockbackForce = (Hitbox.PhysicPart.Position - HumanoidRootPart.Position).Unit * math.random(600,1200)
								local UpwardForce = Vector3.new(0, math.random(1200,1600), 0)
								HitCharacter.PrimaryPart:ApplyImpulse(KnockbackForce + UpwardForce)

								task.delay(5, function()
									RagdollModule:unRagdoll(HitCharacter)
								end)
							end
						end
					end
				end
				
				task.wait(0.15)
			until (TweenCompleted or not Hitbox.PhysicPart.Parent)
			
			Hitbox.DestroyHitbox()
			
			AllHitboxCharacters = nil
		
			Character:SetAttribute("CanRun",true)
			Character:SetAttribute("CanDash",true)

			Character:SetAttribute("IFrames",false)
			Character:SetAttribute("UsingSkill",false)

			Humanoid.WalkSpeed = 14
			Humanoid.JumpPower = 50

		end	
	}
External Media

Video :arrow_up:

1 Like

So you create a part for the hitbox and then use something like .Touched event to detect hits. And the issue is aligning the hitbox with the visuals. Is this correct?

1 Like

I use overlaps for the hitbox, the problems is because its to laggy for the server and also yeah i cant align with the visual effect.

im trying to find a better way to make less laggier and sync with the vfx

Here are a few optimizations I’ve made to your code.

["Hollow Purple"] = {
	SkillName = "Hollow Purple";
	Input = Enum.KeyCode.V;
	CooldownTime = 2; -->> Debug Only
	InCooldown = false;
	IsHoldSkill = false;

	Action = function(Character : Model)
		local Player = Players:GetPlayerFromCharacter(Character)
		local Humanoid = Character:WaitForChild("Humanoid")
		
		if Character:GetAttribute("UsingSkill") then
			return
		end

		--// Animations
		local PurpleAnimation = Humanoid:FindFirstChildWhichIsA("Animator"):LoadAnimation(LimitlessAnimations:FindFirstChild("HollowPurple"))
		
		--// Attributes
		Character:SetAttribute("CanRun",false)
		Character:SetAttribute("CanDash",false)

		Character:SetAttribute("IFrames",true)
		Character:SetAttribute("UsingSkill",true)

		Humanoid.WalkSpeed = 2
		Humanoid.JumpPower = 0
		
		--// Purple VFX 
		PurpleAnimation:Play()
		Events:FindFirstChild("CameraShaker"):FireClient(Player,"StartShake",1.5,3,0.1,1,6)
		
		PurpleAnimation:GetMarkerReachedSignal("Blue"):Wait()
		Replicator:FireAllDistanceClients(Character,500,{Folder = Modules:WaitForChild("VisualEffects"):FindFirstChild("Skills"),ModuleName = "Limitless_Effects",FunctionName = "HollowPurple_ShowBlue"},Player)
		
		PurpleAnimation:GetMarkerReachedSignal("Red"):Wait()
		Replicator:FireAllDistanceClients(Character,500,{Folder = Modules:WaitForChild("VisualEffects"):FindFirstChild("Skills"),ModuleName = "Limitless_Effects",FunctionName = "HollowPurple_ShowRed"},Player)
		--red
		
		PurpleAnimation:GetMarkerReachedSignal("Release"):Wait()
		Replicator:FireAllDistanceClients(Character,500,{Folder = Modules:WaitForChild("VisualEffects"):FindFirstChild("Skills"),ModuleName = "Limitless_Effects",FunctionName = "HollowPurple"},Player)
		
		local HumanoidRootPart = Character:FindFirstChild("HumanoidRootPart")
		if not HumanoidRootPart then return end
		
		local AllHitboxCharacters = {}

		local Hitbox = HitboxModule.RealTimeHitbox(
			CFrame.new((HumanoidRootPart.Position + Vector3.new(0,5,0)) + HumanoidRootPart.CFrame.LookVector * 20).Position,
			Character,
			35,
			{Character},
			false
		)

		local Direction = HumanoidRootPart.CFrame.LookVector
		Hitbox.PhysicPart.CFrame = CFrame.new(Hitbox.PhysicPart.Position, Hitbox.PhysicPart.Position + Direction)
		--local TweenCompleted = false

		local HitboxTween = TweenService:Create(
			Hitbox.PhysicPart,
			TweenInfo.new(2.5),
			{CFrame = Hitbox.PhysicPart.CFrame + (Direction * 400)}
		)

		HitboxTween:Play()
		--[=[
		task.spawn(function()
			HitboxTween.Completed:Wait()
			TweenCompleted = true
		end)
		--]=]

		repeat
			local PartsInside = Hitbox.GetPartsInside()

			for index, Parts in PartsInside do
				if Parts and Parts.Parent then
					local Humanoid = Parts.Parent:FindFirstChild("Humanoid")
					if Humanoid then
						local HitCharacter = Parts.Parent
						if HitCharacter:IsA("Model") and not table.find(AllHitboxCharacters, HitCharacter) then
							table.insert(AllHitboxCharacters, HitCharacter)

							RagdollModule:Ragdoll(HitCharacter)
							CombatModule.LocalFunctions:StunCharacter(HitCharacter, 5)

							local KnockbackForce = (Hitbox.PhysicPart.Position - HumanoidRootPart.Position).Unit * math.random(600,1200)
							local UpwardForce = Vector3.new(0, math.random(1200,1600), 0)
							HitCharacter.PrimaryPart:ApplyImpulse(KnockbackForce + UpwardForce)

						--	task.delay(5, function()
						--		RagdollModule:unRagdoll(HitCharacter)
						--	end)
							task.delay(5, RagdollModule.unRagdoll, RagdollModule, HitCharacter)
						end
					end
				end
			end
			
			task.wait(0.15)
		until (HitboxTween.PlaybackState.Value >= 4 or not Hitbox.PhysicPart.Parent) --(TweenCompleted or not Hitbox.PhysicPart.Parent)
		
		Hitbox.DestroyHitbox()
		
		AllHitboxCharacters = nil
	
		Character:SetAttribute("CanRun",true)
		Character:SetAttribute("CanDash",true)

		Character:SetAttribute("IFrames",false)
		Character:SetAttribute("UsingSkill",false)

		Humanoid.WalkSpeed = 14
		Humanoid.JumpPower = 50

	end	
}

As far as timing goes, you know that the cframe/hitbox is going to be ping time behind the caster’s visuals unless the trigger comes from the server.

client casts (visuals begin) → server hitboxes and deletes → deletion replicates
Thus client’s ping is the delay.
If the trigger is from the server, the visuals and hitboxes will align.

I can only really think of doing Blockcast in Parallel frame by frame.
At the very least, raycasting or box detection code can be ran in parallel while Touched cannot.

As I was writing this, you mentioned overlaps for the hitbox which I don’t know what that is.

1 Like

I really didn’t know about the “PlaybackState” of Tween, nor this other way of using task.delay. I just ran some tests, and it improved performance a bit. Regarding the hitbox, by “overlaps” I was referring to OverlapParams, but actually, I was using :GetPartsInPart().

Also, I just discovered that I can set the maximum number of parts that OverlapParams can return in the function by changing the MaxParts value.

Yeah so I guess you would be able to parallelize your hitbox code in that case. I’m glad I learned about OverlapParams & those other functions lol.

1 Like

I see the topic has been solved but, just mentioning, don’t you think you’d be better off splitting movesets (like “Hollow Purple”) to many modules instead of tables? Because with so many movesets at the same place you’ll eventually have big pain adjusting the code

Or if you think this table structure has a benefit I’d like to hear it :smiley:

Good luck with your system!

All the skills of each moveset are stored in a table for the framework to work properly. This module contains only the skills of this moveset. If I want to create another moveset for a different anime character, it would be another module containing only their skills.


1 Like

Client-Side

1 Like

Hmm I see, so how I see it is;

  • Create new module for each skill like this;

local m = {Divergent Fist = SkillName, Action = blabla}
return m

  • you could require each of the children modules, and merge all tables together, so at the end

Like this you’d keep managability and keep the functionality in my opinion, but it still comes to preference really.

I really like the organization by the way!

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