I'm making a 2D game with pads and balls, sort of like Pong, in Unity 4.6.1. The calculations, however, won't be (very) Unity-specific. The pads are going to appear in various rotations, and therefore the physics for bouncing the balls off the pads, have to be vector-based.
EDIT: The solution to the proposed problems is posted at the bottom!
- Depending on where the ball hits the paddle, it should bounce off in an angle of between -90 and 90 degrees, where the middle of the pad would give 0 degrees, and the extremities on each side should give -90 or 90 respectively. (The numbers are just for reference.)
- The current direction of the ball, should be taken into account, so the resulting bounce vector, will be a product of the normalized velocity vector of the ball, and the calculated and normalized directional vector from the pad-bounce (see "Point 1"). (NOTE: The speed of the ball after the bounce is handled later!)
On the image below, the red arrow is the velocity vector of the ball. The effect of "Point 1" is the blue arrow, and the end-result implementing the functionality of both "Point 1" and "Point 2", is the green arrow.
- The pads can change in size. I always have the following variables available: current width in units, standard width in units, and current scale as a float.
- The ball has a circle collider, which is not a trigger.
- The pads have a manually fitted polygon-collider, matching the bend on it. This is a trigger.
Using the code below, I almost have it going, but not quite. It's not working consistently.
Mind the big edit below! "Point 2" has been solved.
"Point 1" isn't being computed correctly, mostly because I'm having trouble finding a calculation that's precise. When I created my first Pong game, it was easy, because I could just see the difference in ball.y and pad.y and hold that against the width of the pad. I can't for the life of me figure out how to do that using vectors.
"Point 2" is giving me trouble, because I can't figure out how to calculate the normal vector for the pad. The calculations noted in this other answer and here say, that I need to get this normal correct to get the right bounce.
This code is in "OnTriggerEnter2D(Collider2D col)" on the Pad:
GameObject ball = col.gameObject; BallScript ballScript = col.gameObject.GetComponent<BallScript>(); // We know the ball has hit the pad, so we raycast to see where it hits. RaycastHit2D hit = Physics2D.Raycast( new Vector2(ball.transform.position.x, ball.transform.position.y), ball.rigidbody2D.velocity); // Get the normal for the hit. I think this is also not going to work for my purposes, // since my pad isn't square) Vector2 n = hit.normal; n.Normalize(); // this has to be normalized // Get the velocity vector of the ball Vector2 v = ball.rigidbody2D.velocity; v.Normalize(); // I normalize v, because I want to add speed manually later float dotOfvn = Vector2.Dot(v, n); // Always reports -1f or 0.9998f, most likely because n is the same as v // (or the exact opposite, I'm not sure) Debug.Log("Dot: " + dotOfvn); // Apply the function... Vector2 R = -2 * dotOfvn * n + v; // Without the following, the ball always returns in the same direction it came from. // With the following, I almost have it all functioning, except when a ball comes // flying as in image 2 (below). R += (Vector2)((ballScript.transform.position - _transform.position) / currentPadScale).normalized; R.Normalize(); ball.rigidbody2D.velocity = R * ballScript.speed;
Result of the current code is, that everything seems to almost work, but this image shows a situation that happens often, which proves it doesn't. Red arrow is the velocity vector of the ball, and the blue is where the pad sends it upon being hit.
As the code-comments state, I'm not sure which parts I'm doing correctly, but I'm sure it is solvable. I would very much appreciate a helping hand.
I've solved part of my problem. Namely "Point 2". EDIT: For the full solution, scroll down further!
Here's the code:
GameObject ball = col.gameObject; BallScript ballScript = col.gameObject.GetComponent<BallScript>(); Vector2 n = _transform.up; Vector2 v = ball.rigidbody2D.velocity; v.Normalize(); // I normalize v, because I want to add speed manually later float dotOfvn = Vector2.Dot(v, n); Vector2 R = -2 * dotOfvn * n + v; R.Normalize(); ball.rigidbody2D.velocity = R * ballScript.speed;
This bounces the ball from the pad, as if the pad was a wall, and it works at any angle. So far so good. The missing piece I was looking for, was that _transform.up is essentially the normal vector for my pad. It might be _transform.right or an inversion of either, depending on how the sprite is positioned at import. Mine is positioned like so: <=====>
Setting n = _transform.up and v to ball.rigidbody2D.velocity is redundant, but is in this example for clarity, so the function retains its variable-names, v and n.
Now, all I need to do, is solve "Point 1". I can't just use the length between the position of the ball and the position of the pad. It will not be precise enough.
It's a right triangle, and I know one side (position of the ball to the position of the pad). I need to know that length, and I need to figure out how I can know which side of the middle the ball struck. Otherwise, I won't know in which direction to apply additional force, left or right.
Rest assured, I will post the complete solution when I find it, so that others can use it!
I've kept some extra variables, to make it easier to see how they fit into the mathematical functions. Optimize it yourself :) Don't make local variables if you can help it!
This code is placed inside my script on the pad itself, in its OnTriggerEnter2D(Collider2D col). The ball has a rigidbody2d which is NOT a trigger, but the pad is a trigger.
// The ball colliding with my pad (I've checked its tag before getting here!) GameObject ball = col.gameObject; // Holds the ever-increasing speed of my ball, which is to be updated // for each pad-bounce. BallScript ballScript = col.gameObject.GetComponent<BallScript>(); // The formula for a standard wall-bounce is: // -2*(V dot N)*N + V // where V is the incoming velocity of the ball, and N is the normal-vector // for the wall/pad i.e. upwards/outwards from the pad, when the long side // of it is left-to-right, and top and bottom are up and down respectively. // Because my pad-sprite is like this <======>, I use _transform.up. // For other implementations using pad-sprites that are turned differently, // _transform.right or an inversion of either might be used. Vector2 v = ball.rigidbody2D.velocity; // I normalize v, because I want to make a change in speed later (below). // Not normalizing can also introduce weird behavior in the functions, so to be safe, // you can use your v.magnitude, which is the length/speed, and apply it afterwards. // More on that later. v.Normalize(); // We need the dot-product of v and n for the function. float dotOfvn = Vector2.Dot(v, (Vector2)_transform.up); // A clean 0,0 vector2. I want to make it so the players can choose which type // of bounce they want, and even mix them, so I've split up the two ("Point 1" // and "Point 2"), so I can add their effects separately. Vector2 R = new Vector2(); // I add the effect of the wall-bounce to my R. // If you ONLY want a wall-bounce, this is the function you want, and in that case, // you can skip the normalization of v above, so you don't need to manually apply // v.magnitude afterwards. But if you want to continue, I advise you to follow the // code as written. R += -2 * dotOfvn * (Vector2)_transform.up + v; // Now R represents the realistic bounce-vector, that the ball would have if it // had struck a flat wall. // Now, on to the Pong-style bounce! // We want to also be able to take into account the Pong/Arkanoid-style of bouncing // from the pad, where the ball shoots in a different direction, depending on where // it hits the pad. The further from the middle, the sharper the exit-angle. // That means we have to be able to interpolate between the normal-vector for the // pad (upwards), and the vector going along the pad (sideways). // The function for getting a percentage-interpolated vector between two vectors is: // A*t + (1-t)*B // where t is the percentage you want to be close to A compared to how close you want // to be to B. // To be able to use this, we need to know how far from the middle the ball struck. // We want to find the length from where the ball hit, to the middle of the pad, // but not directly from the position of the ball, but from where it hit, and it // has to be from a point on the pad's sideways normal-vector, to be precise. // (See the last image of this post for clarification) // We know the ball has hit the pad, so we raycast to see where it hits. // You have to make sure that your raycast can only hit the pad. Do some layering or // something, so it doesn't hit anything else first. RaycastHit2D hit = Physics2D.Raycast(new Vector2(ball.transform.position.x, ball.transform.position.y), ball.rigidbody2D.velocity); // We find the vector from the hit-point to the position of the pad. Vector2 vectorFromHitPointToPadCenter = hit.point - (Vector2)_transform.position; // Then we can use the Dot-procuct of that vector and our pad's sideways vector, // to determine the length we wanted (see the last image in this post). float length = Vector2.Dot(vectorFromHitPointToPadCenter, _transform.right); // Now, a percentage as a float, where 1.0f is 100% and -1.0f is -100%. // This tells us how far from the middle of the pad the ball has hit in %. // If it is negative, then it has hit the left side of the pad. // E.g. if it is -1.0f, the ball has hit the far left extremity of the pad. // You'll have to figure out your "halfCurrentPadWidth" variable yourself. // It should be half the width of the pad in units. var percentageOfLengthVSHalfPadLength = length / halfCurrentPadWidth; // We apply A*t + (1-t)*B // Again I add this to my existing vector. If you ONLY want this standard Pong- // bounce, just use this, and forget the wall-bounce math above, and change // the += to just =. R += (Vector2)_transform.right * percentageOfLengthVSHalfPadLength + (1 - percentageOfLengthVSHalfPadLength) * (Vector2)_transform.up; // Gotta normalize. R.Normalize(); // Apply speed to the ball. If you just want the ball to continue at its present // speed, you can replace ballScript.speed with ball.rigidbody2D.velocity.magnitude. ball.rigidbody2D.velocity = R * ballScript.speed;