Wayback Machinekoobas.hobune.stream
May JUN Jul
Previous capture 12 Next capture
2021 2022 2023
1 capture
12 Jun 22 - 12 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
0
Question by ovunctuzel · Mar 09, 2018 at 09:19 PM · ooppolymorphism

Does polymorphism apply here?

I am brushing up on my OOP knowledge and want to employ good practices in my Unity project. Let's think about an example scenario:

  • Damage dealing objects have a component that inherits from an OnHit class.

  • A fire sword has a OnHitFireSword component, that burns the enemies for example.

  • The enemies will have a function that takes an OnHit (the base class) object as an argument.

This way I only have one simple function in my enemy script, and each damage dealing object has their own component that handles specific interactions. Does this approach make sense?

In my previous project I had different tags attached to my damage dealing objects, and the enemy script had a separate if statement for each collider tag, which got out of hand very quickly.

Comment
Add comment
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

1 Reply

· Add your reply
  • Sort: 
avatar image
0

Answer by Xarbrough · Mar 10, 2018 at 02:44 AM

I would think about the system like this:

  • There are damageable objects, which store data to indicate their health or related properties, e.g. players, NPCs or destructible environment objects. These do not know anything about weapons.

  • There are weapons ('damage dealers' maybe?), which apply damage points or related effects to the damageable objects. Hence, weapons know about their damaging effects and the objects which to affect.

In a simple case, this looks like this:

 public interface IDamageable
 {
     void ChangeHealth(int amount);
 }
 
 public class PlayerHealth : MonoBehaviour, IDamageable
 {
     private int healthPoints;
 
     public void ChangeHealth(int amount)
     {
         healthPoints += amount;
     }
 }
 
 public class DamageDealer : MonoBehaviour
 {
     public int damage = 5;
 
     private void OnTriggerEnter(Collider other)
     {
         IDamageable damageable = other.GetComponent<IDamageable>();
         if (damageable != null)
         {
             damageable.ChangeHealth(-damage);
         }
     }
 }


A lot of situations can be modelled this way. If the weapon applies fire damage over time, it could either store a reference to all affected entities and apply more damage to them at every tick, or it could add a special kind of object to the affected IDamageable which ticks on its own (think poison). These decisions depend on the needs of your game.

A fire sword know how to apply its effects, this makes sense, however enemies should not know about damage dealers. It may be ok to abstract this by using only a single abstract base class, but then why write this code in the enemy class if it's only ever going to call onHit.DoSomething()? Instead, let the weapon call whatever code it needs through the interface of the affect IDamageable. This gives more flexibility.

For example:

 public interface IDamageable
 {
     void ChangeHealth(int amount);
     Transform GetEffectAttachPoint();
 }
 
 public class PlayerHealth : MonoBehaviour, IDamageable
 {
     private int healthPoints;
 
     public void ChangeHealth(int amount)
     {
         healthPoints += amount;
     }
 
     public Transform GetEffectAttachPoint()
     {
         return transform;
     }
 }
 
 public class FireSword : MonoBehaviour
 {
     public int damagePerSecond = 1;
     public float burnDuration = 3f;
     public GameObject particleFXPrefab;
 
     private List<IDamageable> affectedEntities = new List<IDamageable>();
 
     private void OnTriggerEnter(Collider other)
     {
         IDamageable damageable = other.GetComponent<IDamageable>();
         if (damageable != null)
         {
             damageable.ChangeHealth(-damagePerSecond);
             Instantiate(particleFXPrefab, damageable.GetEffectAttachPoint());
             affectedEntities.Add(damageable);
         }
     }
 
     private void Update()
     {
         // This is totally wrong, but you can do this correctly
         // by looking through all entities and counting down a timer for each one
         // then applying damage every x seconds or a small portion of the damage each frame etc.
         foreach (var entity in affectedEntities)
             entity.ChangeHealth(-damagePerSecond);
 
         // Remove the entities from the list when their timers are done.
     }
 }


If this works, no reason to change it. However, your game might need to separate each component into even more components. For example, if the sword gets destroyed, the burning effect should continue. In this case, we need to separate a StatusEffect object from the weapon:

 public interface IDamageable
 {
     void ChangeHealth(int amount);
     Transform GetEffectAttachPoint();
 }
 
 public class StatusEffect : MonoBehaviour
 {
     public int damage;
     // per second etc.
     public GameObject particleFXPrefab;
 
     private void Update()
     {
         // every x seconds
         GetComponent<IDamageable>().ChangeHealth(-damage);
 
         // if timer is done
         Destroy(this);
     }
 }
 
 public class PlayerHealth : MonoBehaviour, IDamageable
 {
     private int healthPoints;
 
     public void ChangeHealth(int amount)
     {
         healthPoints += amount;
     }
 
     public Transform GetEffectAttachPoint()
     {
         return transform;
     }
 }
 
 public class FireSword : MonoBehaviour
 {
     public StatusEffect statusEffectPrefab;
 
     private void OnTriggerEnter(Collider other)
     {
         IDamageable damageable = other.GetComponent<IDamageable>();
         if (damageable != null)
         {
             GameObject go = damageable.GetEffectAttachPoint().gameObject;
 
             // Apply the effect.
             // Here, I simply add a new component and copy the values from a prefab.
             // Later, the effect can remove itself.
             var effect = go.AddComponent<StatusEffect>();
             effect.damage = statusEffectPrefab.damage;
             effect.particleFXPrefab = statusEffectPrefab.particleFXPrefab;
 
             // Alternatively, you can create an EffectRunner component, which
             // takes some e.g. ScriptableObject data as input to add, update and remove after a set time.
         }
     }
 }

There are a lot of possible variations to these systems, especially if there are multiple effects involved. I've once created a system where each player (PlayerHealth component) also had a StatusEffectRunner, which was a class that simple holds a list of effects which need to be updated. Each effect can remove itself again via a delegate that was passed when the effect was added.

The takeaways are:

  • Entities hold data, e.g. health or visuals, which represent a state and do not know anything about other system's logic like weapons.

  • Weapons are active systems which access other entities through an interface (so that a fire sword can potentially deal damage to a brick wall one day).

  • Interfaces are contracts of functionality. This can be misleading. If you say "Damageable.ApplyStatusEffect(effect)", it looks like the damageable object fullfills a contract of the functionality of being able to have a status effect, but this would mean that the damageable knows what to do with the effect, which it shouldn't, because it would have to change everytime a new effect is added. Instead we can invert the dependency and let the interface provide functionality, which belongs in its domain, e.g. "ChangeHealth" or "GetVisualAttachPoint". You can argue about if those two examples even belong together, or both should be separate, because one is "real state data", whereas the other is only part of the visualisation of such state.

Coming back to the original question: I don't thank that polymorphism is the solution to your problem. Instead, use components and well-designed dependencies. You can still use base classes to share common functionality like StatusEffect - FireEffect/PoisonEffect.

These were just some ideas based on my previous experiences in projects, but hopefully they fit your use-case.

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

75 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 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 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

An OS design issue: File types associated with their appropriate programs 1 Answer

How should I structure my unit class relationships? 1 Answer

Polymorphism issue 3 Answers

RPGStatSystem tutorial problem with clases visibility? 1 Answer

Inherit from a behaviour (JS) 2 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