Search Unity

Handling objects collisions the right way

Discussion in 'Scripting' started by MichaelABC, Oct 20, 2021.

  1. MichaelABC

    MichaelABC

    Joined:
    Jan 25, 2020
    Posts:
    69
    I have to objects: "Ext" and "Int". When "Ext" collides with "Int" "Ext" is destroyed; while when "Int" collides with "Ent" a new "Int" is instantiated in a particular position.

    What is the right order and code to write this and make it work?

    I tried stuff but I end up with Int spawning endlessly and Ent not being destroyed.
     
  2. adehm

    adehm

    Joined:
    May 3, 2017
    Posts:
    369
    Maybe only one of them should have the collision code to handle it. For instance 'Ext' handles both by checking for its collision with 'Int' then calling a function on the 'Int' object's script.
     
    Joe-Censored and MichaelABC like this.
  3. Yoreki

    Yoreki

    Joined:
    Apr 10, 2019
    Posts:
    2,605
    How are you doing it currently? What is your setup? Which object is executing the code, which code? Or are both running some script? Also, how do you plan on determining which object collided with the other? A collision is a collision, there is no object specifically colliding with the other, as if the other did not as well collide with the first. So what do you intend to base this on? Their relative movement directions? Their speed?
     
    MichaelABC likes this.
  4. MichaelABC

    MichaelABC

    Joined:
    Jan 25, 2020
    Posts:
    69
    You both have good points. I decided to do what adehm suggested and divide the work on two scripts, one for each object. Now Ext checks for collision and then destroy itself, after this Int instatiates another int.

    I am far from finish and I am proceeding one step at the time. However I already met with some hiccups.

    This should be unity scripting 101, but for some reason I cannot destroy Ext when it collides with Int:
    Here's the script:

    Code (CSharp):
    1. public class Destroy_Ext : MonoBehaviour
    2. {
    3.     private GameObject colliding;
    4.     public void Update()
    5.     {
    6.        
    7.     }
    8.     private void OnTriggerEnter(Collider other)
    9.     {
    10.         if (other.gameObject.tag == "Int_Molecule")
    11.         {
    12.             Destroy(this.gameObject);
    13.         }
    14.     }
     
  5. Yoreki

    Yoreki

    Joined:
    Apr 10, 2019
    Posts:
    2,605
    Be a bit more clear when talking about a problem. What happens instead? If so what? Does nothing happen? If nothing happens, check if the function gets called at all, and if the if-statement gets entered as you expect. Placing a couple Debug.Log statements can help you gain additional information about a problem.
     
    MichaelABC likes this.
  6. MichaelABC

    MichaelABC

    Joined:
    Jan 25, 2020
    Posts:
    69
    I put the debug.log statements but they don't appear. So I guess nothing is happening.
     
  7. MichaelABC

    MichaelABC

    Joined:
    Jan 25, 2020
    Posts:
    69
    For some weird reason even if a add a debug.log into void Update it does not appear... Why is that?
     
  8. adehm

    adehm

    Joined:
    May 3, 2017
    Posts:
    369
    Is the script attached to a currently active object?
     
    Yoreki likes this.
  9. MichaelABC

    MichaelABC

    Joined:
    Jan 25, 2020
    Posts:
    69
    I'll give you the complete picture: the behaviour that I am exactly trying to get is the one in this animation: https://s10.gifyu.com/images/organ.gif
    And I think I was finally able to do it with the code below.

    Code (CSharp):
    1. public class AttachmentController : MonoBehaviour
    2. {
    3.     //DETECTOR(ext)
    4.     public Transform UR_Detector;
    5.     public Transform UL_Detector;
    6.     public Transform DR_Detector;
    7.     public Transform DL_Detector;
    8.     //HOLDER(int)
    9.     public Transform UR_Holder;
    10.     public Transform UL_Holder;
    11.     public Transform DR_Holder;
    12.     public Transform DL_Holder;
    13.     public Transform Int_MoleculePrefab;
    14.  
    15.     // Update is called once per frame
    16.     void Update()
    17.     {
    18.         //DETECTOR(ext)
    19.         UR_Attach();
    20.         UL_Attach();
    21.         DR_Attach();
    22.         DL_Attach();
    23.     }
    24.  
    25.     private void UR_Attach()
    26.     {
    27.    
    28.         RaycastHit2D Ext2Int = Physics2D.Raycast(UR_Detector.position, Vector2.zero, -0);
    29.  
    30.  
    31.         if (Ext2Int.collider != null && Ext2Int.collider.tag == "Ext_Molecule")
    32.         {
    33.             Ext2Int.collider.gameObject.tag = "Untagged";
    34.             Destroy(Ext2Int.collider.gameObject);
    35.    
    36.             Transform Int_Molecule_Inst = Instantiate(this.Int_MoleculePrefab);
    37.             Int_Molecule_Inst.transform.parent = UR_Holder;
    38.             Int_Molecule_Inst.transform.position = UR_Holder.position;
    39.         }
    40.     }
    41.     private void UL_Attach()
    42.     {
    43.         RaycastHit2D Ext2Int = Physics2D.Raycast(UL_Detector.position,Vector2.zero, -0);
    44.  
    45.         if (Ext2Int.collider != null && Ext2Int.collider.tag == "Ext_Molecule")
    46.         {
    47.             Ext2Int.collider.gameObject.tag = "Untagged";
    48.             Destroy(Ext2Int.collider.gameObject);
    49.        
    50.             Transform Int_Molecule_Inst = Instantiate(this.Int_MoleculePrefab);
    51.             Int_Molecule_Inst.transform.parent = UL_Holder;
    52.             Int_Molecule_Inst.transform.position = UL_Holder.position;
    53.         }
    54.     }
    55.     private void DR_Attach()
    56.     {
    57.         RaycastHit2D Ext2Int = Physics2D.Raycast(DR_Detector.position, Vector2.zero, 0);
    58.  
    59.         if (Ext2Int.collider != null && Ext2Int.collider.tag == "Ext_Molecule")
    60.         {
    61.             Ext2Int.collider.gameObject.tag = "Untagged";
    62.             Destroy(Ext2Int.collider.gameObject);
    63.      
    64.             Transform Int_Molecule_Inst = Instantiate(this.Int_MoleculePrefab);
    65.             Int_Molecule_Inst.transform.parent = DR_Holder;
    66.             Int_Molecule_Inst.transform.position = DR_Holder.position;
    67.         }
    68.     }
    69.     private void DL_Attach()
    70.     {
    71.  
    72.         RaycastHit2D Ext2Int = Physics2D.Raycast(DL_Detector.position, Vector2.zero, 0);
    73.  
    74.         if (Ext2Int.collider != null && Ext2Int.collider.tag == "Ext_Molecule")
    75.         {        
    76.             Ext2Int.collider.gameObject.tag = "Untagged";
    77.             Destroy(Ext2Int.collider.gameObject);
    78.      
    79.             Transform Int_Molecule_Inst = Instantiate(this.Int_MoleculePrefab);
    80.             Int_Molecule_Inst.transform.parent = DL_Holder;
    81.             Int_Molecule_Inst.transform.position = DL_Holder.position;
    82.         }
    83.     }
    84. }
     
  10. MichaelABC

    MichaelABC

    Joined:
    Jan 25, 2020
    Posts:
    69


    Just found I have a new problem though

    Int_Molecule should instantiate only once, but if it touches more than one corner (UR = UP RIGHT, UL = UP LEFT, etc.) it instantiated twice. Is there a way to instantiate the object only once?
     
  11. Yoreki

    Yoreki

    Joined:
    Apr 10, 2019
    Posts:
    2,605
    Have your functions return a bool. Only check the others if the one before it did not attach.
    This will also reduce performance cost somewhere between 0-75%.

    Other than that, looks like a lot of redundant code tho. You could write one function that combines your 4 into one, which would then simply need to be parametrized to take an holder and detector as input. Everything else seems to be the same, repeated 4 times separately. It's usually a good idea to avoid such code repetition for maintenance reasons.
     
  12. MichaelABC

    MichaelABC

    Joined:
    Jan 25, 2020
    Posts:
    69
    I tried to do something like this, but it does not work. Still the same problem o_O
    Code (CSharp):
    1.  private void UR_Attach()
    2.     {
    3.        
    4.         RaycastHit2D Ext2Int = Physics2D.Raycast(UR_Detector.position, Vector2.zero, -0);
    5.        
    6.  
    7.         if (Ext2Int.collider != null && Ext2Int.collider.tag == "Ext_Molecule" && returnal == false)
    8.         {
    9.             Ext2Int.collider.gameObject.tag = "Untagged";
    10.             Destroy(Ext2Int.collider.gameObject);
    11.        
    12.             Transform Int_Molecule_Inst = Instantiate(this.Int_MoleculePrefab);
    13.             Int_Molecule_Inst.transform.parent = UR_Holder;
    14.             Int_Molecule_Inst.transform.position = UR_Holder.position;
    15.  
    16.             returnal = true;
    17.         }
    18.  
    19.         else
    20.         {
    21.             returnal = false;
    22.         }
     
  13. Yoreki

    Yoreki

    Joined:
    Apr 10, 2019
    Posts:
    2,605
    So.. do you use that bool? Might want to show that part aswell.
     
  14. MichaelABC

    MichaelABC

    Joined:
    Jan 25, 2020
    Posts:
    69
    I am setting it as true or false. Other than there it is in the parameters, but I am not using it anywhere else.
     
  15. MichaelABC

    MichaelABC

    Joined:
    Jan 25, 2020
    Posts:
    69
    Do I have to use a singleton to make this work possibly?
     
  16. Yoreki

    Yoreki

    Joined:
    Apr 10, 2019
    Posts:
    2,605
    Alright i did not see you added it to the condition within the function there. That's a bit tangled.
    I would have ordered it more like this:
    • Each function returns a bool based on whether they executed the attachment code or not
      You do know that functions can return values instead of void?
    • In your Update() you define some bool continue_checking=True
    • Now you simply execute something like if(continue_checking){continue_checking&=!UR_Attach()}
      for each of your functions
    , meaning continue_checking would always start as True in Update(), and since you 'and' it with the negated return of each function, it would become false as soon as one of the functions returned true, ie executed the attaching part.

    Your problem is that your system resets itself in the next function call.
    Imagine this hypothetical scenario: the bool starts as False, every function wants to attach.
    What happens:
    - UR can execute and sets the bool to True
    - UL cannot execute, thus sets the bool to False again
    - DR can execute again since the bool is False already again, which you dont want
    - ...

    So that's not working.
    In general, when you have a problem like this and dont know why something works different from how you expect it to work, i recommend playing Debug.Log statements to gain additional informations. You could have, for example, printed a message at the start of each frame (Update) and then in each function within your attachment code part. This way you would have seen that still two attachments can happen per frame. Furthermore, you could have printed the value of the bool to gain additional information.
    Granted that, since you execute everything every single frame, that would have become very spammy on the console.
    The a bit more advanced solution would be to use a proper debugger, which lets you set a breakpoint and then step through the code line by line and inspect the values of involved variables.
     
    MichaelABC likes this.
  17. MichaelABC

    MichaelABC

    Joined:
    Jan 25, 2020
    Posts:
    69
    Ingenious solution, but alas it does not work, that is if I understood correctly how to implement it:

    Code (CSharp):
    1. void Update()
    2.     {
    3.         continue_checking = true;
    4.         UR_Attach();
    5.         UL_Attach();
    6.         DR_Attach();
    7.         DL_Attach();
    8.  
    9.         if (continue_checking)
    10.         {
    11.             continue_checking &=! UR_Attach();
    12.             continue_checking &=! UL_Attach();
    13.             continue_checking &=! DR_Attach();
    14.             continue_checking &=! DL_Attach();
    15.         }
    16.  
    17.     bool UR_Attach()
    18.     {
    19.      
    20.  
    21.         RaycastHit2D Ext2Int = Physics2D.Raycast(UR_Detector.position, Vector2.zero, -0);
    22.      
    23.  
    24.         if (Ext2Int.collider != null && Ext2Int.collider.tag == "Ext_Molecule")
    25.         {
    26.             Ext2Int.collider.gameObject.tag = "Untagged";
    27.             Destroy(Ext2Int.collider.gameObject);
    28.        
    29.             Transform Int_Molecule_Inst = Instantiate(this.Int_MoleculePrefab);
    30.             Int_Molecule_Inst.transform.parent = UR_Holder;
    31.             Int_Molecule_Inst.transform.position = UR_Holder.position;
    32.  
    33.             Debug.Log("UR_Attach");
    34.  
    35.             return true;
    36.         }
    37.  
    38.         else
    39.         {
    40.             return false;
    41.         }
    42.      
    43.     }
    As you can see in the picture the bug happened when hitting with DR_Dector, but interestingly the function is called only once!

    Any bright idea?

    upload_2021-11-2_22-15-19.png
     
  18. Yoreki

    Yoreki

    Joined:
    Apr 10, 2019
    Posts:
    2,605
    You are still executing all of your functions. You now just potentially do it twice.
    Also, you either missed a bracket, or your UR_Attach function is now declared within Update, which you do not want.

    Update would literally just look like this:
    Code (CSharp):
    1. void Update() {
    2.     bool continue_checking = true;
    3.     if(continue_checking){continue_checking &= !UR_Attach()}
    4.     if(continue_checking){continue_checking &= !UL_Attach()}
    5.     if(continue_checking){continue_checking &= !DR_Attach()}
    6.     if(continue_checking){continue_checking &= !DL_Attach()}
    7. }
    (I hope that's valid C# as im only working with Python recently haha)

    However, if only one of your functions gets called then the initial assumption that this might be caused by multiple functions being triggered might be wrong. Does the bug happen with anything other than DR at all? In the GIF you posted above it was also DR causing the problem i think. Maybe there are special circumstances in DR? Or a typo?

    To make sure all your functions behave the same i would, again, recommend combining them all into one function.
    Meaning one single "Attach()" function, which has parameters for a detector and a holder, which you then pass to it for your 4 cases in Update. This cuts down on code redundancy a lot, and makes it impossible for a typo or other mistake to make one of your functions behave differently than the others. This is also what functions are for: reduce code repetition.

    Debugging is all about gaining additional information.
    Try placing some more Debug.Log messages and find out, for example but not limited to:
    • Is the / a situation that causes the additional spawn reproducible? If so, this makes it a lot easier to gain additional information by analyzing what exactly happens.
    • Which part of the code spawns the additional square? Is it actually where you think it is?
    • When does it happen? In the same frame the intended one spawns? A frame later? Even later?
    • What triggers the additional spawn? What collides with what? Why? (Maybe it collides with the object you Destroy, since those get cleaned up at the end of the frame, but i dont think so since Update should only be called next frame.)
    • ...
     
  19. MichaelABC

    MichaelABC

    Joined:
    Jan 25, 2020
    Posts:
    69
    It's syntax valid, but I am not sure it achieves it. What does "&= !" mean?

    The fact that is always DR is just a coincidence of me testing the game the same way. It does with all of them.

    I will implement the one function you suggest once I figure this problem out (and once I figure how to do the one function). It's a good recommendation.

    I am not sure how to debug this further, but I think I might know the reason.

    There is no way the Attachment script can distinguish the different detectors that collide and the holders that are spawned. So it treats all of them as the same: when it collides with X Int it instantiate in X holders.

    upload_2021-11-3_11-6-4.png

    Potential solutions:
    • Make sure that if detectors hits A, B and C an Int only instantiates in either A,B or C (but on the base of what? Shorter distance from the centre of Ext?)
    • Drop the Ext Int distinction and have an Ext molecule to move to holder position instead of instantiating a new one (an object cannot move to two different positions at the same time, so I suppose it will choose one? However I don't recall why I had to do this distinction in the first place, might have been important)
     
  20. MichaelABC

    MichaelABC

    Joined:
    Jan 25, 2020
    Posts:
    69
    I could also add a spawnCount integer
     
  21. Yoreki

    Yoreki

    Joined:
    Apr 10, 2019
    Posts:
    2,605
    You can combine the assignment operator '=' with simple operations. For example:
    Code (CSharp):
    1. int a = 5;
    2. int b = 6;
    3.  
    4. // These are now equal:
    5. a = a + b; // new value is old plus other
    6. a += b; // add and assign. Does the same^
    The '&=' is the 'and assign', so it's basically equal to 'someBool = someBool && otherBool'.
    The '!' is 'not', negating the following bool - in this case the return value of your function.

    I would think about the reason then. Because if there was none, reducing complexity is generally a good thing. Especially if it might cause your problem. Right now there are a lot of factors that make it hard to debug remotely.
    Your idea seems reasonable. When in doubt, make a save copy and experiment around. A version control system also makes this much easier, if you are going to work on something longer term.
     
  22. MichaelABC

    MichaelABC

    Joined:
    Jan 25, 2020
    Posts:
    69
    Sorry, I am familiar with the operators, but not sure of this context. I should have asked the meaning of the whole "if method":
    Code (CSharp):
    1. if(continue_checking){continue_checking &= !UR_Attach()}
    Does it mean something like: if boolean continue_checking is called return continue_checking true and don't set it to the value just returned by UR_Attach (either true or false)

    I just remember why they had to be separate molecules (int e ext) becasue otherwise if the detector is on Int it will keep detecting the collision with the other int and keep calling the function...

    I will try and experiment. I have set up version control with Git Hub
    https://github.com/michael-exe/Organism2

    Also I found what I hope is a good guide on how to use more advanced debugging features:
     
    Last edited: Nov 3, 2021
  23. MichaelABC

    MichaelABC

    Joined:
    Jan 25, 2020
    Posts:
    69
    I managed to solve the instantiation glitch (by getting rid of the instantiation problem altogether). Hooray!

    However I have no idea yet on how to make everything as one function.

    Code (CSharp):
    1. public class AttachmentController : MonoBehaviour
    2.     //really: HolderController
    3. {
    4.     //DETECTOR(ext)
    5.     public Transform UR_Detector;
    6.     public Transform UL_Detector;
    7.     public Transform DR_Detector;
    8.     public Transform DL_Detector;
    9.     //HOLDER(int)
    10.     public Transform UR_Holder;
    11.     public Transform UL_Holder;
    12.     public Transform DR_Holder;
    13.     public Transform DL_Holder;
    14.  
    15.     // Update is called once per frame
    16.     void Update()
    17.     {
    18.         //DETECTOR(ext)      
    19.         UR_Attach();
    20.         UL_Attach();
    21.         DR_Attach();
    22.         DL_Attach();
    23.     }
    24.  
    25.     //DETECTOR(ext) referencing HOLDER(int)
    26.     void UR_Attach()
    27.     {
    28.        
    29.  
    30.         RaycastHit2D Ext2Int = Physics2D.Raycast(UR_Detector.position, Vector2.zero, -0);
    31.         //Not really Ext2Int anymore
    32.        
    33.  
    34.         if (Ext2Int.collider != null && Ext2Int.collider.tag == "Ext_Molecule")
    35.         {
    36.             //Destroy(Ext2Int.collider.gameObject);
    37.             Ext2Int.collider.transform.parent = UR_Holder;
    38.             Ext2Int.collider.transform.position = UR_Holder.position;
    39.             Ext2Int.collider.tag = "Int_Molecule";
    40.         }
    41.  
    42.     }
    43.     void UL_Attach()
    44.     {
    45.         RaycastHit2D Ext2Int = Physics2D.Raycast(UL_Detector.position,Vector2.zero, -0);
    46.  
    47.         if (Ext2Int.collider != null && Ext2Int.collider.tag == "Ext_Molecule")
    48.         {
    49.             Ext2Int.collider.transform.parent = UL_Holder;
    50.             Ext2Int.collider.gameObject.transform.position = UL_Holder.position;
    51.             Ext2Int.collider.gameObject.tag = "Int_Molecule";
    52.         }
    53.     }
    54.     void DR_Attach()
    55.     {
    56.         RaycastHit2D Ext2Int = Physics2D.Raycast(DR_Detector.position, Vector2.zero, 0);
    57.  
    58.         if (Ext2Int.collider != null && Ext2Int.collider.tag == "Ext_Molecule")
    59.         {
    60.             Ext2Int.collider.transform.parent = DR_Holder;
    61.             Ext2Int.collider.gameObject.transform.position = DR_Holder.position;
    62.             Ext2Int.collider.gameObject.tag = "Int_Molecule";
    63.         }
    64.     }
    65.     void DL_Attach()
    66.     {
    67.      
    68.         RaycastHit2D Ext2Int = Physics2D.Raycast(DL_Detector.position, Vector2.zero, 0);
    69.  
    70.         if (Ext2Int.collider != null && Ext2Int.collider.tag == "Ext_Molecule")
    71.         {
    72.             Ext2Int.collider.transform.parent = DL_Holder;
    73.             Ext2Int.collider.gameObject.transform.position = DL_Holder.position;
    74.             Ext2Int.collider.gameObject.tag = "Int_Molecule";
    75.         }
    76.  
    77.     }
    78.  
    79. }
     
    Yoreki likes this.
  24. Yoreki

    Yoreki

    Joined:
    Apr 10, 2019
    Posts:
    2,605
    We start with continue_checking as True. So the first if-statement gets entered. There we assign a new value to continue_checking based on the negated return value of the function. So if the function did not attach, we have continue_checking = continue_checking && True, which is True again. However, if the function did return true, we have continue_checking = continue_checking && False, which gives us False.
    All this leads to us only executing the attachment functions until the first one was successfull.
    So if UR does not attach, we execute UL and so on. But if UL attached, we do not execute DR and DL anymore.
    That's what it does.

    Nice, good job!
    A bit off-topic, but it's a bug, not a glitch. Bugs are reproducible differences between specification (what you want) and implementation (what you did). Which accounts for mostly everything in software development. Glitches are usually irreproducible (often hardware related) problems. A charged particle from outer space flipping a bit in your code and causing unexpected, irreproductible behavior, that would be a glitch.
    The term glitch is also sometimes used for purely graphical bugs, which is fine too imho.

    Your functions contain 95% identical code. So we basically take the 5% that can change and turn it into parameters.
    Code (CSharp):
    1.     void TryAttach(Transform detector, Transform holder)
    2.     {
    3.      
    4.         RaycastHit2D Ext2Int = Physics2D.Raycast(detector.position, Vector2.zero, -0);
    5.         //Not really Ext2Int anymore
    6.      
    7.         if (Ext2Int.collider != null && Ext2Int.collider.tag == "Ext_Molecule")
    8.         {
    9.             //Destroy(Ext2Int.collider.gameObject);
    10.             Ext2Int.collider.transform.parent = holder;
    11.             Ext2Int.collider.transform.position = holder.position;
    12.             Ext2Int.collider.tag = "Int_Molecule";
    13.         }
    14.     }
    And then you simply call it with the correct parameters to get the same result:
    Code (CSharp):
    1.     void Update()
    2.     {
    3.         TryAttach(UR_Detector, UR_Holder);
    4.         TryAttach(UL_Detector, UL_Holder);
    5.         // ..and so on..
    6.     }
    (Again no guarantee it compiles out of the box since it's written directly on the forum)

    Parametrizing functions is pretty basic, so maybe it's a good idea for you to work through a C# introduction?
    The rest comes with experience. Glad you got your problem fixed :)
     
    MichaelABC likes this.
  25. MichaelABC

    MichaelABC

    Joined:
    Jan 25, 2020
    Posts:
    69
    That worked like a charm!
    And you are right: it's time for some revision.