Soft Velocity Tweak, preserve Magnitude, avoid all deadlocks

Hi all, here are my aproach to solve all deadlock.

First of all, I identify two deadlock conditions: zero speed on some axis, or changing speed on axis but a number of consecutive rebounds on unbreakable elements.

To solve this, the first step is to apply the tag “Unbreakable” in the 3 walls and in the paddle.

Then I add some attributes to the ball. One is to count how many consecutive bounces on unbreakable elements I accept, then a speed correction for those cases, and finally a speed correction for cases where an axis is “dead”.

[SerializeField] float maxUnbreakableCollisions = 15;
[SerializeField] float unbreakableDLRandom = 1f;
[SerializeField] float axisDLSpeedAdjust = 5;

Then, I accumulate the number of hits on unbreakable objects in a variable of type int:

 //state variables
Vector2 paddleToBallVector;
bool hasStarted;
int deadCollisionsCount;

private void OnCollisionEnter2D(Collision2D collision)
{
	float tweatPercentaje = UnityEngine.Random.Range(-0.2f,0.2f);

	
	if (hasStarted)
	{
		AudioClip clip = ballSounds[UnityEngine.Random.Range(0, ballSounds.Length)];
		myAudioSource.PlayOneShot(clip);

		if (collision.gameObject.tag == "Unbreakable")
		{
			deadCollisionsCount++;
		}
		else
		{
			deadCollisionsCount = 0;
		}

		AvoidDeadLock();   
	}
}

Finally, my strategy is to avoid locks, in a “smooth” way.

What is done is the following:

  1. Obtain the magnitude of the velocity (this is the absolute value of the velocity vector, since this value I want it to remain invariant throughout the game

  2. If the speed of one axis is less than an acceptable threshold, I correct the speed of that axis, and calculate the speed of the other axis so that the speed magnitude is maintained. Since that axis has zero speed, it won’t look unnatural if the ball bounces to one side or the other, sso the factor can be a +/- 5.0f correction, or something like that.

  3. If I am in a condition of “many rebounds without breaking something” what I do is to “subtly” randomize the axis with the lower speed and, of course, preserve the total absolute value, adjusting the predominant axis.

private void AvoidDeadLock()
{
	float xVel = myRigidBody2D.velocity.x;
	float yVel = myRigidBody2D.velocity.y;
	float initialMagnitude = myRigidBody2D.velocity.magnitude;
	
	if (Mathf.Abs(xVel) <= 0.5f)
	{
		xVel = Random.Range(-axisDLSpeedAdjust, axisDLSpeedAdjust);
		yVel = Mathf.Sign(yVel) * Mathf.Sqrt(Mathf.Pow(initialMagnitude,2)-Mathf.Pow(xVel,2));
		Debug.Log("dead x");
	}
	else if (Mathf.Abs(yVel) <= 0.5f)
	{
		yVel = Random.Range(-axisDLSpeedAdjust, axisDLSpeedAdjust);
		xVel = Mathf.Sign(xVel) * Mathf.Sqrt(Mathf.Pow(initialMagnitude,2)-Mathf.Pow(yVel,2));
		Debug.Log("dead y");
	}
	else if (deadCollisionsCount >= maxUnbreakableCollisions)
	{
		Debug.Log("dead lock");
		
		if (Mathf.Abs(xVel) > Mathf.Abs(yVel))
		{
			yVel = RandomizeAxisSpeed(yVel, initialMagnitude);
			xVel = Mathf.Sign(xVel) * Mathf.Sqrt(Mathf.Pow(initialMagnitude, 2) - Mathf.Pow(yVel, 2));
		}
		else
		{
			xVel = RandomizeAxisSpeed(xVel, initialMagnitude);
			yVel = Mathf.Sign(yVel) * Mathf.Sqrt(Mathf.Pow(initialMagnitude, 2) - Mathf.Pow(xVel, 2));
		}
		deadCollisionsCount = 0;
	}

	myRigidBody2D.velocity = new Vector2(xVel,yVel);
}

IMPORTANT: The method to randomize the speed on the axis that least affects the movement of the ball, takes into account the sign of that speed and preserves it. This is done so that the ball does not make unnatural bounces, which completely alter the direction it was leading before bouncing.

private float RandomizeAxisSpeed(float vel, float initialMagnitude)
{
	Debug.Log(vel);
	if (vel < 0)
	{
		
		vel = Mathf.Clamp(vel + Random.Range(-unbreakableDLRandom, 0), -initialMagnitude, 0);
	}
	else
	{
		vel = Mathf.Clamp(vel + Random.Range(0, unbreakableDLRandom), 0, initialMagnitude);
	}
	Debug.Log(vel);

	return vel;
}

I have tested this on a blockless level and the result is as expected. The ball never goes into deadlock of any kind. When a certain bounce pattern begins to happen, the ball changes its bounce completely subtly and naturally, and this is invisible to the user.

Game for see this strategy in action: https://sharemygame.com/@guillef/block-breaker-with-music

7 Likes

Nice. I want to use it but I can’t understand it right now. Maybe after repeated readings. Thank you.

This is an excellent solution. Little over-engineered for the application, but it’ll be useful in future I’m sure

Hey I finally understood this! And I used this in my game! It’s exactly what I wanted to do.

Hey Nish, I didn’t get the last Method - “RandomizeAxisSpeed”. It starts as if the “vel” variable has a value attributed to it, but it has just been introduced to the code. Could you explain it to me?

Hi SIlk, I will tell you a bit about the general idea.

RandomizeAxisSpeed() ​​is used only in a type of deadlock, when the ball has some speed (possibly on both axes), but is bouncing a number of consecutive times without breaking anything.

There what we have to do is modify the course of the ball, without altering “too much” in an unnatural way its course. How do we do it ?: We touch a little the speed that least influences movement.

So if xvel> yvel we are going to modify yvel a bit, otherwise we will touch xvel.

if (Mathf.Abs (xVel)> Mathf.Abs (yVel))
{
    yVel = RandomizeAxisSpeed ​​(yVel, initialMagnitude);
...

When we randomly adjust the speed we will do it WITHOUT modifying its sign, in ANY cases, except when tthe speed is 0 or near cero. Example, if the ball is moving from left to right, that is Xvel> 0, we are going to adjust xvel a little but we are going to keep its positive sign. This is done in RandomizeAxisSpeed ​​(). We add a random component between -unbreakableDLRandom and 0, or between 0 and unbreakableDLRandom, depending on whether the speed is negative or positive, with that we make sure not to alter the direction that the ball brought in that axis. The use of the clamp() is for very rare cases where possibly, by adding a random component you can take the speed of an axis to its maximum allowed, which is that this axis represents the full magnitude of the speed, so we limit it. On rare occasions in my tests I noticed that the ball was accelerating, because it was adding magnitude to the movement.

After randomizing a speed ALWAYS calculate the velocity component that was not modified, with this formula:

...
xVel = Mathf.Sign (xVel) * Mathf.Sqrt (Mathf.Pow (initialMagnitude, 2) - Mathf.Pow (yVel, 2));`
...

What that line does is to calculate the value of the other speed component while maintaining the total magnitude invariant. That is, the magnitude of the speed in 2D is calculated as:


mag_vel = SQRT (xvel ^ 2 + yvel ^ 2)

then:

xvel = SQRT(mag_vel^2 - yvel^2)   //(<--- if xvel was negative, this calc always returns positive, we "lost" the original sign of xvel!)
xvel_preserve_sign = sign(xvel) * SQRT(mag_vel^2 - yvel^2)

What we do is, if xvel was modified we calculate yvel from the previous formula. We always have to keep the sign that the speeds had at the beginning, to avoid unnatural bounces, which completely alter the direction of the ball when it bounces, that happens when we abruptly change the sign of the speed on any axis.

I hope the explanation has helped!

Regards!

1 Like

Thanks for the explanation, Guillermo!

Thank you for posting this! It helped a lot!

Privacy & Terms