0
\$\begingroup\$

I am using Physics.Overlap to find which enemies are in my damage range. But dealing damage in only one frame is not what I want. Some attacks should be active for a longer time, to catch enemies who come into range part-way through the attack.

But if I call the method again, I can get the same enemy multiple times. This means my damage will be higher if the framerate is higher. To avoid this I can:

  1. Put a timer on the enemy that prevents damage for some period of time after taking damage. This is the simplest approach but it's bad if you have high speed, multi-hit attacks that damages the enemy multiple times quickly.

  2. Keep track of damaged enemies with a list. As long as the hitbox is active, check every frame for enemies inside the hitbox. For each enemy found, check whether it's already in the list, add it to the list and damage it if not.

  3. Same as #2 but use a dictionary/hashset.

Option #2 is my current approach. Hashsets are supposed to make dupe checks faster BUT I'm not sure how It does that. I use a List<damageable> to track damaged enemies and Damageable is an interface.

How does a Hashset<Damageable> differentiate between each entry if they are interfaces?

If I use dictionaries instead I'd need to generate different keys for each enemy. That's another problem unless game objects already have some sort of unique hash key. Even then, there's the possibility of ignoring a gameobject with multiple damageable components.

Then there's also the possibility that I'm getting nothing by doing this optimization. Most hitboxes will probably intersect with 3-5 enemies at most over it's lifetime. Would looping over a list be faster anyways?

\$\endgroup\$
  • \$\begingroup\$ Couldn't you use delta time to damage the enemies that are inside the overlap over time? If you want to have "ticks" of damage you could keep track of them using your second approach. \$\endgroup\$ – Arthur Oct 25 '19 at 12:58
  • \$\begingroup\$ Typically gameplay/fairness-critical behaviours like damage should be happening in FixedUpdate anyway, so they run at a consistent rate regardless of your rendering framerate. That keeps the behaviour and game feel consistent across different devices. \$\endgroup\$ – DMGregory Oct 25 '19 at 13:21
  • \$\begingroup\$ @Arthur I already do that for DOT. I just want the ability to have a single hit attack to last multiple frames. \$\endgroup\$ – Houou In Kyouma Oct 25 '19 at 14:52
  • \$\begingroup\$ Relevant question on Stackoverflow: HashSet vs. List performance. But keep in mind that the C# implementation of Unity is not the same as the standard C# implementation from Microsoft. \$\endgroup\$ – Philipp Oct 25 '19 at 15:16
1
\$\begingroup\$

A Hashset is a collection that can't be sorted and cannot contain duplicate values, making it ideal for checking for duplicates especially due to how values are stored in memory.

It checks for equality first by calling GetHashCode(). Which is a method that all objects in C# implement, if you don't override it yourself it'll use the default implementation. For classes the default implementation should be fine. If you implement it yourself it is important that there are as few collisions as possible, a collision is when 2 unequal items produce the same HashCode.

If a hashset contains another item with the same hashcode, it'll call Equals() on these items (which is also a method all objects in C# implement and can be overridden) to check if they are equal. If they are equal you can't insert it as it is already present.

As for why a HashSet is faster is a bit technical and dependent on the actual implementation used. The simple (and incomplete) answer is that the hashcode is basically converted to an index, meaning that it doesn't have to loop the entire list to check whether an item is already inserted, but rather only the 1 at that index (or a few if there are some hashcode collisions). Making it more efficient especially with large collections.

A HashSet is basically a Dictionary with only keys and no values, so there is no reason to use a Dictionary over a HashSet unless you actually need to store a value with the key.

If you use the list only for duplicate checking, then I think a hashset would be better, even if just to simplify your code of duplicate checking by using the Add method.

if (myHashset.Add(myObject))
{
    // Do logic that should only happen if it wasn't in the hashset previously.
}

The performance difference will however probably not be noticeable with only 5 elements in the list.

As for the interface part, becasue you can't instantiate interfaces you never insert an interface in a HashSet. You insert a class (or struct) that implements an interface in the HashSet, meaning it uses that default (or your custom overridden) implementation.

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ Thanks for the detailed answer. I'm rather inexperienced with the nitty-gritty of interfaces in C#. understand that I'm actually adding an object to a hashset and not an interface. But the interface does not have a GetHashCode() method unless I add it. Does C# call the method on the object itself despite it not knowing what object it is? \$\endgroup\$ – Houou In Kyouma Oct 25 '19 at 14:46
  • 1
    \$\begingroup\$ C# is object oriented. All instances inherrit from object. If you have a class that doesn't inherrit from anything, it will inherrit from object. Meaning class Example {} is actually the same as class Example : object {}. So although an interface doesn't have a ToString method defined, C# knows you can call ToString on whatever implements it as whatever implements it inherrits from object which has a ToString method. So C# knows enough to call Equals and GetHashCode as both are (virtual) methods of object. \$\endgroup\$ – troien Oct 25 '19 at 15:17

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.