Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Efficient and maintainable implementation for collision detection

Discussion in 'Scripting' started by Tomas_Kok, Oct 16, 2017.

  1. Tomas_Kok

    Tomas_Kok

    Joined:
    Oct 5, 2014
    Posts:
    13
    Hi guys,

    I am trying to figure out a generic way to do my collision detection in Unity for a while both in terms of maintainability and performance. I will explain where I am now and I hope that you could provide me with some feedback.

    As a simple example, I have a bullet that could hit something and some GameObjects with a (MonoBevahiour) component that has the function 'hit' with some parameters such as 'damage'. The first way to handle this is providing the objects with a tag such as "Hittable" or more generic such as "Player". Now when the bullet is doing collision detection, it compares the tag and calls the hit function when the comparison returns true. I have a few problems with this approach:

    1. Comparing for a string is not very stable, what if the string changes or somewhere the string is misspelled?
    2. A GameObject can have only one tag. What if I want it to be Hittable (hit) and Talkable (talk)?
    3. There is no guarantee that a GameObject with a certain tag implements the corresponding function.

    My first thought was making a child class of MonoBehaviour (GameObjectInterface for instance) and let every GameObject that collides extend from it. This class functions as the interface of a GameObject to other GameObjects in the scene. When doing collision detection, only the GameObjectInterface functions will be called. By also implementing interfaces, it is able to communicate which collisions it will handle. For example, my Player could have a PlayerGameObjectInterface which extends the GameObjectInterface class and implements the interface Hittable (which forces it to implement a hit method). These methods could be implemented by hand or use ScriptableObjects with the delegate pattern. In any case, I like the modularity of this approach. The big question is, what is the most efficient way to detect whether a Hittable interface is implemented by a colliding object?

    Technically, the bullet could fetch the GameObjectInterface and do a type check for the Hittable interface. But is this efficient? This requires a call to GetComponent<>(), a typecheck and a cast. Ideally every GameObject that should handle collisions has a list of enums associated with it, just like it has a tag and layer. Than a ScriptableObject could store a Dictionary with enum values referring to interfaces (the Hit enum value refers to the Hittable interface), a compile time script could check all enum values a GameObject is associated with and throw an error when an enum value is associated with a GameObject but the corresponding interface is not implemented. Now the bullet can efficiently check the enum values at runtime and safely call the corresponding functions. Unfortunately, I could not find a way to efficiently associate such data with GameObjects.

    What do you guys think? Is there a way to do this or should I tackle this entirely different? The main goal for me is to find a way to define interfaces of GameObjects to other GameObjects in the scene, mainly for collision detection. The Hittable is a simple example, but I really think it would be worth having a generic and stable system for this when games grow more complex.

    Thank's in advance!
     
    Lylek likes this.
  2. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,670
    That could certainly work, but is it possibly over-engineered? There's something to say for simplicity.

    Why not embrace a straightforward component model? It seems to me that a simple Hittable MonoBehaviour would be faster to implement and easier to maintain. Even in a complex game, there aren't that many ways to collide with objects. Even if you had 4-5 different types of MonoBehaviours for collisions, the engineering burden would be lower than the generic system you're describing, and it would be easier for other developers to pick up.

    As a side note, it's not that inefficient to do a GetComponent, type check, and cast only when a collision occurs. It's not like it happens every frame. If you can judiciously restrict collisions by layer, this will also significantly cut down on the checks.
     
    Tomas_Kok likes this.
  3. GroZZleR

    GroZZleR

    Joined:
    Feb 1, 2015
    Posts:
    3,201
    No reason for deep, complex hierarchy. Embrace Unity's composition through aggregation pattern. Define an interface, IHittable in your example, and create components that implement it.

    Code (csharp):
    1.  
    2. interface IHittable { void OnHit(GameObject abuser); }
    3.  
    4. public DestroyOnHit : MonoBehaviour, IHittable
    5. {
    6.    public void OnHit(GameObject abuser)
    7.    {
    8.        Destroy(gameObject);
    9.    }
    10. }
    11.  
    12. public SpawnPresentsOnHit : MonoBehaviour, IHittable
    13. {
    14.    public Transform presentPrefab;
    15.  
    16.    public void OnHit(GameObject abuser)
    17.    {
    18.        Destroy(gameObject);
    19.  
    20.        Instantiate<Transform>(presentPrefab);
    21.    }
    22. }
    23.  
    You can fetch the interface via GetComponent no different than a concrete component.

    Code (csharp):
    1.  
    2. // during a collision:
    3. IHittable hittable = otherObject.GetComponent<IHittable>();
    4.  
    5. if(hittable != null)
    6.     hittable.OnHit(gameObject);
    7.  
     
    frarf, Ian094 and KelsoMRK like this.
  4. Tomas_Kok

    Tomas_Kok

    Joined:
    Oct 5, 2014
    Posts:
    13
    I agree that it is getting over-engineered, I went through the same process when I created an interface system for Assets to instances. What I ended up with was that changing the system itself lacks simplicity and would increase the burden for other programmers. But working with the system however turned out to be pretty simple and improved the development speed.

    Most important for me is that most checks are done at editor/compile time and casts/calls are safe during runtime, without the need for a lot of checks. Apart from that, I like the fact that one component is responsible for a single task (it could always delegate them when the class itself gets cluttered), the consistency that such a system offers and all types of interfaces a component has are clearly defined in one place.

    Having read your answer and having thought about it, I think that I should draw the boundary where I am now. The system itself is working and I should not spend too much time optimizing the GetComponent<> calls and casts.
     
  5. Tomas_Kok

    Tomas_Kok

    Joined:
    Oct 5, 2014
    Posts:
    13
    Thank's for your answer. I understand and appreciate Unity's component based system, but there is also a risk of GameObjects getting bloated with components. I rather have one component for every distinct task and let them delegate their behaviour to other BehaviourComponents/ScriptableObjects/custom classes when needed. I do agree that complex hierarchies should be avoided, but having abstract classes as parents (extending MonoBehaviour) offers a simple way of adding common functionality and the use of polymorphism.
    I guess this is a matter of taste. I think that your ''during collision'' part is right this way. It does not have the problems that a tag comparison has and should be efficient enough.
     
  6. KelsoMRK

    KelsoMRK

    Joined:
    Jul 18, 2010
    Posts:
    5,539
    That's what @GroZZleR is proposing. Your IHittable implementation is responsible for doing whatever needs done when something collides with it.
     
  7. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,670
    If it ain't broke.... :)
     
    Tomas_Kok and Ian094 like this.
  8. Tomas_Kok

    Tomas_Kok

    Joined:
    Oct 5, 2014
    Posts:
    13
    Thank's for all of your replies. I have decided to keep the system as it is because the time I would need to update it further is best spent on other functionality.

    As a final note, this is generically what I think that the problem is. I hope it is of help for those over-engineering a system who read this:
    - You want something in your project done in a specific way so you decide to build a custom editor tool or set of classes to handle this for you.
    - Personally, I do not think it is a problem when this code gets complex, as long as the time writing is is worth the benefits of using it.
    - Once the code has a clear and simple interface for other (non-)programmers and is thoroughly tested, nobody should care about the inner workings.
    - But when the game is being profiled (a lot in my case, with mostly mobile and VR games) their is a chance that your nicely encapsulated component will be inspected by other programmers after all.
    - This is why, as a rule of thumb, I think such a system should at least be as efficient performance wise as the simpler alternative, unless you know it will not be used in performance critical areas.

    In my case, the efficiency part became the problem and the ways to improve it became too complex for what I was trying to achieve.
     
    frarf likes this.