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

Component-based weapon system

Discussion in 'Editor & General Support' started by vexe, Oct 13, 2013.

  1. vexe

    vexe

    Joined:
    May 18, 2013
    Posts:
    644
    Hi, I wanted to reopen/revive this question with a discussion with Mr.Jamora about his answer, cause I think it's really neat and better than mine. Here's what I wanted to comment: (But obviously, this is gonna be an open discussion, and UA isn't well suited for that):

    "Hey @Jamora, development on my game is currently on hold, cause I've been only using object-hierarchy designs all over the place, and I really got a bad feeling about it if I keep doing it that way. So now I'm more leaning towards component-oriented design. I'm still reading and learning about it, trying to reshape my way of thinking cause I come from a pure OOP environment. I thought OOP was the right way to go with Unity, but it seems not.

    The more I look at your solution, the more I like it. (correct me if I'm wrong) but you're treating interfaces as components?
    So like a concrete class like `SMG` would have 2 components, `IReloadable` and `IShootable` for example. This is actually very neat and new to me, I mean, when I hear `components`, I think of stuff being attached to a `gameObject` (Scripts and whatnot)

    But your approach is a bit different, you're attaching components (interfaces) to classes, to make new stuff. So now, if I wanted my `gameObject` to be an `SMG`, I don't have to attach it the `Shooter` and `Reloadable` scripts (pure components, not interfaces), all I do is create a concrete class `SMG` that implements `IShootable` and `IReloadable` and attach `SMG` to my gameObject.

    I wonder if that would cause any problems in the future?

    The problem with object-hierarchy, let's say for example you had:

    GameObject
    - Static
    -- Tree
    - Dynamic
    -- Enemy
    -- Player

    Now what happens if I wanted to make an `EvilTree`? A tree that does `Enemy` stuff, but at the same time it's `static`.

    So I wonder if it's better to use your system, and go all interfaces, or pure component-oriented approach, (like if I wanted to make an `SMG`, I would attach a `Shooter` and `Reloadable` scripts to my object)

    What do you think? what would you choose, and why? (advantages/disadvantages of each)"

    I really like his solution, but I haven't tried it yet. I also want to know the difference between his approach, and the normal component-based approach (like mentioned at the end of my comment)

    So what do you think, community, about how should a weapon system be implemented? and what do you think of our two solutions?

    Thanks.
     
    Last edited: Oct 13, 2013
    erebel55 likes this.
  2. konkol

    konkol

    Joined:
    Apr 6, 2011
    Posts:
    33
    I think you should write down statistics of your weapons in a table and realised that you can simple keep this table in memory and refer to it whenever you need to know what are stats of a weapon.
     
  3. Yoska

    Yoska

    Joined:
    Nov 14, 2012
    Posts:
    188
    Interesting. I have never really found a good use for interfaces. I try to use them once in a while but eh. Probably I'm just not understanding something about them. Perhaps pure component-oriented approach is more crude but it gives you better tools to manipulate and inspect properties of game objects within editor.
     
  4. Deleted User

    Deleted User

    Guest

    Well is not an easy question to answer, but I can give my view on it. In your example you said to have a SMG component, so I guess you are using specific components for each weapon, in this case I think interfaces can be a good thing and save the hassle to rewrite almost identical code from other weapon scripts.

    As for me I don't use this approach, I try to be more modular as possible, I use one component for all weapons simple called WeaponBehaviour, then in it I do all the calculations based on spread, rate of fire etc... changing the values in editor for each weapon. As you I try to minimize as possible complicate hierarchy.

    For ammo related functions I pass values trough a static function and see i is time to reload or if a weapon can't shoot anymore, probably not the best idea but I'm good with it.

    Inherit interfaces can be good, but you have to see if is what you really need it in this case (I don't think is a matter of better approach, but what you are more confortable with). Again based on your example could be a good approach for weapons. For enemies well, I'm not sure.
     
  5. vexe

    vexe

    Joined:
    May 18, 2013
    Posts:
    644
    The only problem I think with composition over inheritance (Mr.Jamora's approach) is the fact that you might have your guns have the same implementation, to a method in lets say 'IReloadable' - Let me illustrate:

    Code (csharp):
    1.  
    2. public interface IReloadable
    3. {
    4.     void Reload();
    5. }
    6.  
    7. public interface IShooter
    8. {
    9.     void Shoot();
    10. }
    11.  
    12. public class SMG : IShooter, IReloadable
    13. {
    14.     public void Shoot()
    15.     {
    16.           // shoot logic 1
    17.     }
    18.     public void Reload()
    19.     {
    20.           // reload logic 1
    21.     }
    22. }
    23.  
    24. public class AssaultRifle: IShooter, IReloadable
    25. {
    26.     public void Shoot()
    27.     {
    28.           // shoot logic 2
    29.     }
    30.     public void Reload()
    31.     {
    32.           // reload logic 1
    33.     }
    34. }
    35.  
    Both the SMG and the assault rife, have the same Reload logic, since they both have to implement the Reload method, there's gonna be code duplication, copy-paste the implementation. (Which you wouldn't normally have, with normal inheritance) - imagine more IReloadables, more mess.... very quickly... you got the picture. I could gather similar logic in a class, but what's the point if you're gonna have to inherit again? we're trying to escape the rigidness of structure that comes from inheritance.

    See the wiki.
     
    Last edited: Oct 13, 2013
  6. Jamora

    Jamora

    Joined:
    Mar 5, 2013
    Posts:
    9
    Interfaces ARE components. Though I prefer to call them behavior. My University professor called them roles. However you call them, they can be used to make objects share behavior, but not implementation. Inheritance forces objects to share both.

    As I mentioned in my UA answer's last paragraph, using interfaces doesn't exclude using inheritance. If I were to implement a weapon system, I would probably end up using both mechanics: implement all appropriate interfaces in the highest base class and then inherit.

    The way I've learned interfaces and inheritance is that interfaces define roles or behaviors whereas inheritance only maintains (or extends) logic. If all child classes share the same behavior, the base class should implement the appropriate interface. If they additionally share the exact same implementation, then the base class should also provide the implementation.

    Let's say we have an inheritance tree like that in Vexe's answer in UA, with the addition of an umbrella in the melee-side of the hierarchy. I would have

    Code (csharp):
    1. public abstract class Weapon: MonoBehaviour{
    2.     /*variables for damage etc.*/
    3.     public abstract void UseWeapon();
    4.     protected void PlayAnimation(Animation animationToShow){/*animation stuff*/}
    5. }
    6.  
    7. public abstract class FireArm : IReloadable, IShootable{
    8.     [SerializeField]//added to all variables to show them in inspector
    9.     protected int reloadTime;
    10.     protected int range;
    11.     protected AnimationClip reloadAnimation;
    12.  
    13.     public abstract void Reload(); //because SemiAuto and Automatic weapons have different reload implementation
    14.     public void Shoot(){
    15.         PlayAnimation(reloadAnimation);
    16.         UseWeapon();
    17.     }
    18. }
    19.  
    and so on and so forth, but because we have a cool game with umbrellas that can shoot in addition to pummel people, we'd be in real trouble if we weren't using interfaces: we couldn't put the shooting umbrellas in the firearm side, because it also has melee behavior and vice versa. However, with interfaces the umbrella class could be in either, as long as the missing behavior is added

    Code (csharp):
    1.  
    2. public class Umbrella : Melee, IShootable{
    3.     protected int range;
    4.     public void Shoot(){
    5.         PlayAnimation(reloadAnimation);
    6.         UseWeapon();
    7.     }      
    8. }
    9.  
    I will admit that the two Shoot methods are exactly the same, but I consider that to be the lesser evil between copy-paste and having behavior that an object shouldn't have. I also think the fact that the example is rather simple has something to do with it. (Do we really need a Shoot-method when we already have UseWeapon?). If the copied code is generic in nature, maybe it should be in a static helper/utility class.

    The true power of interfaces, in my opinion, comes in cases where behavior is shared between different hierarchies. Let's extend our Weapon example to a full-blown game where you can not only shoot people with weapons, but construct your own base. In your base, you could have defensive turrets, shops and maybe AI soldiers. Of those, you could upgrade your shops and turrets.

    Now, if we have a generic getter method for interfaces (luckily, we do), we could just point our cursor at things and do a simple check: if the thing I'm pointing has an IUpgradeable, then upgrade. Doesn't matter if it's a shop, a handgun or a turret because its role is to be upgradeable. Thus, it will have an implementation for an Upgrade method.

    Or consider a game where not only units can be damaged, but also buildings, trees and plants, but not rocks. Imagine then an AoE spell, "firestorm" going off in a forest. How to get all units, buildings, plants and trees while keeping the rocks and fire-immune creatures unharmed? Easy, Physics.OverlapSphere to get all GameObjects, then see which have an IDamageable script. Finally call the Damage(int amount, DamageType element) -method of those scripts. Each fire-immune creature would then, in their internal implementation discard (or heal, or whatever) the incoming fire-type damage.

    The same way you could have Static (trees) and Dynamic (Player, Enemy) objects:
    Code (csharp):
    1.  
    2.  
    3. public interface ITargettable{
    4.     void Target();
    5. }
    6. public interface IDamageDealer{
    7.     int DealDamage();
    8. }
    9. public interface IDamageReceiver{
    10.     void Damage(int amount);
    11. }
    12. public interface IEnemy : ITargettable, IDamageDealer, IDamageReceiver{
    13.     void Update();
    14. }
    15.  
    16. public interface IMovable{
    17.     void Move();
    18. }
    19.  
    20. public abstract class Dynamic: MonoBehaviour, IMovable{
    21.     /*All the logic a dynamic object needs. Perhaps related to moving?*/
    22. }
    23.  
    24. public class Enemy : Dynamic, IEnemy{}
    25.  
    26. public class EvilTree : Static, IEnemy{}
    27.  


    What I explained above was the standard software development -way. We all know Unity is a little special. While I've never implemented my suggestion #3 in this UA post, I think this might be the most Unity-friendly way to have this component based approach without using interfaces.

    Basically you have an abstract base class that defines a behavior instead of an interface. That base class is then inherited to each particular behavior and, using the Command pattern, the correct method of a behavior can be called.

    EDIT:
    Added more examples.

    Using interfaces can sometimes lead to copy-paste code. However, what gets copied is usually very generic code, which could be put into a static utility/helper class
     
    Last edited: Oct 13, 2013
    erebel55 likes this.
  7. minionnz

    minionnz

    Joined:
    Jan 29, 2013
    Posts:
    391
    Here's a question hopefully someone more experienced than I can answer -
    Is there any performance penalty attaching multiple monobehaviours (assuming that there is no FixedUpdate/Update method in these behaviours - they're there simply to be referenced by another script) vs using interfaces/classic OO.

    I had guessed that there is some kind of performance hit when using classes derived from MonoBehaviour, but I've never actually bothered to find out. Does Unity do some kind of optimisation to check whether a MonoBehaviour actually hooks into the Update/GUI etc life cycle?
     
  8. minionnz

    minionnz

    Joined:
    Jan 29, 2013
    Posts:
    391
    I've been thinking about this a bit and thought I'd try messing around in code - and I've come up with something like this:
    $snipbehaviour.JPG

    The idea is that Shootable and Reloadable are both public properties on the WeaponBehaviour script. The property types are IShootable and IReloadable respectively. Both properties are decorated with a custom "AttachableAttribute" which tells unity to use a custom editor

    The custom editor for each of these types finds any classes that inherit from IShootable/IReloadable and displays these in a drop down - which can be changed in the inspector panel. This allows you to easily switch out QuickReload to SlowReload for example.

    I don't think I'd implement it like this though. I can't think of any advantage this way has over something that uses separate MonoBehaviours + custom editors (ie: a single Shootable that can be customised, rather than a hierarchy of unique Shootables).

    Another example would be a set of chess pieces - I'd design it like this:
    Code (csharp):
    1.  
    2. ChessPiece
    3.   - Property Enum ChessPieceType
    4.   - CanMove(position)
    5.   - MoveTo(position)
    6. BoardLogic
    7.   - GetPossibleMovementsFor(piece)
    8.   - IsEnemyAtPosition(position)
    9.  
    Rather than using inheritance.. Is it the best way? I don't know - but it's a lot easier converting a piece from Pawn to Queen by switching the enum, rather than going through the whole drag/drop behaviour process.
     
  9. vexe

    vexe

    Joined:
    May 18, 2013
    Posts:
    644
    @Jamora man you're awesome! :D - Your answers are great! Unfortunately for me I haven't continued my formal education, so some of what you mentioned are missing from my toolbox (like the Command pattern) - I will take the time now to study and fully comprehend what you just told me, try out all the approaches and give you my feedback. Thanks a lot for your time man!

    However, since I'm learning as I go (self-learner) - things could go wrong, I could easily follow a wrong direction since I'm going on my own without any guides. I'm currently trying to learn about design patterns, there are obviously some of them that are not related to game programming.

    Could you be so kind to tell me what are the most used patterns (you use for example) in game programming, that I should pay heavy attention to?
    I have found some really great sources to learn patterns, dimecasts.net and yesterday I just found gameprogrammingpatterns (which I linked to my singleton answer) - Thanks again man.

    @minionnz about your performance query, I think it's best to ask in a separate thread, because this is not related so much to the subject of this thread. And your custom inspector stuff looks cool, thanks.

    I don't think enum would suffice, as each piece has its own characteristics (material points, ways of attacks, etc)
    Since they all do the same stuff (move, attack, have material points, etc) and don't have different behaviours, I don't think you'd need interfaces for extra behaviours. Just a base Piece abstract class, with abstract Move, CanMove, etc methods, implemented in custom ways by Pawn, Queen, Rook, etc.
     
    Last edited: Oct 14, 2013
  10. Jamora

    Jamora

    Joined:
    Mar 5, 2013
    Posts:
    9
    Not directly related to implementing component based weapon systems, but since you asked.

    The most used guideline is (or should at least be) KISS: Keep It Simple, Stupid. Meaning you make the simplest possible solution that works for the current needs as opposed by implementing a fancy design pattern straight away. There are two main reasons why KISS is good. 1) You get things done. 2) Your code is only as complex as it needs to be.

    The second important would be SOLID, which stands for
    • Single Responsibility principle (SRP)
    • Open Closed principle (OCP)
    • Liskov Substitution principle (LSP)
    • Interface Segregation (IS)
    • Dependency Inversion (DI)
    There're wikipedia pages on all of these.

    SRP means that a class should have one thing it's responsible for, and one thing only. This helps in tracking down and preventing bugs because you only have one logic to implement per class. Additionally If something does go wrong during gameplay, it's easier to figure out what logic was wrong and then zone in on the correct class. You'll know if you're following this rule when you ask yourself "What is the responsibility of this class?" If the answer contains even one AND, you better have a good explanation (to yourself) why.

    OCP means that a class should be open for extension, but closed for modification. This basically means, if you want more functionality in a class, you extend it instead of go poke around in the code, potentially creating bugs where there were none before. Unity3D breaks this rule by having most of their central classes sealed.

    Liskov substitution means that an extended class should do at least as much as the base class. Let's say you have a math class that computes the square root of positive numbers to 5 decimal points. Following the OCP, people are free to extend the class, but LSP dictates, that because the base class computes square roots of positive numbers to 5 decimals, any class extending our base class must do the same. Our own class may compute square roots of negative numbers, or up to 10 decimal precision, but it is not allowed to compute only numbers 0-100 or only 4 decimal precision. Because any end user may be using our class as the base class, the same restrictions must apply.

    Interface Segregation is the SRP for interfaces. Instead of a massive interface with lots of behaviors, it's better to have lots of interfaces with very segregated, or specific, function. That way you can combine the needed interfaces to create just the object you need, instead of having lots of functionality the object shouldn't logically have.

    Dependency Inversion is the notion of having a class always depend on a higher abstraction. The abstraction hierarchy is
    • Interfaces
    • Abstract Classes
    • Concrete Classes
    The idea here is that the more abstract a class is, the less pressure there is for it to change. We all know that changing implementation always causes bugs. So, we're trying to keep the bugs in the concrete classes, where the implementation is, instead of the abstract classes, which are reserved for the program logic flow.

    "Program to interfaces, not implementation." is a mantra I find myself saying at times, and it is Depencendy Inversion at its core. It allows us to change the implementation of any less abstract class during runtime, because any dependency is not to the actual implementation, but merely to what role a class has promised to implement.

    Not following these guidelines can, and often do, lead to code smells (a.k.a design smells)

    As for the actual design patterns by the Gang of Four you asked for. I often find need for the Strategy pattern. I've once used the Facade when working with a database.

    The strategy pattern (which I often call Command and I shouldn't) is when you have a class that acts as the context for another class. In the link in my previous post, my suggestion #3 is in fact the Strategy pattern. We have the class Enemy that acts as the context, as it has the abstract Mover and other attributes of that class as fields. Those abstract classes can be changed strategically, according to the situation. An example:

    Let's consider a GameObject with the following Components:
    -public class Enemy : MonoBehaviour
    -public class SineMover : AbstractMover (extends MB)
    -public class EdgeBouncer : AbstractEdgeReacter (extends MB)


    Now, we could have our Enemy class

    Code (csharp):
    1.  
    2. public class Enemy : MonoBehaviour{
    3.      public AbstractMover mover;
    4.      public AbstractEdgeReacter edgeReacter;
    5.  
    6.     void Update(){
    7.         mover.Move(direction);
    8.     }
    9.  
    10.     //possibly called by the collision event on the Edge.
    11.     public bool ReactToEdge(Edge edge){
    12.         edgeReacter.React(edge);
    13.     }
    14.  
    15.     //Possibly called on the second hit to an edge.
    16.     public void ModifyMovingBehavior(AbstractMover newMover){
    17.         Destroy(mover);
    18.         mover = gameObject.AddComponent(newMover);
    19.     }
    20. }
    21.  
    The variables are uninitialized because the components are supposed to be dragged and dropped to the appropriate fields in Enemy. Then, whenever appropriate, you can change the moving behavior and the Enemy class doesn't care. It's programmed to the interface of AbstractMover so it doesn't need to care about the implementation and just keeps calling Move() every Update. It's worth noting that any outside class should only know of the existance of the Enemy class. There isn't (AFAIK) any way to make private Components on a GameObject. (It's possible to hide Components, which isn't what I mean.)

    The Facade is simply a class that provides a front for actions the class should do, but shouldn't really have access to the needed resource. Like databases. Any other class shouldn't know how the data is stored; it should just know how to access it. So instead of directly accessing the database from each class that needs to have access, you provide a singular point of entry (possibly a static class). It's easier to maintain too. Especially if the database is changed to another storage system, say XML files, you'll be glad you only need to rewrite one class instead of search the codebase for all accesses to the old database.

    Phew, this post turned out to be a doozie. Seems like I just reiterated half of the Designing Object Oriented Software course.
     
  11. vexe

    vexe

    Joined:
    May 18, 2013
    Posts:
    644
    I actually feel guilty, in my previous post I asked @minionnz to ask his performance question in separate thread cause it was not directly related, but then I asked you about patterns, which also isn't directly related. I'm sorry I should have done that myself as well. (I actually did that to get an answer from you lol but I could have easily posted in a separate thread and gave you the link..... :/)

    Thanks a lot Jamora, couldn't have been written better. I recently went over those SOLID principles from the dimecasts link I posted above, your explanation helps me understand better now.

    About your weapon example (trying to get back on course lol), I think you forgot to make Firearm and Umbrella inherit Weapon.

    From this point on, I will work on a component-based weapon system and post my updates here.
     
    Last edited: Oct 14, 2013
  12. AndrewGrayGames

    AndrewGrayGames

    Joined:
    Nov 19, 2009
    Posts:
    3,821
    That sounds like some sort of database. Hmm.
     
    LMan likes this.
  13. BFGames

    BFGames

    Joined:
    Oct 2, 2012
    Posts:
    1,543
    On our game Gunjitsu (gunjitsugame.com) we use a component based system, and i works really well.

    We got components such as player damage, exploding, spiral movement, homing, bouncing, spread and much more. In all we got 29 modifiers at the moment that can all work individually of each other. All they got in common is a single projectile script that handles all the basics (is it a physical bullet, raycast or something else)

    With this system we can simply just drag over new modifiers and create new types of weapons in only a few minutes. We got over 20 weapons now ;)
     
  14. PhobicGunner

    PhobicGunner

    Joined:
    Jun 28, 2011
    Posts:
    1,813
    Here's the particular approach I'm taking for a first person shooter.

    I'm going with the component based system. A weapon is split into the following components:
    • The WeaponController script
    • A script which subclasses from TriggerController
    • A script which subclasses from ProjectileController
    • A script which subclasses from AmmoController
    The WeaponController is sort of the main script which manages the other components of a weapon.
    The TriggerController is responsible for raising an event whenever a bullet should be fired (atm I have SemiAutoTriggerController, FullAutoTriggerController, and BurstFireTriggerController)
    The ProjectileController is responsible for actually firing off a projectile (such as a hitscan, spawning a physical object, etc).
    The AmmoController is responsible for keeping track of the weapon's ammo, performing reloads, and determining if a bullet can currently be fired (for instance, a bullet can't be fired if the weapon is in the middle of reloading). I currently have SimpleAmmoController (quake 3 style ammo), ClipAmmoController, ShellAmmoController (shotgun-style where each shell is reloaded individually), and EnergyBasedAmmoController (weapon has some amount of energy which is expended with each shot, and recharges over time. if the weapon hits zero energy, you have to wait for it to cool off before you can fire it again)

    There's a few reasons I prefer this over interfaces. With interfaces, you may find yourself reimplementing a lot of logic with each weapon class (since an interface cannot implement any logic itself). But for components, each piece handles its own logic independently.
    It's a lot more designer friendly as well. You can create a new weapon just by dragging and dropping pieces into the inspector and changing their values, whereas for interfaces you'd have to create a new weapon class for every new weapon, meaning you have to involve a programmer.

    EDIT: Woops, just realized how old the thread was....
     
  15. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,614
    Then that implementation shouldn't be an interface, it should be a composite. Make a separate component called "GunReloader" or whatever, and instead of implementing the interface also attach that GunReloader component to the same GameObject.

    Use the right tool for the right job.
     
  16. papakaliati

    papakaliati

    Joined:
    Nov 13, 2014
    Posts:
    1
    I am sorry, but all the answers here are quite bad on the use of interfaces and composition.
    What you are looking for is actually constructor injection .

    That means, that you actually have a class implementing the shooting interface, lets say

    1. public interface IReloadable
    2. {
    3. void Reload();
    4. }

    5. public interface IShooter
    6. {
    7. void Shoot();
    8. }

    9. public class Shoot1: IShooter
    10. {
    11. public void Shoot()
    12. {
    13. // shooting logic 1
    14. }
    15. }

    16. public class Shoot2: IShooter
    17. {
    18. public void Shoot()
    19. {
    20. // shooting logic 2
    21. }
    22. }
    Now the implementation of the Weapon class looks like this

    1. public SMG
    2. {
    3. private IShooter shoot;
    4. private IReloadable reload;

    5. public SMG( IShooter shoot, IReloadable reload)
    6. {
    7. this. shoot = shoot;
    8. this.reload = reload;
    9. }

    10. public Shoot()
    11. {
    12. shoot.shoot();
    13. }
    14. }
    new SMG( new shoot1(), new reload())

    The point is that when you create the SMG class doesn't care which shoot implementation you have. It will work with either the Shoot1 or Shoot2.

    Now if you want to add a new SMG with slower reload, or harder hitting, then only thing you need to do is create a Shoot3() class
    and by using
    new SMG( new shoot3(), new reload())
    you have a complete new SMG, without affecting the original code at all.
     
    Bunzaga likes this.
  17. Steamroller

    Steamroller

    Joined:
    Jan 2, 2013
    Posts:
    71
    This is a really interesting concept. I really like the idea of separating out the functionality into reusable chucks that can be pieced together to create new entities regardless of the object inheritance hierarchy. It looks like there are a couple of different approaches being discussed here: Components and Mixins. I've been playing around with getting both types of systems working so i can compare them to one another. Mixins are definitely the harder to implement because of the limitations of language (ie. no multi-inheritance) but I've been able to get around that with using extension methods on the interface as described here.

    So i have a bunch of classes now that have functions like Shoot() and Reload(), but how do you ties these thing together? Does the Shootable class have to call the Reloadable class when its out of ammo? What if something isn't reloadable? Does it just keep trying everytime you try and shoot? If there is another class that controls the ammo, how does the Shootable class have to consult that? It seems like there is going to be a lot of cross referencing between these classes. Everytime i add some new functionality, I'll have to go into a lot of these class and make sure that its integrated. This doesn't seem very elegant. The other way I've thought about it was to use some sorta event system. Like having events for things like fire and reload, and the reload class listens for fire events. How would the shoot class know if it has ammo in this type of system?

    I guess I'm just looking for some guidance on how you would plumb these things together.
     
  18. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    This is an old thread. There are a ton of new concepts and tools that Unity has introduced since then, including UnityEvents and the associated ExecuteEvents. Unity is much more friendly towards direct use of interfaces now.

    @Steamroller my general approach is to keep dependencies as high up the chain as possible. You can document dependencies, and make Unity enforce them, with RequiresComponent.

    In your gun example, I would likely include an ammo component, a fire component, and a reload component. The fire component is dependent on the ammo component. The ammo component might depend on the reload component and so forth.

    I did a video on the idea here

    http://youtube.com/watch?v=1YGVP6wsxj0
     
  19. frosted

    frosted

    Joined:
    Jan 17, 2014
    Posts:
    4,044
    Just looking over this stuff, I'd tend to lean towards @PhobicGunner's approach.

    The difference is that he has an actual reason for the separation between various types. Different triggers, truly different kinds of ammo (recharge vs reload) etc.

    The difference between IShootable vs ProjectileController might seem mostly semantic (and it probably is), but Phobic's approach actually ends up mapping to input/output in his design instead of some of the more abstract examples.

    TriggerController - this responds to direct player input
    ProjectileController - converts player input to game world output
    AmmoController - rules check for player input

    This kind of approach (mapping the abstraction against direct, clear input/output) is just going to work better and lead to better decisions than more 'conceptual' approaches.
     
  20. mahdiii

    mahdiii

    Joined:
    Oct 30, 2014
    Posts:
    856
    I don't implement several interfaces for a class like SMG : IShooter, IReloadable .Instead I implement one by one like class CShooterBehaviour1:IShooter and CReloadBehaviour1:IReloadable. then I can easily create a class like smg and add them as fields.it is composition over inheritance
    Code (CSharp):
    1. public class SMG:Monobehaviour{
    2. IReload m_reloadBehaviour;
    3. IShooter m_shootBehaviour;
    4. void Start(){
    5. m_reloadBehaviour=Getcomponent<IReload >();
    6. m_shootBehaviour=Getcomponent<IShooter >();
    7. }
    8. }
    public class CReloadBehaviour1:Monobehaviour,IReload{
    void Reload(){}
    }
    public class CShootBehaviour1:Monobehaviour,IShoot{
    void Shoot(){}
    }[/code]
    but it is better to inherit from one abstract base class at first.
     
  21. mysticfall

    mysticfall

    Joined:
    Aug 9, 2016
    Posts:
    649
    I know it's a necroed thread, but I felt the need to comment on the design decision as I think I understand the reason behind the original approach.

    The thing is, OOP and component oriented design are not mutually exclusive concepts, so the most of the principles of the former still hold if you take the latter approach. What the latter is really opposed to is extending a concept by making a deep concrete class hierarchy, not the whole concept of OOP design.

    As such, even when you take the component based approach, meaning of your classes should be recognizable by looking at its class hierarchy, if you follow OOP principles.

    For instance, you can actually 'read' a type signature like 'SMG : MonoBehaviour, IShootable, IReloadable' as 'SMG is a MonoBehaviour object(whatever that is), with which you can shoot something and reload ammo'. It's important because it acts as a some kind of a contract so that other classes can rely on to interact with it.

    On the other hand, if you choose to make SMG not implementing any such interfaces, and instead making it declare such properties m_reloadBehaviour or m_shootBehaviour, there's no way to reload a SMG in the same way as you can do for a Pistol class, because even though both of them might have an IReloadable property with the same name, you can't use that information without casting or reflection, which is a bad OOP design (or a violation of 'Liskov Substitution Principle').

    The whole point of the 'composition over inheritance' approach is to minimize duplicated code, and to avoid the limitation of such languages which does not support multiple inheritance or mixin, and it can be still achieved if you make your SMG class implementing multiple behavioral interfaces like IShootable, since you can just delegate the actual implementation to various components that handles each interface.
     
    mahdiii likes this.
  22. mahdiii

    mahdiii

    Joined:
    Oct 30, 2014
    Posts:
    856
    You can use GetComponent<IReloadable>() to access reload (pistol or SMG)!! it is standard that unity uses it. it is actually component based over inheritence approach. I prefer to use reflection and getcomponent instead of duplicating codes and copy paste codes like as vexe said.
    When you select to inherit from several interfaces, you understand that you need to duplicate codes (same functions)
     
    angrypenguin likes this.
  23. mysticfall

    mysticfall

    Joined:
    Aug 9, 2016
    Posts:
    649
    Yes but again, it's not mutually exclusive with making SMG to implement such behavioral interfaces, as I said above.

    The difference is accountability, because if you just rely on GetComponent, there's no gaurantee that an instance of SMG would have IReloadable or IShootable components attached to it and you can't even know if it was some sort of an weapon by looking at its signature.

    Of course, you can use RequireComponent attribute to enforce the contract. But again, that doesn't really exclude the possibility to follow the well established OOP principles, in my opinion.

    Say, if you want to see what weapons you have in your inventory then it'd be easier and more intuitive if you can just check 'if(item is IShootable)' rather than invoking 'GetComponent<IShootable>' and doing a null check on each return value.

    And if you have both SMG and heavy machine gun in your game which differs only in fire power but not in functionality, then using GetComponent to filter all machine gun types in your inventory could be problematic as there's no separate component that every machine guns need to have, while it's much simpler if you just declare your classes as 'SMG : IMachineGun' and 'HMG : IMachineGun', respectively. (I know there's a tagging system in Unity, but still...)

    As I understand it, Unity is a platform rather than a complete development paradigm to replace OOP, so you might be better off to follow some sort of design principles if you want to keep your code extendible and manageable, even though you develop on Unity.

    So, why not developing on Unity following OOP principles, rather than using Unity instead of OOP design? I still believe Unity to be a good development platform, but I don't think it's meant to be a replacement for a programming paradigm.
     
    Last edited: Sep 10, 2017
  24. mahdiii

    mahdiii

    Joined:
    Oct 30, 2014
    Posts:
    856
    dude, You say that you can prevent duplicate codes without using component?! you use only interfaces and inheritance?
    Can you illustrate that?
     
  25. mysticfall

    mysticfall

    Joined:
    Aug 9, 2016
    Posts:
    649
    To clarify, I didn't say you can do that without using components at all, or the way I belived to be the best approach could eliminate code duplication completely.

    The composition over inheritance pattern, as shown in sample code in the linked article, still requires some boiler plates to delegate functionalities to constituent components, because the primary intention of the pattern is not reducing code, but to make the type hierarchy more flexible.

    But still, those boiler plates don't contain any real implementation details and can be even automatically generated by an IDE these days, so it's seen as the lesser of an evil compared to copy-pasting actual business logic to avoid rigid class hierarchy, which is the problem the pattern intends to solve.

    And if you see the UML diagram included in the above article, you will notice how it still follows OOP design approach, as Duck class, for instance, is not just an arbitrary class that has Flyable component as its property, but actually implementing it via Ducklike interface, thus achieving design flexibility without abandoning the whole OOP paradigm.

    Unity follows that same design pattern in its component based approach, so there's no need to choose between OOP and compositional approach as you can easily do both of them at the same time.
     
    Last edited: Sep 11, 2017
    angrypenguin likes this.
  26. mahdiii

    mahdiii

    Joined:
    Oct 30, 2014
    Posts:
    856
    Thank you but it has used components.
    Code (CSharp):
    1. abstract class GameObject : IVisible, IUpdatable, ICollidable {
    2.   [B]  private readonly IVisible _v;
    3.  
    4.     private readonly IUpdatable _u;
    5.  
    6.     private readonly ICollidable _c;[/B]
    7.  
    8.     public GameObject(IVisible visible, IUpdatable updatable, ICollidable collidable) {
    9.         _v = visible;
    10.         _u = updatable;
    11.         _c = collidable;
    12.     }
    13.  
    14.     public void Update() {
    15.         _u.Update();
    16.     }
    17.  
    18.     public void Draw() {
    19.         _v.Draw();
    20.     }
    21.  
    22.     public void Collide() {
    23.         _c.Collide();
    24.     }
    25. }

    I changed my scripts :)

    Code (CSharp):
    1.     public class SMG:Monobehaviour,IReload,IShooter  {
    2.     IReload m_reloadBehaviour;
    3.     IShooter m_shootBehaviour;
    4.     void Start(){
    5.     m_reloadBehaviour=Getcomponent<IReload >();
    6.     m_shootBehaviour=Getcomponent<IShooter >();
    7.     }
    8.     public void Reload(){
    9.            m_reloadBehaviour.Reload();
    10.     }
    11.     public void Shoot(){
    12.            m_shootBehaviour.Shoot();
    13.     }
    14. }
    15.  
    16. public class CReloadBehaviour1:Monobehaviour,IReload{
    17. void Reload(){}
    18. }
    19. public class CShootBehaviour1:Monobehaviour,IShoot{
    20. void Shoot(){}
    21. }
    or nonmonobehaviour CReloadBehaviour1,CShootBehaviour1
    Code (CSharp):
    1.  public class SMG:Monobehaviour,IReload,IShooter  {
    2.     IReload m_reloadBehaviour;
    3.     IShooter m_shootBehaviour;
    4.     void Start(){
    5.     m_reloadBehaviour=new CReloadBehaviour1();
    6.     m_shootBehaviour=new CShootBehaviour1();
    7.     }
    8.     void ChangeReloadBehaviour(IReload _newReloadBehaviour){
    9.           m_reloadBehaviour=_newReloadBehaviour;
    10.    }
    11. void ChangeShootBehaviour(IShoot _newShootBehaviour){
    12.           m_shootBehaviour=_newShootBehaviour;
    13.    }
    14.     public void Reload(){
    15.            m_reloadBehaviour.Reload();
    16.     }
    17.     public void Shoot(){
    18.            m_shootBehaviour.Shoot();
    19.     }
    20. }
    21.  
    22. public class CReloadBehaviour1:IReload{
    23. void Reload(){}
    24. }
    25. public class CShootBehaviour1:IShoot{
    26. void Shoot(){}
    27. }
    Cool,One question: Is it suitable to implement an empty implementation of an interface? (for example: NoReloadable class or NoShootable class), because now we can always use Reload() in "for" loop without caring about that the class is not Reloadable or Reloadble
     
  27. mysticfall

    mysticfall

    Joined:
    Aug 9, 2016
    Posts:
    649
    Personally, I wouldn't do that because the whole point of separating such interfaces for each functionalities is to make it clear that an object may have only part of those capabilities. If every weapons needs to have both Shoot() and Reload() methods but can choose to ignore its invocation, you don't really need the component based approach at all, since you can easily override a method that is not needed from a common single parent implementation.

    If what you want is an easy way to call them in a loop, you can always do either of those:
    Code (CSharp):
    1. for (var weapon : weapons)
    2. {
    3.     var reloadable = weapon as IReloadable;
    4.  
    5.     if (reloadable != null)
    6.     {
    7.         reloadable.Reload();
    8.     }
    9. }
    Code (CSharp):
    1. for (var weapon : weapons) {
    2.     (weapon as IReloadable)?.Reload();
    3. }
    Code (CSharp):
    1. weapons
    2.     .Where(w => w is IReloadable)
    3.     .Cast<IReloadable>()
    4.     .ForEach(w => w.Reload());
     
    mahdiii likes this.
  28. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,614
    Hallelujah! It's nice to read that rather than the usual complaints about how Unity breaks OO by using components "instead".

    That said, not specific to Unity, component oriented design does have some interesting implications on an OO workflow. For example, just by having a Transform component it is possible for any code, anywhere in a Unity application to look up a GameObject and mess with its position. Add a Rigidbody to that GameObject as well and now you've got two ways to change its position, only one of which you're meant to use depending on the usage scenario...

    I think the issue here is the use of those things through fields, rather than searching for them as components. There's nothing wrong with having the fields present if that's how the implementer of the SMG class decides to do things, but the reason for that shouldn't primarily be to expose those to users weapons in general for the exact reasons you mentioned.

    How is either easier than the other? To me they're exactly the same.

    On that broader problem, though, ideally if I wanted to see if something was a weapon I don't think I'd want to look for a shooting or reloading ability. A nailgun can be shot and reloaded, it's not necessarily a weapon. A sword is a weapon, it can't be shot or reloaded.

    Weapons do all have some stuff in common. As the most basic example, the entire purpose of a weapon is to damage or harm things, so I'd probably have some level of either interfacing or inheritance to reflect that - a class or interface that describes the damage a weapon does. The more specific we get, though, the more flexibility we might need. A variety of different weaons can all launch projectiles (guns, crossbows, bows, mortars...), but it's not necessarily a problem if a weapon can't launch a projectile. So to me it actually makes a lot of sense for those to be components which are optionally present. Those components could in turn implement interfaces (or inherit a class) to tell you what type of functionality they add to their composited object so that if, for example, you've got different ways of launching projectiles, you can have a HitscanShooter component and a RaycastProjectileShooter component and a PhysicsProjectileShooter component which all offer different implementations without any client code having to care about the differences.
     
    Last edited: Sep 11, 2017
    mysticfall likes this.
  29. mysticfall

    mysticfall

    Joined:
    Aug 9, 2016
    Posts:
    649
    Yeah, that's exactly what I tried to say :)

    Ok, I admit that there's not much of a difference in terms of lines of code required, but I still find it to be at least more intuitive to understand a class statically through its type hierarchy, rather than relying on its runtime behaviour like what it returns to GetComponent call.

    I'm not saying that the latter way is inherently bad, or you can't make a good coding convention relying on that method. But if we agree that OOP is something worth, and Unity is not really a replacement for a design paradigm, I think abandoning the whole type hierarchy and treating every MonoBehaviour classes as the same kind of 'bags' that hold some arbitrary components is unnecessary and better be avoided.

    Of course, the mention about IWeapon was just an example and the actual design should be done according to the domain (the specific game that is worked on) at hand. If I was to design something like that, I'd probably go as the following code (not that different from what you suggested) :
    Code (CSharp):
    1. public interface IDamageable
    2. {
    3.     float Health { get; }
    4.  
    5.     void Damage(float amount);
    6. }
    7.  
    8. public interface IDamaging
    9. {
    10.     float Power { get; }
    11.  
    12.     void Damage(IDamageable target);
    13. }
    14.  
    15. public interface IWeapon : IDamaging
    16. {
    17. }
    18.  
    19. public interface IProjectileWeapon : IWeapon, IShooter
    20. {
    21.     IShooter Shooter { get; }
    22. }
    23.  
    24. public interface IShooter
    25. {
    26.     IProjectile Shoot(Vector3 direction);
    27. }
    28.  
    29. public interface IProjectile
    30. {
    31.     IObservable<GameObject> OnHit { get; }
    32.  
    33.     IObservable<Vector3> OnDrop { get; }
    34. }
    35.  
    36. public interface IAmmoBasedWeapon : IProjectileWeapon
    37. {
    38.     float Ammo { get; }
    39.  
    40.     float MaximumAmmo { get; }
    41. }
    42.  
    43. public interface IReloadableWeapon : IAmmoBasedWeapon, IReloadable
    44. {
    45.     IReloadable Reloader { get; }
    46. }
    47.  
    48. public interface IReloadable
    49. {
    50.     void Reload();
    51. }
     
    mahdiii and angrypenguin like this.
  30. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,614
    I definitely agree that Unity isn't a replacement for a design paradigm. It's a set of tools, that's all.

    While I've had quite some success with using GameObjects as "component bags" I do generally find myself mixing inheritance, composition and interfaces as best fits on a per-task basis.
     
    mysticfall likes this.
  31. kaplica

    kaplica

    Joined:
    Feb 20, 2014
    Posts:
    87
    This topic has been very helpful guys. I've been using recently zenject framework and I've been trying different approaches listed here and combined with this framework you can make wonders... You are no longer bound to use mono behaviours that much, so constructor injection can be used in conjunction with interfaces.

    Some of the examples here for composition I know from C# look wrong.

    If you do:

    private IReloadable _reloadable;

    and then _reloadable = GetComponent<IReloadable>();

    how does this pull Implementation of IReloadable? For example Reloadable1, 2 ,3 etc. Is this not pulling just interface, or is it pulling an implementation script that has this interface bound?

    Also, the other example by Jamora does makes sense, inheritance is needed at some point. Composition cannot be applied to everything every time.
     
    Last edited: Sep 19, 2017