Help with Decision: Constraints or .Stepped (server-sided)?

Hello :smiley:
I am currently trying to make a fireball that follows the target similar to many MMORPGS with tab-targeting like WoW. To be more precise, the fireball should always move towards the target, even when the target itself is moving, preferably at a constant speed. The fireball should not collide with anything and should not be affected by gravity, so I need full controll over the fireballs position and rotation without physics interfering. I could even do without Touched events. Also it should be handled by the server and not the client.

My current solution does what I want, however I have heard it could cause immense performance issues or lag (not sure) if used on the server and not the client:

RunService.Stepped:Connect(function()
	if (target.Position-fireBall.Position).Magnitude > 10 then -- stop when in hit-range
		local direction = target.Position - fireBall.Position -- move direction for FireBall
		fireBall.Position += 1 * direction / direction.Magnitude 
	end
	fireBall.CFrame = CFrame.new(fireBall.Position, target.Position)
end)

(Suppose 15 players and another 15 enemies cast a fireball, maybe even a rain of fireballs)

I heard that using Constraints would be faster but they don’t seem to be a good fit for what I am trying to achieve. I played around with different constraints and never got a decent result.

Another solution I could imagine would be to have the server only estimate when the fireball hits and let the client calculate and show the movement of the fireball. But it feels like this would lead to many more problems and overcomplicate things.

If it wasn’t clear enough what I am trying to achieve, I could also make a short video of it.
Before I continue to make many skills based on my current approach, I would love to hear your opinions on it :smiley:

Hello!
Using RunService in this case is inefficient, since if there is a lot of people casting a fireball this will create a big lag for the server. You can try to use RemoteEvents to cast a fireball through the server.
Here is an example how you can make it:

When a player casts a fireball, they send a RemoteEvent to the server with the target’s position.
On the server, create a new fireball object and store its target position.
In a loop running on the server (such as a while loop), continuously update the fireball’s position and rotation towards the target position. You can use a constant speed for the fireball’s movement.
Every time the fireball’s position is updated, send a RemoteEvent to all clients with the updated position and rotation.
On the client, listen for the RemoteEvent and update the fireball’s position and rotation accordingly

Using RemoteEvents should prevent perfomance issues and is still going to work very well with lots of players.

1 Like

Thanks for your answer :smiley: I think I don’t completely understand you though.

  1. I don’t understand the purpose of the RemoteEvent to update the position and rotation.
    The position and rotation calculated on the server would replicate to the clients anyways, right?

  2. As far as my understanding of RunService goes using a loop instead of .Stepped could reduce the amount of times the position and rotation get calculated which would reduce the load on the server. But isn’t this also going to make the movement of the fireball jittery?

This simple code shouldn’t cause lag, but there are some improvements you could make:

1 * value is really just value, so there is no reason in having it be multiplied by one.

  1. direction / direction.Magnitude is actually making the vector be 1 stud long, and Roblox has made an optimization for this, so you can use direction.Unit instead.

  2. CFrame.new(Eye, Target) is deprecated, you should use CFrame.lookAt instead.

  3. The fireball can end up moving slower when the server slows down slightly due to load. While this shouldn’t be a problem most of the time, it’s good practice to make use of the parameter that all RunService events give your code, called the “step” or “delta time”. You use it by multiplying it with the vector you use to move the fireball, along with the speed of the fireball:

local FireballSpeed = 60

RunService.Stepped:Connect(function(DeltaTime)
	local MoveVector = target.Position - fireball.Position
	
	if MoveVector.Magnitude > 10 then -- stop when in hit-range
		fireBall.Position += MoveVector.Unit * FireballSpeed * DeltaTime
	end
	
	fireBall.CFrame = CFrame.new(fireBall.Position, target.Position)
end)
  1. While one fireball isn’t going to be too bad, if you have multiple fireballs they will all be using different .Stepped events. Ideally, you should have one .Stepped event, and have a list of all fireballs and move them all using a for loop.
1 Like

For anything visual, its good practice to change it on the client. The server should be calculating the position of the fireBall, whilst firing remotes to clients. Don’t fire the remote every frame, fire it once every 10 Steps or so, and interpolate the part on the client to create visual smoothness.

Note that the server would replicate the position to all clients, but due to ping it will never be fluid at all times

1 Like

Thank you :smiley:

True, this is still from playing around with the speed :sweat_smile:

Thanks for clearing this up. I was looking for something to normalize the vector and “lookAt” I only tried to find with “:”, not with".".

Just so I get this straight. You mean to loop over all the fireballs in one .Stepped and update the position for each as I did before, right?

Yes, having less events is better for code like this.

1 Like

Thanks :smiley:
So I wouldn’t even create a part on/for the server, but a simple Vector3 or CFrame.
I would then update it in .Stepped (instead of while or for to have access to DeltaTime, like Judgy_Oreo said) and count the steps to maybe 10 and send a remote to the clients which then tween from the last position to the new position.

I’ll have to figure out how to let the clients know which position belongs to which fireball, but it sounds a lot better than what I initally had planned.