1
\$\begingroup\$

I don't know how to name what I am searching for... Force, Impact?
It's easier to explain with an image for me.
We have two GameObjects with colliders, A and B. They can move in any direction (top down game with 360° movement possibilities).
First case (left): A go to the right at a speed of 2, B to the left at a speed of 1 and they collide. How to calculate that A take 1 damage (from the speed of B in its direction) and B take 2.
Second case: A go to the right at speed of 2 and B to the right at speed of 1. A take 0 damage and B take 1.

enter image description here

I am using Collision2D which doesn't have "impulse" property. I have no idea how to calculate thing like this manually.

\$\endgroup\$
2
\$\begingroup\$

This is commonly done with collision.relativeVelocity, something like this:

void OnCollisionEnter(Collision) {
    Vector3 impactVelocity = collision.relativeVelocity;

    // Subtracting a minimum threshold can avoid tiny scratches at negligible speeds.
    float magnitude = Mathf.Max(0f, impactVelocity.magnitude - minimumDamageThreshold);
    // Using sqrMagnitude can feel good here,
    // making light taps less damaging and high-speed strikes devastating.

    float damage = magnitude * collisionDamageScale;

    healthComponent.TakeDamage(damage);
}

But this version will apply symetrically to both participants. (From the view of either A or B, the other object is approacing just as fast, so we should take the same damage)

If you want an object to take damage only when it hits something that's not moving away (at a slower speed), then we'll need to reconstruct the absolute velocities the objects had in the world's inertial frame just before this collision (the velocities you see in the collision handler are post-resolution).

Note that this isn't how real-world physics works, where there's no privileged inertial frame, but sometimes breaking the rules makes for more intuitive behaviour for game purposes.

// Cache this in Start()
Rigidbody _body;

void OnCollisionEnter(Collision collision) {

    Vector3 normal = collision.GetContact(0).normal;
    Vector3 impulse = collision.impulse;

    // Both bodies see the same impulse. Flip it for one of the bodies.
    if (Vector3.Dot(normal, impulse) < 0f)
        impulse *= -1f;

    Vector3 myIncidentVelocity = _body.velocity - impulse / _body.mass;

    Vector3 otherIncidentVelocity = Vector3.zero;
    var otherBody = collision.rigidbody;
    if(otherBody != null) {
        otherIncidentVelocity = otherBody.velocity
        if(!otherBody.isKinematic)
            otherIncidentVelocity += impulse / otherBody.mass;
    }

    // Compute how fast each one was moving along the collision normal,
    // Or zero if we were moving against the normal.
    float myApproach = Mathf.Max(0f, Vector3.Dot(myIncidentVelocity, normal));
    float otherApproach = Mathf.Max(0f, Vector3.Dot(otherIncidentVelocity, normal));

    float damage = Mathf.Max(0f, otherApproach - myApproach - minimumDamageThreshold);

    healthComponent.TakeDamage(damage * collisionDamageScale);
}

Note that this particular formula also deals no damage to an object that strikes a stationary target (though the target takes damage)


In 2D, you have to total up the impulse to use yourself:

static Vector2 ComputeTotalImpulse(Collision2D collision) {
    Vector2 impulse = Vector2.zero;

    int contactCount = collision.contactCount;
    for(int i = 0; i < contactCount; i++) {
        var contact = collision.GetContact(0);
        impulse += contact.normal * contact.normalImpulse;
        impulse.x += contact.tangentImpulse * contact.normal.y;
        impulse.y -= contact.tangentImpulse * contact.normal.x;
    }

    return impulse;
}

One benefit of this is that each object in the collision sees its own version of the impulse, so you don't need the flipping line I included in the 3D version.

\$\endgroup\$
  • \$\begingroup\$ Thanks for the answer, I miss the "impulse" property because I use Collision2D. But for the rest I think I understood the calculation \$\endgroup\$ – Borhink Aug 17 '19 at 16:09
  • \$\begingroup\$ That would be an important detail to include in your question. \$\endgroup\$ – DMGregory Aug 17 '19 at 16:24
  • \$\begingroup\$ Sorry, I thought it wouldn't have changed much if there were 2 or more axes... I didn't know there wasn't a "impulse" in 2D. I'm searching on google how to calculate "impulse" \$\endgroup\$ – Borhink Aug 17 '19 at 16:32
  • \$\begingroup\$ Thank you for the edit, it work now \$\endgroup\$ – Borhink Aug 17 '19 at 19:34

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.