Blade Ball Ball Physics

If you want the real answer, they most likely use a client-sided and server-sided simulation of the ball. The server side handles the physics and collision detection, while the client-sided simulation is for visuals. I did something similar in my tennis game. Essentially, whenever the server ball is created, they fire a remote to the client to replicate the simulation in the exact same way.

To combat lag and unexpected server behavior, the client double checks the location of the server ball, and if it’s too far, they update the location of the client ball to sync it up.

14 Likes

Same issue as what I just said

2 Likes

There is stuttering with body movers and physics constraints. That’s the issue they are trying to solve

2 Likes

I understand the LinearVelocity but for what would they be using the VectorForce?

2 Likes

To give the ball anti-gravity properties.

3 Likes

True but how does blade ball replicate the ball to the client with no issues and how do they do it with curving the ball…

2 Likes

thats the whole point of this discussion :sob:

3 Likes

Any hints on how you, blade ball do it? :sweat_smile:

2 Likes

move the ball on the client with tweening or bodymovers and calculate if the ball touched the player on the server :100:

2 Likes

AlignPosition or something similar that directly changes the displacement, because it just makes more sense. Both LinearVelocity and VectorForce controls the ball from a higher derivative which is way harder to configure and manage; you can expect them to involve calculus and higher degree polynomials. So many things can go wrong if you used those two, such as accidentally making the ball enter a stable orbit around a player.

It is just simplier to use AlignPosition and I doubt the devs would torture themselves for the sake of realism. Compare these two; the first one uses AlignPosition and the other one uses LinearVelocity via proportional navigation:

alignPos.Position = target.Position
local V_r: Vector3 = target.AssemblyLinearVelocity - self.AssemblyLinearVelocity
local R: Vector3 = target.Position - self.Position
local Omega: Vector3 = (R:Cross(V_r))/(R.X*R.X + R.Y*R.Y + R.Z*R.Z)	
local normalAcceleration: Vector3 = (proportionality*V_r):Cross(Omega)
	
linearVel.VectorVelocity = (self.AssemblyLinearVelocity + normalAcceleration*dt).Unit * missileSpeed
7 Likes

What if the movement is fake and it’s just a serverside timer with some client movement? It’s possible, but some symptoms of the movement during lag make me believe otherwise.

2 Likes

This is not the issue, i have that figured out, it just the fact that body movers are very buggy, and are delayed + networking issues

2 Likes

For educational purposes:

They most likely use a bezier curve function, and set the ball’s CFrame each frame, synced on all clients. And most likely, calculation of points that “curve” the function is based on Linear and Angular velocity.

Or maybe they use some bodymover, but as you know, Roblox physics are unreliable.

4 Likes

Smoothest way as to how you would do this would be to set the network ownership of the ball to whomever the target may be, this could create desync, and lack security if you don’t add proper sanity checks.

Layered on top of the network ownership you could do any of the aforementioned methods. But I imagine @FP_Nation is the correct approach, especially looking at particular skills, seems like it’s a bezier curve or something alike which is updated each frame across all clients.

If you know the ball is at position X, and you need it to go to position Y within 4 seconds, you can normalize the step in which the ball is at and update the position of it across all clients. (ie; 0 = 0 seconds, 0.5 = 2 seconds, 1 = 4 seconds etc…)

2 Likes

Wouldn’t changing the network ownership make the ball appear laggy / desynced across clients?

2 Likes

Hence why you would want to layer additional effects, like the one I mentioned (bezier curve), on every client.

The network ownership would be purely for the person the ball is going towards, which is the most important for timings, etc…

2 Likes

uhhhhhhhhhhhhhhhhhhhhhhhhhhhhh ball

2 Likes

I think the devs might be using float curves. This is just a theory and im not pretty sure bout it.

this is a video that i saw, maybe the devs like make 3 points, the 1st point infront of the player who deflected the ball, 2nd point in middle of the deflected player and the target and the 3rd point infront of the targeted player. JUST A THEORY I MIGHT BE WRONG SO DONT JUDGE ME (didnt watch the video completly btw its too complex for my monkey brain)

3 Likes

The devs stated they dont use tweens, and these use tweens

1 Like

The dev probably meant that they don’t use TweenService, not that they don’t use interpolation at all. They could still calculate a point on a curve every frame and set the CFrame of the ball based on that. In the video posted by @fasmandom , TweenService:GetValue() was used for calculating a time number for calculating the point on the curve. TweenService is not needed for that, and it’s probably useless if the developers want to have precise control of the speed of the ball over time.

I don’t know whether the developers of Blade Ball calculate points on a parametric curve, and if they do, what kind of curve it is. However, after seeing this post I thought it’d be interesting to try making something similar (a ball following a character) using quadratic bezier curves (which are parametric curves).

Calculating curve control points
The function that creates the ball object (which is not an instance but can contain references to instances used for visualization) has parameters for the initial position and movement direction of the ball, and for the initial position of the target. It calculates an initial curve using these.

When using parametric curves for following a moving target, it is necessary to update the curve when the target moves. Thus, every frame, my code computes a new curve and then moves the ball along the new curve. P0 (start point) of the new curve is the position calculated for the ball on last frame. It’s tangent (derivative) direction at P0 is the same as the tangent direction of the last frame’s curve at the t value (curve parameter value between 0 and 1) of the ball position calculated last frame. P2 of the new curve is the new target position. P1 of the new curve is calculated by picking a point on the tangent line of P0 such that the point is in the derivative direction from P0 (this ensures G1 continuity i.e. that there is no sudden change of tangent direction at the point where two consecutive curves meet) and the lengths of P1 - P0 and P2 - P1 have a spesific ratio calculated in an arbitrary way that I thought would give good results but wasn’t actually as good as I thought. Anyways, the way I calculate P1 ensures that the curve will stay the same until the target moves (without explicitly checking whether it moves).

I’m not entirely happy with the results I get with the way I calculate the control point P1 of the curve. The direction changes sometimes feel too fast and it’s also possible for the path to go inside the ground. P1 could be calculated in many different ways as long as it is in the correct direction from P0. Also, calculating just a single quadratic bezier curve is limiting. Forming the curve calculated on a spesific frame from two quadratic curves or using a cubic curve would give more freedom for improving the curve shape but it can be difficult to decide a good way to use this freedom (how should the distances and directions between control points be calculated since there are so many possible ways?).

Calculating the t value for the new position
Every frame, a new ball position is calculated. For this, it is necessary to find a t value that corresponds to a sensible point on the curve (a sensible arc length from the last position of the ball). The arc length that the part should move along the curve should depend on its desired velocity and the time between frames. So, after calculating how much the part should move (a scalar), we need to find the t value that corresponds to this amount of movement (arc length between t = 0 and this t value is the amount of movement).

It might feel logical to just calculate the t value as t = (amount of movement) / (curve length) and plug this into the bezier curve formula. However, there’s a problem. Normally, when calculating points along a bezier curve with constant difference between consecutive t values (for example t0 = 0, t1 = 0.1, t2 = 0.2, t3 = 0.3 etc.), the points are denser near the middle of the curve (the arc length between consecutive points is not constant). So when moving something along the curve by linearly changing t, it will first move quickly, then slow down near the middle of the length of the curve, and finally speed up again when approaching the other end point of the curve.

Arc length parameterization
When we want to control the speed at which something moves along the curve, arc length parameterization is needed. We need a function that, given a t value that represents the arc length between p0 and the desired point, calculates the regular t value that corresponds to this arc length t. With such a function, if we wanted to calculate such a point P that arc length between P0 and P is a quarter (0.25) of the length of the curve, we can just give 0.25 to the aforementioned function and plug the returned t value to the bezier curve point formula to get P.

The way I went about implementing arc length parameterization was by using the instantaneous speed of change of the point on the curve with respect to t. This speed is the magnitude of the derivative vector. First, I calculate points on the curve normally without arc length parameterization with constant t differences (for example the aforementioned 0, 0.1, 0.2, 0.3 etc.). As mentioned before, the arc length between such points is not constant. For each pair of consecutive such points, I create a QuadraticBezierInterval object that stores these points, their t values and the euclidean (straight/shortest) distance between them. The sum of these euclidean distances is set as curve.length. The length calculated this way is always an underestimation because the actual arc length between two points is greater than the straight (shortest) distance between them but with enough points its close to the correct length. When calculating the regular t value corresponding to an arc length t value, I first calculate the approximate desired arc length between t = 0 and this regular t by multiplying curve.length with the given arc length t. After that I sum BezierInterval euclidean distances until I find the interval in which the desired t value is by checking when the sum exceeds the desired arc length. Then I calculate the aproximate arc length from interval.point0 to the desired point.

local approximateArcLengthFromIntervalPoint0 = approximateArcLengthBeforeT - sumOfLengthsOfWholeIntervalsBeforeT

I derived the way I calculate the regular t value from this arc length from the equation for travelled distance s in uniformly accelerating motion. s in this case is approximateArcLengthFromIntervalPoint0 . The scalar acceleration (rate of change of speed) is not actually constant in the case of a bezier curve but this still seems to work pretty well. Here’s the equation.

s = v0 * Δt + 1/2 * a * Δt^2

v0 in the equation is the speed at interval.t0 and Δt = interval.t1 - interval.t0. The average acceleration is (v - v0) / Δt where v is the speed at interval.t1. By substituting this into the equation, we get the following:

s = v0 * Δt + 1/2 * (v - v0) / Δt * Δt^2
-- after simplifying:
s = 1/2 * (v0 + v) * Δt

Then we can easily solve Δt from the equation and calculate the approximate desired regular t value.

Δt = 2*s / (v0 + v)

-- this is the t value that the function returns
t = interval.t0 + Δt = interval.t0 + 2*s / (v0 + v)

Now, finding a point and a derivative vector with an arc length t value can be done in the following way.

local regularT = self:getNormalTFromArcLengthParameterizationT(arcLengthT)
local arcLengthParameterizationPoint = self:getPointNormally(regularT)
local arcLengthParameterizationDerivativeVector = self:getTangentVectorNormally(regularT)

The way I calculate the arc length that the ball should move is based on the same equation, but this time I’ll just use the equation s = 1/2 * (v0 + v) * Δt instead of solving Δt from it, and in this case, Δt is time between position updates, v0 is the ball speed at the moment of the earlier position update and v is the ball speed at the moment of the new position update. After that, arc length t is calculated using the aforementioned formula t = (amount of movement) / (curve length).

local curveArclengthParamaterizationT = amountToMove / newCurve.length

Other
The instances are meant to be created on the clients. They are only used for visualization.

The movement should be smooth because the client calculates the ball position every frame instead of it being sent from the server which means there won’t be sudden teleporting. However, the paths may be more or less different (depending on internet connection) between clients which can cause the distance from ball to target along the curve to be different between clients. Perhaps the desync could be decreased by sending the distance from ball to the target along the server curve to the clients and having the clients adjust the speed of the ball based on how much closer or further that client’s ball is from the target than it should be. I’m not sure how exactly this speed adjusting should work, though.

27 Likes