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

Weapon Decorator C#

Discussion in 'Scripting' started by Martin_Gonzalez, Jul 26, 2014.

  1. Martin_Gonzalez

    Martin_Gonzalez

    Joined:
    Mar 25, 2012
    Posts:
    361
    Hi there, i'm trying to do a simple weapon decorator for a pistol. I understand how decorator works and how is the structure but i have some question about it.

    i have my Component

    Code (CSharp):
    1. public class WeaponComponent : MonoBehaviour
    2. {
    3.     public Transform noseTransform;
    4.  
    5.     protected float shootDistance = 0;
    6.     public virtual void Shoot()
    7.     {}
    8. }
    then i have my Concrete Component

    Code (CSharp):
    1. public class PistolWeapon : WeaponComponent
    2. {
    3.     public PistolWeapon()
    4.     {
    5.         shootDistance = 30;
    6.     }
    7.  
    8.     public override void Shoot ()
    9.     {
    10.         Debug.Log("Shooting Distance: "+shootDistance);
    11.     }
    12. }

    My Decorator

    Code (CSharp):
    1. public abstract class WeaponDecorator : WeaponComponent
    2. {
    3. }
    My Concrete Decorator

    Code (CSharp):
    1. public class SilencerDecorator : WeaponDecorator
    2. {
    3.     private WeaponComponent _wC;
    4.  
    5.     public SilencerDecorator(WeaponComponent wC)
    6.     {
    7.         _wC = wC;
    8.         GameObject go = (GameObject)Instantiate(Resources.Load("Silencer"),_wC.noseTransform.position,Quaternion.identity);
    9.         go.transform.parent = _wC.noseTransform;
    10.     }
    11.  
    12.     public override void Shoot ()
    13.     {
    14.         base.Shoot ();
    15.         shootDistance = 20;
    16.         Debug.Log("Shotting with Silencer. Distance: "+shootDistance);
    17.     }
    18. }
    And then i have my testing script

    Code (CSharp):
    1. public class Testing : MonoBehaviour {
    2.  
    3.     // Use this for initialization
    4.     void Start ()
    5.     {  
    6.         GameObject go = (GameObject)Instantiate(Resources.Load("Pistol"));
    7.         WeaponComponent pistol = go.GetComponent<PistolWeapon>();
    8.         pistol.Shoot();
    9.         pistol = new SilencerDecorator(pistol);
    10.         pistol.Shoot();
    11.  
    12.     }
    13. }
    The output is what i expected.

    Code (CSharp):
    1. //First only the Pistol
    2. Shooting Distance: 30
    3. UnityEngine.Debug:Log(Object)
    4. //Then the pistol with silencer
    5. Shooting Distance: 20
    6. UnityEngine.Debug:Log(Object)
    But then i thought. What if i want to insert a Grip into the weapon that affect only a variable.
    GripDecorator doesnt have to Shoot, but if i do a new GripDecorator and says pistol.Shoot() will shoot the Grip. I'm confused on that.

     
  2. LightStriker

    LightStriker

    Joined:
    Aug 3, 2013
    Posts:
    2,716
    Aren't you getting some warning that a class deriving from MonoBehaviour should not be created with "new"?
     
  3. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,377
    your decorator isn't calling shoot on the actual weapon it decorates.

    you have properties on the top class that are specific to a certain kind of weapon

    your decorator should wrap another class, effect that class, and then call shoot on it
     
  4. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,377
    Like this (note this is just a example code, components of course should avoid constructors, and the wrapping of the weapon should be handled in Awake):

    Code (csharp):
    1.  
    2. public abstract class Weapon
    3. {
    4.  
    5.     public virtual void Shoot()
    6.     {
    7.         //override
    8.     }
    9.  
    10. }
    11.  
    12. public class Pistol : Weapon
    13. {
    14.  
    15.     public float Range = 30;
    16.  
    17.     public override void Shoot()
    18.     {
    19.         //calculate the shot
    20.     }
    21.  
    22. }
    23.  
    24. public abstract class WeaponDecorator : Weapon
    25. {
    26.  
    27.     protected Weapon _weapon;
    28.  
    29.     public WeaponDecorator(Weapon w)
    30.     {
    31.         _weapon = w;
    32.     }
    33.  
    34.     public override void Shoot()
    35.     {
    36.         _weapon.Shoot();
    37.     }
    38.  
    39. }
    40.  
    41. public class SilencerDecorator : WeaponDecorator
    42. {
    43.  
    44.     public SilencerDecorator(Weapon w) : base(w)
    45.     {
    46.     }
    47.  
    48.     public override void Shoot()
    49.     {
    50.         _weapon.Range = 20.0f;
    51.         base.Shoot();
    52.     }
    53.  
    54. }
    55.  

    [edit]
    Changed to work within the entity component design pattern used by MonoBehaviour

    Code (csharp):
    1.  
    2. public abstract class Weapon : MonoBehaviour
    3. {
    4.  
    5.     public virtual void Shoot()
    6.     {
    7.         //override
    8.     }
    9.  
    10. }
    11.  
    12. public class Pistol : Weapon
    13. {
    14.  
    15.     public float Range = 30;
    16.  
    17.     public override void Shoot()
    18.     {
    19.         //calculate the shot
    20.     }
    21.  
    22. }
    23.  
    24. public abstract class WeaponDecorator : Weapon
    25. {
    26.  
    27.     public Weapon DecoratedWeapon;
    28.  
    29.     public override void Shoot()
    30.     {
    31.         if(this.DecoratedWeapon == null) return;
    32.         this.DecoratedWeapon.Shoot();
    33.     }
    34.  
    35. }
    36.  
    37. public class SilencerDecorator : WeaponDecorator
    38. {
    39.  
    40.     public override void Shoot()
    41.     {
    42.         this.DecoratedWeapon.Range = 20.0f;
    43.         base.Shoot();
    44.     }
    45.  
    46. }
    47.  
    Though if I'm going to be honest. This isn't the best mixture of the two, and I'd probably go a much different route. Just demonstrating how you should avoid the constructor.




    As for your grip, it would wrap these.

    The idea of your decorators is that they just wrap around your class you're decorating.

    Code (csharp):
    1.  
    2. var weapon = new Grip(new Silencer(new Pistol)));
    3.  
    There you have a weapon that is a pistol decorated with a silencer and a grip. Your code just knows it's a weapon, in this case that weapon happens to be typed as a grip, but it doesn't care about that. It just cares that the method 'Shoot' exists, and the grip will apply it's decoration, then call shoot on silencer, which applies its decoration, and the finally calls shoot on pistol, which then actual calculates the shot.
     
    Last edited: Jul 26, 2014
    io-games, thexdd and Stardog like this.
  5. Martin_Gonzalez

    Martin_Gonzalez

    Joined:
    Mar 25, 2012
    Posts:
    361
    Nope
     
  6. Martin_Gonzalez

    Martin_Gonzalez

    Joined:
    Mar 25, 2012
    Posts:
    361

    I guess i understood

    so if i want to add a Grip decorator what i have to use is something like this:

    Code (CSharp):
    1. public class GripDecorator : WeaponDecorator
    2. {
    3.     private WeaponComponent _wC;
    4.    
    5.     public GripDecorator(WeaponComponent wC)
    6.     {
    7.         _wC = wC;
    8.         //Change Weapon attributes
    9.     }
    10.  
    11.     public override void Shoot ()
    12.     {
    13.         base.Shoot ();
    14.     }
    15. }
    But...Grip doesnt have to override Shoot isnt it? If not, When i do pistol = new GripDecorator() Shoot will not exist.
     
  7. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,377
    I must have edited my post after you read it, where I talked about the grip.

    Sorry about that.

    I'll quote it here:

    Note how in my previous post I demonstrated that your decorator MUST call shoot on the weapon it is decorating. May that weapon it is decorating be a weapon as well.

    Oh and I should also say, my decorator design is actually broken. Because the weapon you're decorating isn't necessarily typed correctly to have the 'Range' property on it.

    Really decorators are for doing extra stuff on top of what the decorated class does. Not to necessarily modify that class. If it modifies it, there should be some interface through which to modify it in the abstract class that defines it.
     
  8. Martin_Gonzalez

    Martin_Gonzalez

    Joined:
    Mar 25, 2012
    Posts:
    361
    I understand...But, in wich part do you reference teh public Weapon DecoratedWeapon; in the Decorator, in the ConcreteDecorator isnt it?
     
  9. Martin_Gonzalez

    Martin_Gonzalez

    Joined:
    Mar 25, 2012
    Posts:
    361
    Grip Decorator must override the Shoot method too?
     
  10. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,377
    Well in the code I wrote, no, because the WeaponDecorator class takes care of calling the weapon. So you don't have to override it. But technically the Grip is calling Shoot... it's just taken care of in the super class.
     
  11. Martin_Gonzalez

    Martin_Gonzalez

    Joined:
    Mar 25, 2012
    Posts:
    361
    but i have another Question.
    I did all that and when i print i realize that Grip is modifing the Silencer weapon but no Pistol

    So imagine that if i do.
    Weapon pistol = new Pistol();
    pistol = new Silencer();
    pistol = new Grip(); //if i modify any params it will modify Silencer params but no pistol params

    is that why you used a Weapon variable on the Decorator?