Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

(Suggestion) Which programming design is better for damaging the enemy?

Discussion in 'Scripting' started by Shiina011, Aug 26, 2021.

  1. Shiina011

    Shiina011

    Joined:
    Oct 1, 2020
    Posts:
    46
    So I'm trying to damage my enemy with player attack and come up with 2 programming design.

    1. Enemy have their own information like HP on their script and when player damaging them, they handle their own decreasing HP on their own script.

    2. Enemy have their own information like HP on their script but when player damaging them, player script is the one who decreasing their HP. So player script just grabbing enemy HP value and updating the value.

    From both ways, which is better?
     
  2. diXime

    diXime

    Joined:
    Oct 2, 2018
    Posts:
    162
    Hello,
    To be very strict, it would be better that HP remains a private variable, so you can handle internally what happens when they die. Maybe you'd want some sort of event to happen, some animations, and so on... which you don't want to write in full "if" statements inside your player's class. So ideally, you'd want to make it private and make getters/setters, like so, inside your ennemies classes (for example):
    Code (CSharp):
    1. public class EnnemyClass {
    2.     public float HP { get => hp; set => SetHP(value); } // this is public and is what you'd be calling when you need HP value
    3.     private float hp; // this is the actual value, kept private
    4.  
    5.     private void SetHP(float newHP) // what happens when you set the hp value
    6.     {
    7.         hp = newHP;
    8.         if(hp <= 0)
    9.         {
    10.             // Perform dying actions
    11.         }
    12.     }
    13. }
    Maybe it would be even better to do something like DoDamage instead of SetHP, so you don't set the hp to abnormal values (bigger than max), but it's entirely up to you and the way you intend to use it.
    This way, you can even create "enrage" actions like in some good games like raid bosses in World of Warcraft or the monsters of Monster Hunter, when the creature perform different actions when under 30% hp. And so on ! Your imagination is the only limit.
     
  3. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,758
  4. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,043
    In fact I would argue against both.

    What you're struggling with is essentially a notion of interaction as an entity per se.
    Enemy health is a property of that enemy, however, nobody should be the owner of an interaction that reduced the health.

    If you really want to be semantically correct about this, you can make a pattern where a player spawns an interaction, declaring its type and passing the enemy as an argument. Doesn't that make more sense?

    Now you have some added benefits: you can have various types of interaction (i.e. talking), various types of damage (i.e. electricity damage), and complex interactions from which new interactions may arise (i.e. traps).

    Additionally, in this way interactions literally do not have to be owned (carried) by any entity in particular, letting you do environment interactions (i.e. lightnings), or interactions that are bound to some other object in the world (i.e. fountain of youth), or even space itself (i.e. ward magick).

    So your dichotomy is a false one.
     
    exiguous likes this.
  5. Lethn

    Lethn

    Joined:
    May 18, 2015
    Posts:
    1,583
    I think the ultimate decider in this would be to learn how to use the Unity profiler I've been surprised myself learning the various tweaks you can do to a game to squeeze out that extra FPS and sometimes it can have absolutely nothing to do with what you thought was causing the problem. If we're talking pure optimisation learning to read the profiler is probably the best thing you can do in this case. If both pieces of code work similar to each other and the only real differences is where the hitpoints are grabbed from I suspect that the performance will be marginal at best. Experiment with both methods and see what happens for yourself so you can make the decision, it may just be a preference thing in the end.
     
  6. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,043
    I'm certain the question is not about the performance, but the semantical purity of two designs. It is a distinction made from an abstract vantage point, "does the player deliver damage" or "does the enemy receive the damage". And as I said, the paradox lies in the assumption that the solution boils down to a dichotomy.

    This paradox stems from the apparent energy efficiency of the solution. Rare is a programmer who will zoom out enough to appreciate he or she is stuck at the local maximum. In this case you have to acknowledge that you need to add something to the problem, rather than reduce it further, a very energy-inefficient mental maneuver.
     
  7. diXime

    diXime

    Joined:
    Oct 2, 2018
    Posts:
    162
    It's not a dichotomy. Most interfaces deal with public setters of private variables. Even your proposition would also work with both interfaces and public setters. *Your* dichotomy is a false one. I merely presented the first steps, Kurt added an extra layer (to my opinion, issued for when there are many enemies and types of enemies), and you added another one. It's a fair point, I just disagree with your assumption that they were mutually exclusive.

    The enemy is not always available as an argument. In a shooter game situation, you can be aiming at something and your projectile lands on something else. In that case, the "DoDamage" would be on the bullet, for example, and only perform its action upon collision.

    Then again, the three approaches would work just fine together. It depends on how big the game is and the need for any extra layer. And when local maximum is reached, adding a separate interaction won't be any issue at all. You perform the strictly related to the enemy in the enemy class (like the enrage situations, the death animation, etc. ), the damage type on the projectiles if you add such things to your game later, and so on.
     
  8. Shiina011

    Shiina011

    Joined:
    Oct 1, 2020
    Posts:
    46
    Thank you guys for all the replies, I honestly don't expect to get this much answer because my question is really simple. I just don't know which is better to handle the enemy decreasing hp.

    So after reading more, I decided to follow @diXime suggestion where this matter should be handled inside the enemy instead of the player script (remains me of SOLID method). Then by using interfaces as Kurt suggests, I can expand this decreasing hp method easily for a different types of enemies.

    So again, thank you for all the help.
     
  9. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,043
    to which the answer is 42 obviously :D
     
  10. Inxentas

    Inxentas

    Joined:
    Jan 15, 2020
    Posts:
    275
    I am a fan of three things: DRY (don't repeat yourself) KISS (keep it stupidly simple) and Seperation of Concern.

    As such I decided to let another component handle Health and Damage entirely separate from the thing that gets damaged. It is simply added to the top-level object of a prefab of that thing, where most of my code lives. The component is added to things that might be affected by damage. Encapsulation, if I have my OOP terminology straight.

    I've written a single HealthComponent that manages all of this internally and there is a clear distinction between private methods (for internal use) and public ones (for anything that might affect it). For example, damage is done via a method and then clamped between max HP and zero. In order to read anything from that component (for rendering HP bars and such) I like to use Getters. In order to configure the thing I use a ScriptableObject that also contains it's resistance profile against various damage types.

    It has public methods that accept a Damage class as input. There is literally no way to damage a thing except via those access points. The reason I do this is because of Damage consisting of a whole profile including damage types and proc/status effect settings. I've gone as far as to even separate the concern "damage" from a projectile to the explosion or impact it causes when conditions are reached. This might be overkill for your requirements, but one of the benefits I found to this approach was that the idea of "damage" can be made more complex, and managed with ScriptableObjects, without complexifying the whole enemy.

    This all may sound quite complex in regards to the initial question, but if you indeed put the logic on the enemy instead of the player you're already keeping your options open enough for now. You're treating "getting damaged" as a concern of the thing that gets damaged, instead of the thing that does damage. So no matter how your enemy takes damage, damage recieved is treated consistently. And damage dished out remains the concern of things...

    ...that do damage!