2
\$\begingroup\$

I am creating a 2D platformer using Unity3D, I want to have the player destroy enemies when it jumps/falls over them (just from the top). But I have been having problems trying to implement this. I stripped everything and used two simple box colliders in order to make it easy. So, when onCollisionEnter2D is called I get the bounds of the objects colliding like this:

    Bounds playerBounds = new Bounds(transform.position, collider2D.bounds);
    Bounds collisionBounds = new Bounds(collision.transform.position, collision.collider.bounds);

Then I check if the object colliding is below by comparing it's bounds:

    //If the player's lowest point is greater than the 
    //colliding object's highest, then it is above it
    bool isAbove = playerBounds.min.y >= collisionBounds.max.y;

Then I check if the object is actually an enemy, and if so, I destroy it:

    if(isAbove)
    {
        if(collision.gameObject.tag == "Enemy")
        {
            collsion.gameObject.getComponent<EnemyController>().die();
        }
    }

Now the thing is this never happens, isAbove is never true, so I tried checking the heights of each object so I use

    Debug.Log("player : "  + playerBounds.min.y + " enemy : " + collisionBounds.max.y)

It says the player bottom-most is below the enemy top-most. Since I use this same method to detect if the player is grounded in order to enable jumping I noticed it works beautifully with other things like platforms but not with enemies. Also y used Debug.break to stop the game and noticed that when colliding with platforms the player's collider was always above the platform's, but when it collided with enemies they overlap: so the player's bottom-most part is below the enemy's top-most part. I read somewhere that this is due to the minPenetrationPenalty property, I didn't like the results of messing with it so I just left it the same (If I lower it doesn't even work with platforms, and I increase it they don't even touch and the collision is detected). I tried to compensate for this penetration when checking the bounds so I said:

    bool isAbove = playerBounds.min.y >= collisionBounds.max.y - 0.01;

It sometimes fails and sometimes not depending on the angle and other stuff, I still don't get it because if this was the problem It shouldn't work with the platforms either but it does, it only fails with enemy. I even tried to check for the overlap and said:

    float overlap = playerBounds.min.y - collisionBounds.max.y;

    bool isAbove = overlap > -0.01;

this still works beautifully with platforms only reporting positive values (which means they are not overlapping), but with enemies it actually reports negative values (which confirms the previous: they do overlap), even more strange it actually reports overlaps greater than what should be allowed, the usually report a -0.09 which means a 0.09 overlap which is greater than the 0.01 that the physics engine is supposed to allow.

If I apply this code in OnCollisionStay2D instead it works, I guess by this time the overlap is fixed, but this looks unnatural and I still don't understand why this works perfectly with platforms (they never even overlap!) and not my enemies.

\$\endgroup\$
2
  • \$\begingroup\$ I haven't used unity, but most likely your terrain is using a different collision type, such as static terrain. Unity is probably finding the correct contact point for these, whereas for two dynamic collision boxes (player, monster) it isn't trying so hard to keep them from overlapping since your likely to implement some sort of damaging mechanic. You would have to compare the player vs the monster with that in mind, for instance playerBounds.min.y >= collisionBounds.max.y*0.8f \$\endgroup\$ Feb 14, 2015 at 11:21
  • \$\begingroup\$ @Carlsson well, the platforms are static objects without rigidbody, and the enemies are dynamic, so I'll try using a greater value as soon as I can \$\endgroup\$ Feb 14, 2015 at 13:46

3 Answers 3

1
\$\begingroup\$

I haven't used unity, but most likely your terrain is using a different collision type, such as static terrain.

Unity is probably finding the correct contact point for these, whereas for two dynamic collision boxes (player, monster) it isn't trying so hard to keep them from overlapping since you're likely to implement some sort of damaging mechanic.

You would have to compare the player vs the monster with that in mind, by for instance adding a scaling factor playerBounds.min.y >= collisionBounds.max.y * 0.8f

A better option might be to calculate the normal between the player and the enemy and then calculate the angle from the up vector, in pseudo code:

normal = (playerBounds.center - collisionBounds.center).normalize();
if (normal.dot(UpVector) < cos(Pi/4))
  killEnemy();

Edit: A normal is simply a vector with a length of 1, to convert any arbitrary vector (except for 0,0,0 which is undefined) to a normal you simply divide it with it's length (x,y,z) / sqrt(x*x + y*y + z*z)

It's useful to us in this case because you can calculate the angle between two unit vectors using the dot product between two vectors, in the code example I gave I check if the angle is less than 45 degrees.

In your game you might want to change this slightly, for an enemy that is as wide as it is tall 45 degrees is good, but for a taller one you would need a smaller angle or it will accept if your characters legs hit the side of the head.

The accuracy of the normal will be more than enough for calculating the angle in this case.

However, since your colliding with boxes it can still happen that you get a detected collision without the characters visually touching, simply by having two corners of the boxes touching while neither head nor feet are near the corners.

You can fix this by making collision boxes for each bodypart and check with these after the initial boxes collide (in which case you can check if the feet of your character is colliding with the head/shoulders of the enemy instead of checking the angle).

Another, much more difficult option, is SAT or GJK algorithms to detect the point of impact, but for this you have to split your objects into concave subsections and if your doing that you could just as easily fix bounding boxes for each bodypart instead. You would only really need to use these algorithms if you need to calculate the exact point of impact (so that the objects can change their rotation in a space game for instance).

\$\endgroup\$
4
  • \$\begingroup\$ OK I see, but I am a little concerned about the accuracy of calculating the normal, if you could explain a little further just to see if it does what I think it does \$\endgroup\$ Feb 14, 2015 at 13:53
  • \$\begingroup\$ @user3195897 I added some more details and information to the post for you \$\endgroup\$ Feb 14, 2015 at 14:53
  • \$\begingroup\$ I see, I am going to experiment with this, to see which one works better for me and share the results with you \$\endgroup\$ Feb 14, 2015 at 17:04
  • \$\begingroup\$ Thanks I used the first method, the second was a good idea and I might apply it later for other stuff, anyways I didn't use the second one because I need to know the correct angle for every different type of object that the player might collide with, so enemies, crates, platforms, etc.. all need different angles and there are so many different types of objects that checking for each one will take ages. Besides I need to calculate those angles by myself first. \$\endgroup\$ Feb 14, 2015 at 18:58
1
\$\begingroup\$

Another solution would be to add another collisionbox ontop of the enemy that is onTrigger, when it's triggered push the player up a bit and destroy the enemy.

\$\endgroup\$
1
  • \$\begingroup\$ I guess that is my second option in case I don't find a solution to this \$\endgroup\$ Feb 14, 2015 at 13:48
0
\$\begingroup\$

Use Physics2D.Raycast http://docs.unity3d.com/ScriptReference/Physics2D.Raycast.html on your enemies and then check if your player hits the enemies raycast that's on top of the enemy.

Cheers, Demetry :)

\$\endgroup\$

You must log in to answer this question.

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