Search Unity

Player class and code design.

Discussion in 'Scripting' started by Derpnaut, Mar 22, 2018.

?

Best way to set up a Player class?

  1. My private components alternative.

  2. My public components alternative.

  3. Some other alternative.

Results are only viewable after voting.
  1. Derpnaut

    Derpnaut

    Joined:
    Apr 2, 2017
    Posts:
    9
    Hello,

    I have been using Unity for a few years now (only in an amateur fasion in personal and school projects), and I'm also currently studying IT at a university. When I now started to develop a new top-down rpg type game I came to struggle with how to design my player class. The two different ideas both involve the Player class being sort of an interface (not literally) between the different components of the player (inventory, stats, combat etc.) and whoever needs to use them (add an item, use a spell, buff the player...).

    The first idea was to have the different components private and add methods for all the communication in the player (AddItem(), Buff()...) however I also realized this might lead to a very long Player class.

    The other idea I had was to only have the player be a collection with public getters for all the different components ex:
    Code (CSharp):
    1. public Inventory { get; private set; }
    thus mostly bypassing the player, breaking the law of demeter (if I haven't completely misunderstood the principle) and generally I felt had the potential of leading to further problems down the line.

    So my question is, what is the recommended way of setting up this type of a Player class, which one of these would you chose and or do you have any alternatives that might be better?
     
    IndependentThinkers likes this.
  2. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,532
    So I have what I call an 'Entity' class. It represents the root of a GameObject that is an entity (player, mob, etc).

    It's fairly simple and really just has a 'Type' property that returns an enum of if it's a Player/Mob/Generic/etc. And properties for any really simple components that might be attached to it. Like say 'HealthMeter'.

    Then I add the components for all the other stuff. Like your inventory and what not. If I want to access them I just use GetComponent (or even GetComponentInChildren if you want to be able to stick them in child GameObjects). This way you're not forcing your entity to have everything, but you find out what it does have by calling GetComponent.
     
  3. Derpnaut

    Derpnaut

    Joined:
    Apr 2, 2017
    Posts:
    9
    Hello lordofduct!

    Thanks for your answer, well that is a way of doing things I had not considered. A problem for me would be that most of my components (inventory, eq...) are not monobehaviors but instead pure c# data classes (mostly to somewhat have a separation model and view) this, however, might be a bad way of doing things in Unity perhaps and your idea a better solution. I will definitely take this into consideration. :)
     
  4. Rotary-Heart

    Rotary-Heart

    Joined:
    Dec 18, 2012
    Posts:
    813
    There are many ways of handling this, is up to what works with your project and your coders.

    Pure C# data classes can work too. You can combine your idea with what lordofduct said. Use a Component that holds your object references and use the GetComponent to get the reference holder then access the inventory.

    I have a hybrid of what lordofduct said and pure C# data classes. The inventory class reference only exists inside a Storage component. So if I want something to have an inventory I just attach the Storage component. Of course the Storage component has more than just the Inventory reference. I use what lordofduct said of a type property to indicate what kind of storage it is (General, Food, Liquid, etc.).
     
  5. Derpnaut

    Derpnaut

    Joined:
    Apr 2, 2017
    Posts:
    9
    Hi Rotary-Heart,

    So if I understand you right, you do sort of my private version with all the different access functions but you've split them into several scripts? That might be the best solution for me, however, a part of why I wished to do just that in the first place was to be able to do (for example) Player.TakeDamage(), or Player.AddItem() but then leave the implementation of those methods to the component using them.

    Really what I would've wanted was to have the Player not be a single class but instead a collection of classes where each function call was redirected to the component it's meant for. I guess there's no practical, good way of doing this so I'll have to use some other solution.
     
  6. Rotary-Heart

    Rotary-Heart

    Joined:
    Dec 18, 2012
    Posts:
    813
    You could do that, it's up to how you code it. Your Player.AddItem() will be calling your inventory to add the item and so on.
     
  7. Derpnaut

    Derpnaut

    Joined:
    Apr 2, 2017
    Posts:
    9
    Yes which was the first of my original choices. My problem with that however, was that my player class most likely would become very long and cluttered.
     
  8. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,532
    I would suggest that's a bad idea.

    Your Player class would definitely become long.

    And it would also have a whole bunch of methods on it named things weird relative to the class itself.

    'Player.AddItem'... Player isn't inventory, why do you add item's to it? Classes should usually be able to be described relatively simply, usually in a single sentence. It should really serve a single role.

    A 'List' is an indexed resizable collection of objects.
    A CharacterController is a physical representation of an entity that can be moved.
    A BoxCollider is a physics collider in the shape of a box.

    A Player is the entity representing the player.
    A PlayerInventory is the inventory for a player.
    A HealthMeter is the health container for an entity.

    Here's an example of a player entity in one of my games:


    Note that the PlayerEntity itself really is just a container for some general purpose stuff, but mostly just offers properties to critical parts of the player (healthmeter, movement controller, and action motor).

    The other stuff is broken out into some other components directly on this. Or attached to child GameObjects. Such as these:

    AnimData - holds animation related stuff


    AliveMotor - holds the scripts that perform movement.


    The game she is in:
    https://jupiterlighthousestudio.itch.io/prototype-mansion
     
  9. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,532
    Now, you previously mentioned you created a lot of your classes as pure C# classes for data only.

    So to separate view and model.

    ...

    I would say you don't necessarily have to use pure C# classes to get that. You can still use ScriptableObject and MonoBehaviour, and separate view and model (ScriptableObject is specifically for doing that, since it's intended as a data container only).

    Lets say you went that way. You could use your Player script as the hook to these data containers.

    Something like this...
    Code (csharp):
    1.  
    2. public class PlayerInventory : ScriptableObject
    3. {
    4.    
    5.     #region Fields
    6.    
    7.     private List<InventoryItem> _items = new List<InventoryItem>();
    8.    
    9.     #endregion
    10.    
    11.     #region Methods
    12.    
    13.     public void AddItem(InventoryItem item)
    14.     {
    15.         _items.Add(item);
    16.     }
    17.    
    18.     public void RemoveItem(InventoryItem item)
    19.     {
    20.         _items.Remove(item);
    21.     }
    22.    
    23.     //so on so forth
    24.    
    25.     #endregion
    26.    
    27. }
    28.  
    29. public class InventoryItem : ScriptableObject
    30. {
    31.    
    32.     //fields and stuff
    33.    
    34. }
    35.  
    36. public class PlayerEntity : MonoBehaviour
    37. {
    38.    
    39.     public PlayerInventory Inventory;
    40.     public HealthMeter Health;
    41.     //so on so forth
    42.    
    43. }
    44.  
    Then you'd drag the scriptable object's onto their appropriate fields for each. You can access them by said properties from other scripts.

    And if you swap out the visual portion (your player gameobject), you just reference the same data containers of inventory and healthmeter.

    There, model/view separation.
     
  10. Derpnaut

    Derpnaut

    Joined:
    Apr 2, 2017
    Posts:
    9
    Thank you very much for your reply lordofduct,

    Scriptable objects is something I've read a little bit about but haven't really used yet, but it is definitely something I will explore and experiment with further.
     
  11. mahdiii

    mahdiii

    Joined:
    Oct 30, 2014
    Posts:
    856
    I would like to use scriptableObjects like databases (only for containers that keep data) and decouple uis (views) from models with monobehaviour classes