0
\$\begingroup\$

I am just learning the basics of physics simulation with rigidbodies. I am trying to simulate some basic real world functions like kicking a ball.

Let's say I have a simple capsule mesh/collider as my player and a sphere as a ball. I want when the player comes close enough to the sphere for them to be able to "kick" the ball, exerting a velocity on the ball along the vector from the player's front face towards the ball.

I am new to all the angle systems of rigidbodies so I'm having a bit of difficulty.

The best I could come up with was to add this code to the control of the player capsule:

        if (Input.GetKeyDown(KeyCode.B)) {
            Collider[] hitColliders = Physics.OverlapSphere(playerGO.transform.position, 1f);
            foreach (var hitCollider in hitColliders) {
                if (hitCollider.gameObject.GetComponent<Rigidbody>()) {
                    //if (objectWithinFrontRangeOfPlayer) { // use playerGO.transform.forward
                        Vector3 positionDiff = hitCollider.transform.position - playerGO.transform.position;
                        Quaternion angleOfImpact = Quaternion.LookRotation(positionDiff);
                        hitCollider.GetComponent<Rigidbody>().velocity = new Vector3(angleOfImpact.x, angleOfImpact.y, angleOfImpact.z) * 30f;
                    //}
                    
                }
            }
        }

So the steps I see are:

  1. Upon player pressing "kick" button (B), check for any colliders in the area, then check for which have rigidbodies.
  2. If rigidbody is found within kicking range, I should ideally then check if it is within a certain angle tolerance like +/- 60 degrees of the playerGO.transform.forward direction (one can usually only kick effectively things in front of how you are facing). I am not sure how to do this.
  3. I then need to calculate the angle/vector at which the velocity should be applied to the sphere. I tried using Quaternion.LookRotation(hitCollider.transform.position - playerGO.transform.position); but then I don't know what to do with the Quaternion. If I had it as a normalized unit vector I could do what's written above and multiply the intended velocity from the kick to each axis. But as a Quaternion, while what I wrote above certainly "kicks" the back, the vector is not correct.

Any tips on how to fix this code and make it work roughly correctly?

Thanks a bunch.

\$\endgroup\$

1 Answer 1

1
\$\begingroup\$
Quaternion angleOfImpact = Quaternion.LookRotation(positionDiff);
hitCollider.GetComponent<Rigidbody>().velocity = new Vector3(angleOfImpact.x, angleOfImpact.y, angleOfImpact.z) * 30f;

A few things wrong with this:

  • A quaternion is not an angle, nor a collection of angles like an Euler triplet. Don't name a Quaternion variable "angle" or you will confuse anyone trying to read your code, including yourself.

  • The x, y, z components of a quaternion form a vector parallel to the axis the object needs to rotate around to reach this orientation, not a vector in the direction that the resulting orientation faces. So if you tried to kick the ball to the right with this code (along (1, 0, 0)), the quaternion's x, y, z would point along the world up vector (0, 1, 0) (because I have to yaw around the y axis to look right) - 90 degrees apart from what you want!

  • This axis vector gets smaller as the angle gets smaller. So if you tried to kick the ball straight ahead with this code (along (0, 0, 1)), the quaternion you'd get would be (0, 0, 0, 1) - so you would set the velocity to zero!

As a rule: just don't touch the individual x, y, and z components of a quaternion. Unless you're very deliberately manipulating 4-dimensional imaginary numbers, it's almost guaranteed that you are doing the wrong thing.

There is no reason to bring quaternions into this at all. A rigidbody's velocity is just a cartesian vector in world space, nothing angular/rotation-y about it. So if you want to kick the ball along the line joining the player to the ball, then just use that line:

Vector3 positionDiff = hitCollider.transform.position - playerGO.transform.position;
// Flatten the offset to the horizontal xz plane.
positionDiff.y = 0;

// Check the angle relative to our facing direction, 
// and abort if it's too far away.    
if (Vector3.Angle(positionOffset, playerGO.transform.forward) > kickDegreeRange)
    continue;

// Here you might want to add some y back to the positionOffset
// so that you get a velocity that points slightly upward.

// Keep the direction of the offset, discard the length (make it a unit vector).
Vector3 kickDirection = poisitionDiff.normalized;

// Set the velocity to point in this direction, with a fixed speed.
hitCollider.GetComponent<Rigidbody>().velocity = kickDirection * kickSpeed;

The same notes I left on your previous question about not hard-coding keys for game actions apply here too. Prefer something like Input.GetButtonDown("Kick") or kickAction.WasPerformedThisFrame(), so that your code isn't coupled to just one fixed control scheme.

\$\endgroup\$
6
  • \$\begingroup\$ That's absolutely perfect. Thanks for the good summary. I realized you could not use Quaternions as I did but I did not understand how they worked or the correct method to do this. Thanks for explaining both. My app is for mobile so everything will eventually be controlled by custom UI Element touch buttons. So I will likely avoid the Unity Input Manager. I wasn't aware of Input Events. I have been using actions like public event Action runButtonFunction in my custom buttons class. I'm not sure if there is any extra utility in InputEvent. If you have any thoughts I'd be curious to listen. \$\endgroup\$
    – mike
    Commented Mar 26, 2022 at 20:05
  • \$\begingroup\$ One simple follow up: How would you go about angling up the kick, say by 10-20 degrees? The kickDirection would need to be rotated on the x/z axes to allow this. Is kickDirection = Quaternion.Euler(20, 0, 20) * kickDirection; logically or mathematically sound? \$\endgroup\$
    – mike
    Commented Mar 26, 2022 at 20:27
  • \$\begingroup\$ Completely unsound. This says "Rotate the vector 20 degrees around the world Z axis, then rotate the result 20 degrees around the world X axis". So if your direction was left (-1, 0, 0) then you end up tilting it downward by 20 degrees, then backward by another 20 degrees. What you could do instead is kickDirection = kickDirection * Mathf.Cos(angle) + Vector3.Up * Mathf.Sin(angle) \$\endgroup\$
    – DMGregory
    Commented Mar 26, 2022 at 20:31
  • \$\begingroup\$ Okay. lol at me. :) Yes. Pythagorean math. I spent the past 30 min drawing triangles while trying to re-derive your equation for educational purposes but failed. I am not good enough at this math. I know sin(theta) = opp/hyp, cos(theta) = adj/hyp, tan(theta) = opp/adj and I can see you are adding a small amount to the vertical while subtracting a bit from the horizontal. If you're willing to share a bit on the basic math for how that works or was derived to maintain the same vector length like that I would be curious. Otherwise no worries. Thanks for the help and instruction either way. \$\endgroup\$
    – mike
    Commented Mar 26, 2022 at 21:08
  • \$\begingroup\$ It's just the unit circle. kickDirection is in the horizontal plane, so it's the "adjacent" side of our triangle. Vector3.up is perpendicular to this plane, so it's the "opposite" side of our triangle. So to get a hypotenuse of length 1, elevated above the horizontal by angle, we need the length of the adjacent side to be the cosine of angle, and the length of the opposite side to be the sine of angle. \$\endgroup\$
    – DMGregory
    Commented Mar 26, 2022 at 21:20

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .