4
\$\begingroup\$

I've just really gotten started in Unity and while documentation has been helpful I haven't figure out how to detect collisions with 2 or more objects. I was able to get a script working that plays a sound when I touch the pressure plate with the player but I don't know how I would do something when 2 objects are within the collider but undo it when one of them leaves the area.

void OnTriggerEnter2D(Collider2D other)
{
    if(other.tag == "Player")
    {

        AudioSource.PlayClipAtPoint(Sound, transform.position);
    }

}
}

Am I going about this the wrong way by using OnTriggerEnter2D? I see that there is a mass component on Rigidbodies but I don't know if I should be using it because I am still a novice in Unity. I'm also aware of OnCollisionExit but I don't think that works in this case because I'm currently only detecting 1 object but I need to detect 2. I was able to find videos that somewhat helped but all of them only worked with one object and were in a 3D space rather than a 2d one.

\$\endgroup\$
4
\$\begingroup\$

Let's first declare a variable to store the number of players currently standing/colliding with the platform.

In this case we want two players to activate the platform; Unity gives us 3 main functions to check whether an object collided with another object: OnTriggerEnter, OnTriggerStay and OnTriggerExit.

In this case we are working with 2 dimensions, so we are going to use the 2D versions of these functions.

We specifically want to check whether our players are both colliding with the object's collider; (In the pseudo script i attached below, I am assuming you tagged your first player Player1 and your second one Player2)

We are going to use the OnTriggerEnter2D() function, (which takes in as parameter the object the platform is colliding with) to increment the player count variable by 1, if either one of our players is inside the trigger area;

Once either one of them gets out the trigger area, playerCount gets decremented by one, to a total of 0, if both players have gone out the trigger area.

PseudoScript

int playerCount = 0;

void OnTriggerEnter2D(Collider2D other)
{
     if(playerCount < 2)
   {
       if(other.tag == "Player" || other.tag == "Player2")
       {
          playerCount++;
       }
    }

   else
   {  
       OnState(); 
   }
}

void OnTriggerExit2D(Collider2D other)
{
   if(playerCount > 0)
   {
       if(other.tag == "Player" || other.tag == "Player2")
       {
          playerCount--;
       }
    }

   else
   {  
       OffState(); 
   }
}
\$\endgroup\$
  • \$\begingroup\$ The logic in OnTriggerEnter2D won't work if only Player 1 is standing on the plate. \$\endgroup\$ – Quentin Oct 24 '18 at 9:55
  • 1
    \$\begingroup\$ Now it won't work if something else is on the pressure plate :p \$\endgroup\$ – Quentin Oct 24 '18 at 10:01
  • 1
    \$\begingroup\$ Sorry, lunch time is coming up ;) -- but I'll take your word for it, I'm not familiar with Unity. \$\endgroup\$ – Quentin Oct 24 '18 at 10:12
  • 1
    \$\begingroup\$ I understand for the most part and I really appreciate the time that you spent helping me answer my question. I haven't tested it fully but it was really helpful in learning how to work with multiple collisions. I'm still fleshing out what I want OnState and OffState to do but I haven't gotten any compiler errors. \$\endgroup\$ – Mark Gregg Oct 24 '18 at 10:33
  • 4
    \$\begingroup\$ Please do not get into the habit of relying on the Update function. This can be a big issue when working within Unity. By putting the actual code to check in the Update function, you are forcing an if check EVERY SINGLE frame. This may not appear much, but imagine there are 20 pressure plates? That's 20 additional checks per frame, for no reason. You already have the event calls of OnTriggerEnter2D and On TriggerExit2D, there is no need to use Update. Simply call a function when/where necessary from the OnTriggerEnter2D, or OnTriggerExit2D. \$\endgroup\$ – cmprogram Oct 24 '18 at 10:57
7
\$\begingroup\$

As you can see on the execution order diagram, the engine will first execute all FixedUpdate methods, then all OnTriggerXXX methods and then all Update methods.

That means you can have a player count for your pressure plate which has

  1. A FixedUpdate() method which resets the count to 0
  2. A OnTriggerStay(Collider other) method which increases the count by 1 everytime it is called with an other collider which belongs to a player
  3. A Update method which checks the count and acts appropriately.

A completely different approach you could use would be to use the Unity physics engine and implement your pressure plate like an actual, physical pressure plate.

  1. Assign non-kinematic, gravity-affected rigidbodies to the characters and the pressure plate.
  2. Place the pressure plate slightly above the ground
  3. Use a spring joint between pressure plate and floor so that the pressure plate resists forces.
  4. Restrict the movement- and rotation axis` of the plate's rigidbody so it can only move up and down.
  5. Add a trigger-collider below the pressure plate which detects if the plate moves down.
  6. Calibrate the spring strength and the player mass so that the weight of two players moves it into the trigger collider but the weight of one player does not.

However, I would only recommend this solution if you are creating a game which is supposed to have realistic physics and you are already making use of the physics engine. Also keep in mind that crafty players might find alternative methods to add enough force to the pressure plate to trigger it. Whether this is good or bad depends on your game.

\$\endgroup\$
  • \$\begingroup\$ the physics-based approach is a much better looking and working apporach. 1+ \$\endgroup\$ – Gabriele Vierti Oct 24 '18 at 11:21
  • 1
    \$\begingroup\$ While unlikely to be a problem in most real world cases, it isn't a good idea to rely on FixedUpdate as an "EarlyUpdate". For one it usually happens multiple times per update which can do unnecessary work (not really an issue in this case), but more importantly if the framerate goes very high it might not happen at all since it happens on a fixed interval! A more robust way can use Time.frameCount when setting/checking the value and resetting it to 0 before either operation if the current frameCount differs from the last used when setting. \$\endgroup\$ – Lunin Oct 24 '18 at 18:54
  • \$\begingroup\$ @Lunin This situation doesn't use FixedUpdate as EarlyUpdate. It uses FixedUpdate as intended: As a step which happens before OnTriggerXXX events, which are also executed within the fixed update loop. \$\endgroup\$ – Philipp Oct 24 '18 at 22:49
  • \$\begingroup\$ Ah, you're right; since OnTrigger is physics bound rather than update bound this should hold true even if FixedUpdate gets skipped due to high framerate as so will the OnTrigger events (thus leaving it in the correct state until the next physics update). My bad! I'll leave my comment unedited so any others with the same thought process I had can see the correction :) \$\endgroup\$ – Lunin Oct 24 '18 at 23:12
2
\$\begingroup\$

When working with colliders in Unity, remember that trigger and collider are two different things. Do not confuse these two.

If you would like your object to react/bounce off another object, use Collider. In your case, Trigger is what yo should be using. So ensure IsTrigger is enabled on the pressure plate.

You must also ensure at least one GameObject, either the pressure plate, or the player and other object you wish to detect, has a Rigidbody2D attached to it.

private int colCount = 0;

private void OnTriggerEnter2D(Collider2D col){

switch(col.tag){
    case "Player":
    colCount++;
    break;
    case "Other Object":
    colCount++;
    break;
  }

if(colCount >= 2){
    runDetectionConditions();
  }


}

private void OnTriggerExit2D(Collider2D col){

switch(col.tag){
    case "Player":
    colCount--;
    break;
    case "Other Object":
    colCount--;
    break;
  }

}

// Run whatever conditions you would like to run, when two objects are detected on pressure plate
private void runDetectionConditions(){
Debug.Log("Two objects are being detected on collider.");
}
\$\endgroup\$
1
\$\begingroup\$

There are a lot of people here already answering in terms of using the OnTrigger calls (which could be replaced with OnCollision calls if you want to work with non-trigger colliders). However I wanted to suggest another method should you find yourself in a situation where you just want to do a quick check of what colliders are in a given area without doing a lot of setup.

Both 2D and 3D Unity have Physics functions to get one or many objects in a given location. For 2D Physics the single version is always something like Physics2D.Overlap[shape], while the multi version is Physics2D.Overlap[shape]All. For 3d Physics just drop the 2D from either of those.

For something like a pressure plate you'd be looking at something like Physics2D.OverlapAreaAll, Physics2D.OverlapBoxAll, Physics2D.OverlapCapsuleAll, or as another option possibly Physics2D.LinecastAll. These could all be placed much like your trigger is now, only through code instead of the editor.

From these you will get an array of Collider2D objects which are in the given area and on the appropriate layers (should you employ layer masking, which you should since it will throw out a lot of unnecessary checking). From there you can count, adjust, or whatever any traits you wish on the relevant GameObjects based on the results of the check.

As a side note since I see performance comments on other answers, this method will pay the detection cost for each pressure plate you have in scene that is doing the check. However, adding triggers to your scene is not free either. In either case don't worry about the performance too much until you start seeing a problem, at which point you can pull up the profiler and see what is taking too much of your time and address it.

\$\endgroup\$

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.