Search Unity

C# Interface Question

Discussion in 'Scripting' started by pjlx911, Oct 22, 2014.

  1. pjlx911

    pjlx911

    Joined:
    Dec 19, 2013
    Posts:
    19
    Hi,

    I am working on programming a project for a Pirate (Yar!) game. One of the people, who I'm working with, had turned me on to the idea of using Interfaces with Unity. The thing is that I've never used Interfaces within C#, let alone not with Unity. I've looked up a bunch of tutorials, and while I understand the basics, there is this one issue that I'm having and would like some help.

    I am working on an Inventory-type system for this game. The method that I want to use Interfaces are; I have a class that is the Interface and holds all the functions I want to use, I have another class that is a Dictionary that I want to use to store the information, and I want to create weapon/item classes that draws from the Interface class then stores it in the Dictionary class.

    I also want to use the Interfaced functions' values that I passed it, and use them for algorithms for the weapon's stats. The issue I keep having is that I seemingly have a hell of a time calling/using an Interface/Dictionary from another script.

    I am new at Interfaces in general, so am I doing it wrong? Also, does Unity have issues with doing what I'm trying to do? I keep trying to get it to work, then MonoBehaviour throws a fit, so I don't know.

    Any help would be appreciated, here's my current code:


    ----------


    Interface Script

    using UnityEngine;
    using System.Collections;
    using System.Collections.Generic;

    public interface IItemManager
    {
    void RangedWeapons(string name, int weapSlot, float damage, float range, float accuracy, float spread, float weight, float reloadT, float accidentChance);
    void MeleeWeapons(string name, int weapSlot, bool isTwoHand, float damage, float weight, float accuracy, float range, float swingSpd, float breakChance);
    void MiscWeapons (string name, bool canExplode, bool canCarry, bool canPoison, int numOfItems, float damage, float weight, float radius);
    void HealthItems (string name, bool canHeal, float duration, int healthAmt);
    }

    ----------

    Dictionary Script:

    using UnityEngine;
    using System.Collections;
    using System.Collections.Generic;

    public class ItemDB : MonoBehaviour
    {
    //reminder: Dictionary<int, string>

    public static Dictionary<int, string> RangedWeaponsDictionary = new Dictionary<int, string>();
    public static Dictionary<int, string> MeleedWeaponsDictionary = new Dictionary<int, string>();
    public static Dictionary<int, string> MiscWeaponsDictionary = new Dictionary<int, string>();
    public static Dictionary<int, string> HealthItemsDictionary = new Dictionary<int, string>();

    }


    ----------

    Pistol Script (that I want to use the above-mentioned scripts in):


    using UnityEngine;
    using System.Collections;
    using System.Collections.Generic;

    public class PistolScript : MonoBehaviour {


    private float baseDmg;
    private float baseRng;
    private float baseAcc;
    private float baseSprd;
    private float baseWgt;
    private float baseReld;
    private float baseAccident;


    private float iDmg;
    private float iRng;
    private float iAcc;
    private float iSprd;
    private float iWgt;
    private float iReld;
    private float iAccident;

    private IItemManager iimngr;

    // Use this for initialization
    void Start () {
    //adds a new instance of a RangedWeapon to the ItemDB
    ItemDB.RangedWeaponsDictionary.Add (1, "PistolA");

    //sets the base stats for a Pistol
    baseDmg = 0.3f;
    baseRng = 0.3f;
    baseAcc = 0.75f;
    baseSprd = 0.3f;
    baseWgt = 0.3f;
    baseReld = 0.3f;
    baseAccident = 0.3f;

    //this pistols stats
    //void RangedWeapons(string name, int weapSlot, float damage, float range, float accuracy, float spread, float weight, float reloadT, float accidentChance);
    iDmg = 0.5f;
    iRng = 15.0f;
    iAcc = 0.75f;
    iSprd = 0.3f;
    iWgt = 0.3f;
    iReld = 0.45f;
    iAccident = 0.5f;

    //New pistol RangedWeapon is initalized
    iimngr.RangedWeapons ("Pistol", 2, iDmg, iRng, iAcc, iSprd, iWgt, iReld, iAccident);
    }

    // Update is called once per frame
    void Update () {

    }


    }
     
  2. KelsoMRK

    KelsoMRK

    Joined:
    Jul 18, 2010
    Posts:
    5,539
    That's not really how you would use an interface. Think of it as a contract. If class X implements interface Y then it must have these methods/properties in it. You can then write more generic management code.

    Imagine an IItem interface
    Code (csharp):
    1.  
    2. public interface IItem
    3. {
    4.     void Use();
    5. }
    6.  
    And then we make some real items
    Code (csharp):
    1.  
    2. public class HealthPotion : IItem
    3. {
    4.     public void Use()
    5.     {
    6.         Debug.Log("Ahhh, refreshing!");
    7.     }
    8. }
    9.  
    10. public class ManaPotion : IItem
    11. {
    12.     public void Use()
    13.     {
    14.         Debug.Log("Tastes like blueberries!");
    15.     }
    16. }
    17.  
    Then our "inventory" becomes super easy to maintain
    Code (csharp):
    1.  
    2. public class Inventory
    3. {
    4.     private List<IItem> items = new List<IItem>();
    5.  
    6.      // make some items and add them to the inventory somewhere else in the code
    7.     items.Add(new HealthPotion());
    8.     items.Add(new ManaPotion());
    9.  
    10.     // use those items
    11.     items[1].Use();    // prints "Tastes like blueberries!" to the console
    12.     items.RemoveAt(1);
    13.     items[0].Use();    // prints "Ahh, refreshing!" to the console
    14.     items.RemoveAt(0);
    15. }
    16.  
    The inventory doesn't care what the item is or does, just that it is an item and has the Use method.
     
  3. pjlx911

    pjlx911

    Joined:
    Dec 19, 2013
    Posts:
    19
    Ok, so are these separate? Or do you have to put all of it in one script?

    My confusion, I guess, is that I keep thinking that you can/should have an interface script. Then I could "plug and play" the functions created in different scripts as needed. If this is not the case, I keep wondering what's the point of Interfaces (as if you are just limited to using it all in one script, there's no modularity involved).

    Oh, also, is this like the Header classes from C++? Where I create the basic functions/variables in it, then make a separate class to give those functions/variables functionality, then use the main class to tie it all together?
     
  4. Suddoha

    Suddoha

    Joined:
    Nov 9, 2013
    Posts:
    2,824

    It's not like the header classes in C++. Those are really just there to seperate the actual implementation from the declaration and are then imported to your main.cpp in order to say 'hey, these classes and its members exist'.
    *Edit Re-reading your post i understood why you saw these similarities - the interface in fact only says which signatures the methods will have, but the idea is not about seperating the declaration from implementation/definition, it's rather about declaring the base that all classes which implement the interface have in common for further use in polymorpism (see below) as your classes will have their own implementation of the method anyways, at least in most of the cases. That's why it does not make much sense to define the body there. *End of edit

    One good example for interfaces is the use for classes that shouldn't derive from the same base class as they're basically totally different. These classes however can have similar behaviour (doesn't need to be the actual behaviour but you'd call it similarly) and you may want to use them somehow in the same context. Since they're actually pretty different, you wouldn't want them to be in relation through classes inheritance, but you can use interfaces instead to make them implement the same methods.

    One thing you could do instead is implementing the methods in each class just as any other method, but interfaces have a great advantage:

    I assume you know about polymorphism, something similar can be done with classes implementing the same interfaces. In order to make it more obvious, i'll try to explain it by using methods with interface paramter.
    So e.g. you can make two completely different classes implement the same interface and have methods which have an interface parameter. Now you're not bound to actual classes or derived classes to match the parameter, you can rather pass any class into it that implements that specific interface.

    @KelsoMRK has already given a great and useful example with a List, but it seems not to be quite clear to you as you'd probably say hey, they're both items, why don't they just inherite the Use() method from a base item class.

    Let's assume you've got two completely different classes which you wouldn't want be in the same inheritance hierarchy but they have still something in common, i'm not that good at making examples but i'll give it a shot:

    Two classes, 'Bottle' and 'TrashBin'. Well, you could argue now that they somewhere high up in a hierarchy have something to do with each other, but let's say they do not. :p

    Details below.

    Code (CSharp):
    1. public interface IExample
    2. {
    3.     void EmptyObject();
    4. }
    5.  
    6. public class TrashBin : IExample
    7. {
    8.     public void EmptyObject()
    9.     {
    10.         // whatever
    11.     }
    12. }
    13.  
    14. public class Bottle : IExample
    15. {
    16.     public void EmptyObject()
    17.     {
    18.         // whatever
    19.     }
    20. }
    21.  
    22. public class MyProgram
    23. {
    24.     public void SomeMethod()
    25.     {
    26.         TrashBin trashBin = new TrashBin();
    27.         Bottle bottle = new Bottle();
    28.  
    29.         HandleObject(bottle);
    30.         HandleObject(trashBin);
    31.     }
    32.  
    33.     public void HandleObject(IExample iEx)
    34.     {
    35.         iEx.EmptyObject();
    36.     }
    37. }

    As you can see, you have these both classes implementing the IExample interface. If you implemented the methods in every class without interface, you'd know need to specify the type and call the specific method with the corresponding (base)class parameter. You might need several HandleObjects methods for that and decide at runtime which one to choose.
    But interfaces allow you to do so much easier, you'll have one method with interface parameter and can pass any object of a type that implemented the interface - of course you need to be sure you've defined the method in the class - because that's the common base and all these objects passed into it have the methods provided by the interface and thus iEx.
    Just like @KelsoMRK's list, you're not limited to collect objects of a specific class (or any derived ones), you can collect any object that supports the interface and its methods and iterate through them, calling the 'same' methods.

    There're still other nice aspects that haven't been mentioned here, but once you got some spare time, don't hesitate to get into it a bit more. :)

    *edit In case i used some terms incorrectly, sorry for that. It's always difficult to explain these things in a different language. :)
     
    Last edited: Oct 22, 2014
  5. guavaman

    guavaman

    Joined:
    Nov 20, 2009
    Posts:
    5,627
    Another nice thing about interfaces in C# is that you can implement as many as you want on the same class. One issue though is if you're used to using Unity's drag and drop in the inspector, interfaces can't be used there without a bunch of hoop jumping. (I have seen some plugins made to help with this, but they involve a lot of voodoo internally to achieve it that looks prone to breaking.)
     
  6. KelsoMRK

    KelsoMRK

    Joined:
    Jul 18, 2010
    Posts:
    5,539
    I'll give a more real-world example based on code that we have in Fractured State.

    Our Finite State Machine implementation uses an interface to ensure that every state has the necessary methods. So we have an interface like this
    Code (csharp):
    1.  
    2. public interface IState
    3. {
    4.     void Enter();
    5.     void Execute();
    6.     void Exit();
    7. }
    8.  
    And the state machine itself looks something like this
    Code (csharp):
    1.  
    2. public class StateMachine
    3. {
    4.     private IState currentState;
    5.  
    6.     public void ChangeState(IState newState)
    7.     {
    8.         if (currentState != null)
    9.             currentState.Exit();
    10.  
    11.         currentState = newState;
    12.         currentState.Enter();
    13.     }
    14. }
    15.  
    Now if I were to write a state implementation and not include those methods I would get a compile-time exception because the class isn't fulfilling it's end of the bargain when it comes to implementing the interface. This is more desirable than a runtime exception where I would have to launch the game and get a unit to use the state I just wrote only to find out I muffed something somewhere.

    In either case - I can now do this
    Code (csharp):
    1.  
    2. public class UnitMoveState : IState
    3. {
    4.     public void Enter()
    5.     {
    6.         // use A* to find a path
    7.     }
    8.  
    9.     public void Execute()
    10.     {
    11.         // if we found a path then move along it until we get to our destination
    12.     }
    13.  
    14.     public void Exit()
    15.     {
    16.         // make the unit stop moving and change to an idle state
    17.     }
    18. }
    19.  
    So - our A* algorithm runs in a different thread which presents some challenges within the context of Unity. Essentially - you can't interact with the game world outside of the main thread so after we get a path we can't simply dump into our state code and get our unit moving immediately. We also can't do any physics checks to smooth the path after it's been calculated either. So what do we do? We use another interface which denotes that this particular state class can accept an A* path after it's been calculated.
    Code (csharp):
    1.  
    2. public interface IPathState
    3. {
    4.     void SetPath(List<Vector3> path);
    5. }
    6.  
    And in our thread manager we accept requests for new paths and queue them up so the pathing thread can process them. We wrap requests in a small object to manage everything

    Code (csharp):
    1.  
    2. public class PathRequest
    3. {
    4.     public PathRequest(IPathState state, Vector3 start Vector3 end) { .... }
    5.  
    6.     public void CompleteRequest(List<Vector3> path)
    7.     {
    8.         state.SetPath(path);
    9.     }
    10. }
    11.  
    12. public class PathManager
    13. {
    14.     // ...skipping some code for brevity
    15.  
    16.     foreach (var request in pathRequests)
    17.     {
    18.         var path = CalculatePath(request.Start, request.End);
    19.         request.CompleteRequest(path);
    20.     }
    21. }
    22.  
    Then we change our move state to implement this new interface as well as IState and we get the combined functionality

    Code (csharp):
    1.  
    2. public class UnitMoveState : IState, IPathState
    3. {
    4.     public void Enter()
    5.     {
    6.         var pathRequest = new PathRequest(this, unitPosition, destination);
    7.         PathManager.Enqueue(pathRequest);
    8.     }
    9.  
    10.     public void SetPath(List<Vector3> path)
    11.     {
    12.         // because this is called from PathManager it actually runs in the other thread
    13.         // so we just set some flags here so Execute() knows what to do in the main thread
    14.         this.path = path;
    15.         pathSet = true;
    16.     }
    17.  
    18.     public void Execute()
    19.     {
    20.         // wait for path
    21.         if (!pathSet) return;
    22.  
    23.         // smooth path after we get it
    24.         if (!pathSmooth)
    25.             SmoothPath();
    26.  
    27.         MoveOnPath();
    28.     }
    29. }
    30.  
    So now UnitMoveState can move with async paths with no additional code in our state machine; and it will work for pathing to a target in our UnitAttackState or entering a building in our UnitEnterStructureState without modifying any pathing code. We simply make those classes implement the IPathState interface. We could even use IPathState on non-state classes to do things like draw helper lines for paths after they've been calculated or whatever else you might think of that needs a path. Also - something like a UnitIdleState that doesn't need to worry about paths simply doesn't implement that interface and then we don't need to write any code to handle something that isn't supported and if I tried to pass a UnitIdleState instance into our pathing code I would get a compile time exception telling me that UnitIdleState isn't set up to handle path requests. The upshot is that the A* code an the State Machine code don't care so long as the interface contract is fulfilled.

    Interface implementation is fundamentally different from inheritance because inheritance assumes that the child class is using the base functionality laid out by its ancestors. Interfaces, again, are contractual so they only care about their methods being implemented. The actual implementations could be completely different.
     
    Last edited: Oct 23, 2014
    PJRM likes this.
  7. Deleted User

    Deleted User

    Guest

    Think of Interface as a template for a class..
    The interface alone does nothing other then provide the structure of the overall program..
    It enforces the rules, so when you build a class you can make sure you inherit all of the functions required.
    This makes the compiler produce nice big errors which makes the coder aware that he forgot to write a particular functions.

    So you write your template
    interface with functions and no body.. just decs basically.
    MyInterface
    void NEEDTHIS()
    void NEEDTHIS2()

    Then you write a your class
    MyClass : MyInterface
    void NEEDTHIS()
    - compile... nope...

    MyClass : MyInterface
    void NEEDTHIS()
    void NEEDTHIS2()
    yep...
     
  8. KelsoMRK

    KelsoMRK

    Joined:
    Jul 18, 2010
    Posts:
    5,539
    This is a little misleading. It's not a template because a class doesn't *have* to implement an interface and it can also implement multiple interfaces simultaneously. It's also not *inheriting* any functionality because the interface doesn't explicitly define any. What you've described is more of a blend between an interface and an abstract base class - which is totally doable by the way. The FSM pattern I described previously actually does that in our real implementation, I just didn't include it here in the interest of clarity.
     
  9. Deleted User

    Deleted User

    Guest

    Thanks KelsoMRK,
    You are correct, I was just trying to think of a way to make it easy to explain but did not even think about how I could be confusing him more..