Wayback Machinekoobas.hobune.stream
May JUN Jul
Previous capture 13 Next capture
2021 2022 2023
2 captures
13 Jun 22 - 14 Jun 22
sparklines
Close Help
  • Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
  • Asset Store
  • Get Unity

UNITY ACCOUNT

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account
  • Blog
  • Forums
  • Answers
  • Evangelists
  • User Groups
  • Beta Program
  • Advisory Panel

Navigation

  • Home
  • Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
    • Blog
    • Forums
    • Answers
    • Evangelists
    • User Groups
    • Beta Program
    • Advisory Panel

Unity account

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account

Language

  • Chinese
  • Spanish
  • Japanese
  • Korean
  • Portuguese
  • Ask a question
  • Spaces
    • Default
    • Help Room
    • META
    • Moderators
    • Topics
    • Questions
    • Users
    • Badges
  • Home /
avatar image
2
Question by erebel55 · May 26, 2015 at 08:08 PM · c#damageattackdesign-patternsinterfaces

Hit and Damage System with interfaces

I have been trying to use the Single Responsibility Principle, interfaces, and abstract classes to write code with good structure.

The situation I am trying to code is as follows

Objects can be hit and not damaged (ex. a wall is hit but not damaged).

Objects can be hit and damaged (ex. player is hit and damaged).

So I created two interfaces.

 public interface IHittable
 {
     void Hit();
 }
 
 public interface IDamageable
 {
     void Damage(int damageTaken);
 }

So, in the player's case he can be hit and damaged.

Therefore, I thought a hit should drive the damage.

Which led to the following implementations of my interfaces.

 public class PlayerHit : MonoBehaviour, IHittable
 {
     IDamageable damageable;
     
     void Start()
     {
         damageable = (IDamageable)GetComponent(typeof(IDamageable));
         if (damageable == null)
         {
             throw new MissingComponentException("Requires an implementation of IDamageable")
         }
     }
     
     public void Hit()
     {
         damageable.Damage(/*how should I pass this in?*/);
     }
 }

 public class PlayerDamage : MonoBehaviour, IDamageable
 {
     Stats stats;
     
     void Start()
     {
         stats = GetComponent<Stats>();
     }
     
     public void Damage(int damageTaken)
     {
         stats.health -= damageTaken;
     }
 }

However, since everything that is hit isn't also damaged I didn't think it would be right to pass the damageTaken as an argument of Hit().

Therefore, how should I pass damageTaken to Damage()?

I thought about consolidating these two interfaces into one interface and only applying the damage if the object had health.

But I was hoping there was a better way?

Comment
Add comment · Show 4
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image Multifred · May 27, 2015 at 09:55 AM 2
Share

I think you should create a single script for your player, in C# you don't have multiple inheritance, but you can implement multiple interface!

 public class Player:$$anonymous$$onoBehaviour, IDamageable, IHittable
 {
     //your codes
     public void Hit()
     {
         int myDamage = 50;
         //animator trigger?
         Damage(myDamage);
     }
 
     public void Damage(int damageTaken)
      {
          stats.health -= damageTaken;
      }
 }

Edit: @erebel55 : mistake fix

avatar image Baste · May 27, 2015 at 10:15 AM 1
Share

We're doing something very similar in our game, but we have only one interface - IDamageReceiver.

I can see why you want to split out getting hit and taking damage, but really - is there every any instance where you would take damange but not get hit? Or, in strict program$$anonymous$$g terms - will you ever implement IDamageable but not IHittable?

I would just have the IHittable interface, and pass along the damage. Then the implementations that doesn't care about damage can just ignore that parameter. Yes, you will be sending in some unused parameters, which won't look very good, but honestly, that's not a very big deal.

Patterns are nice and all, but don't be a slave to them. Yes, getting hit and taking damage are different concepts, so if you really want to go all Single Responsibility Principle on that, sure, split them. I'd say that they're so intimately related that it should be one thing.

avatar image erebel55 · May 27, 2015 at 10:58 AM 1
Share

@$$anonymous$$ultifred I assume you meant

 public class Player : $$anonymous$$onobehaviour, IDamageable, IHittable

I didn't think it was possible to call an implemented function within the same class that is implementing it. I will test that.

Edit: it is possible, your code works okay. I guess I would need to get the current weapon's damage ins$$anonymous$$d of doing int myDamage = 50;

avatar image erebel55 · May 27, 2015 at 11:00 AM 0
Share

@Baste I have thought about this, whether they should be one concept or two. And I do think there are cases were you would want to implement IDamageable and not IHittable. For example, let's say there are restricted areas that a player is damaged if he goes to. Or maybe a Vampire is slowly damaged during the day if he is outside.

4 Replies

· Add your reply
  • Sort: 
avatar image
3
Best Answer

Answer by fafase · May 27, 2015 at 11:09 AM

I do not see any problem with your setup.

Your player is both hittable and damageable. Both interfaces are not relying on each others.

The Hit method is using the IDamageable object because it is meant to be damaged.

In the case of a wall, that can be hit but not damaged, your hit method will simply do something different.

 public void Hit(){
     // Apply hit texture but no damage
     // Anyway we have no IDamageable object in here
 }



Comment
Add comment · Show 2 · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image erebel55 · May 27, 2015 at 11:14 AM 0
Share

Right, my question was more how to get the damageTaken to Damage(int damageTaken) without passing it through Hit()

avatar image fafase · May 27, 2015 at 11:50 AM 2
Share

Well I kinda think interfaces are not meant to be used the way you do. The purpose of interfaces is to make classes blind to each other.

In your case, there is no real need as it is. It would take more of its purpose if you were turning things the other way.

At the moment, the player/enemy/wall gets the hit and performs all actions. So it is aware of all of its components and can then fetch and use. The interface is useless.

On the other hand if you grab the interface from the bullet and apply to it the damage the bullet contains, then your interface is valid.

It means the bullet checks if the hit object is IHittable and IDamageable (actually I would only use the second) and then call the method passing the damage. The bullet does not know what it is dealing with, only the method implements accordingly.

 // In player
 public void Hit(int damage){
     health-=damage;
     // Apply a hit
 } 
 
 // in Wall
 public void Hit(int damage){
    // Apply hit texture
    // no damage
 }
 
 
 // on the projectile, whether a tiny bullet or a huge one
 public int damage;
 void OnCollisionEnter(Collider col){
     IDamageable hit = (IDamageable ) col.gameObject.GetComponent(typeof(IDamageable )); 
     if(hit != null){
        hit.Hit(damage);
     }
 }

Now the interface is useful as the bullet is just doing its thing if possible. What happens next is none of its business.

avatar image
3

Answer by JoshuaMcKenzie · Apr 28, 2016 at 07:07 AM

what if you have a taser that deals no damage but still causes the player to play a flinching animation? what about if the player is in a poison cloud that doesn't stunlock the player but constantly damages them over time? by having Hit call Damage you're locking down the code.

what you would actually want to do is just have both Hit and Damage as separate calls. and even better in separate scripts. Hit() shouldn't call Damage(), nor should Damage() call Hit(). you'll want to keep them decoupled if you can.

have the triggering class just check if the script is IHittable and is IDamageable in separate if checks.

 public class Bullet :MonoBehaviour
  {
     int damage;
     void OnTriggerEnter(Collider other)
     {
         IHittable hitScript = other.GetCompnentInParent<IHittable>();
         IDamagable damageScript = other.GetCompnentInParent<IDamagable >();
 
         if(hitScript != null)
         {
             hitScript.Hit();
         }
 
         if(damageScript != null)
         {
             damageScript .Damage(damage);
         }
 
         Destroy(gameObject);
     }
  }

here the bullet is fully decoupled yet can still pass the info that's needed for the scripts catching it. if the bullet has a noticable effect on what it collided with, then it can use the Hit(). if the bullet can damage the target then it can call Damage(). they are two calls exclusive to each other and are only linked due to being called in the same onTriggerEnter event. while on the other hand the player script isn't programed specifically to handle the bullet, the two effects have been decoupled allowing all sorts of cool scenarios to come into play.

if you want your character be even more flexible then you'd write the handler scripts for IHittable and IDamageable into separate scripts as separate components. that way you can, during runtime, remove/disable/ or decorate(cool concept, look up decorator pattern when making buffs/debuffs) the IDamageable component to make the player invincible cause he picked up a power star. and when the powerup fades you re-enable/re-add/revert that IDamageable script back to normal.

Comment
Add comment · Show 1 · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image neitys_unity · Mar 17, 2021 at 06:45 AM 0
Share

more scripts and getcomponent calls? what could go wrong lol

avatar image
2

Answer by musicm122 · May 27, 2015 at 11:35 AM

I tend to think SRP in terms of game jobs I.E. Handing damage related functionality because I'm a bit lazy and because I tend to code with YAGNI. I'd just have one abstract class that deals with Health related stuff because that is as specific as I generally need to get. What I mean by this is that when you are dealing with Damage and hitting you are generally going to have to deal with much of the same logic. The use cases when hitting and damaging aren't overlapping is generally pretty small so I'd have them fall under the same responsibility.You could just possibly rejigger have Damageable inherit from the interfaces if you like but I really don't see a need to go that deep.

 public abstract class Damageable
 {
     public int HP;
     public Animator Anim;
     AudioSource audio;
     public AudioClip LameHitSound;
     public AudioClip HealSound;
     public AudioClip DamageSound;
     
     //maybe some cooldown timer logic, particle effects, damage sprites etc.. 
     
     public virtual void TakeDamage(int amt)
     {
         HP-=amt;
         if(HP<=0)
         {
             Death();
         }
     }
     
     public virtual void Hit(int amt)
     {
         if(amt==0)
         {
             //play sound effect for lame damage
             audio.PlayOneShot(LameHitSound, 0.7F);
             Anim.SetTrigger("TakeLittleDamage");
         }
         else if(amt<0)
         {
             //play effects and such for healing
             audio.PlayOneShot(HealSound, 0.7F);
             Anim.SetTrigger("Heal");
         }
         else
         {
             //shake screen, preform screen shake and other effects for damage
             audio.PlayOneShot(DamageSound, 0.7F);
             Anim.SetTrigger("TakeSolidDamage");
         }
     }
     
     public abstract void Death();
 }
 
 //and Inherit from it 
 public class PlayerHealth : Damageable
 {
     public override void Death()
     {
         //Call Game Over logic
     }
     
 } 
 //and override as you need
 public class EnemyHealth : Damageable
 {
     public override void Death()
     {
         //play death anim, set death variables
     }
 }
 
 public class BoxHealth : Damageable
 {
     void Start(){
         HP = 1;
     }
     public override void Death()
     {
         //Provide player with Item;
     }
     public override void Hit(int amt)
     {
         if(amt>0)
         {
             audio.PlayOneShot(DamageSound, 0.7F);
             Anim.SetTrigger("TakeSolidDamage");    
         }
     }
 }

I hope this helps.

Comment
Add comment · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image
1

Answer by Stardog · Apr 24, 2016 at 10:53 PM

Another option is to pass in an IDamageDealer (from the weapon) to the Hit function. Passing the damage amount alone maybe doesn't make sense conceptually, but passing in the thing that hit it does.

 public void Hit(IDamageDealer damageDealer)
 {
     damageable.Damage(damageDealer.Amount);

     Debug.Log(damageDealer.Name + "hit me for " + damageDealer.Amount + " damage");
 }

Also, look into the new UnityEvent variable. And sometimes it's just easier to use components for this type of thing.

Late answer, but an answer nonetheless.

Comment
Add comment · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users

Your answer

Hint: You can notify a user about this post by typing @username

Up to 2 attachments (including images) can be used with a maximum of 524.3 kB each and 1.0 MB total.

Follow this Question

Answers Answers and Comments

25 People are following this question.

avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image

Related Questions

Multiple Cars not working 1 Answer

Distribute terrain in zones 3 Answers

How to control damage output. 0 Answers

My Enemy Damage Script not working 1 Answer

Take health from enemy 3 Answers


Enterprise
Social Q&A

Social
Subscribe on YouTube social-youtube Follow on LinkedIn social-linkedin Follow on Twitter social-twitter Follow on Facebook social-facebook Follow on Instagram social-instagram

Footer

  • Purchase
    • Products
    • Subscription
    • Asset Store
    • Unity Gear
    • Resellers
  • Education
    • Students
    • Educators
    • Certification
    • Learn
    • Center of Excellence
  • Download
    • Unity
    • Beta Program
  • Unity Labs
    • Labs
    • Publications
  • Resources
    • Learn platform
    • Community
    • Documentation
    • Unity QA
    • FAQ
    • Services Status
    • Connect
  • About Unity
    • About Us
    • Blog
    • Events
    • Careers
    • Contact
    • Press
    • Partners
    • Affiliates
    • Security
Copyright © 2020 Unity Technologies
  • Legal
  • Privacy Policy
  • Cookies
  • Do Not Sell My Personal Information
  • Cookies Settings
"Unity", Unity logos, and other Unity trademarks are trademarks or registered trademarks of Unity Technologies or its affiliates in the U.S. and elsewhere (more info here). Other names or brands are trademarks of their respective owners.
  • Anonymous
  • Sign in
  • Create
  • Ask a question
  • Spaces
  • Default
  • Help Room
  • META
  • Moderators
  • Explore
  • Topics
  • Questions
  • Users
  • Badges