Search Unity

Survival System help + Calling a method from an element in a list?

Discussion in 'Scripting' started by nerdares, Sep 25, 2016.

  1. nerdares

    nerdares

    Joined:
    Aug 4, 2015
    Posts:
    18
    Lets say I made a class called Car in a separate file. Inside, I made a method called drive which should drive the car.

    Now let's say I make a new script that contains many instances of the Car class. BUT INSTEAD, I'm storing these in a list:

    Code (CSharp):
    1. List<Cars> myCarCollection = new List<Cars>();
    2.  
    3.  
    Now in my list, I'll add some new cars

    Code (CSharp):
    1. myCarCollection.Add(new Car());
    2. myCarCollection.Add(new Car());
    I've added two cars in that list. Let's say I want the first car I entered to use the drive method I have made.

    Code (CSharp):
    1. myCarCollection.ElementAt(0).drive();
    If you try to do this, it won't work. My question is, can you call methods from an individual element in a list, as long as that element is a class with a method?
     
  2. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,334
    That should work as it stands. ElementAt is a bit akward, the usual way to do this is:

    Code (csharp):
    1. myCarCollection[0].drive();
    Are you getting any error messages? "Won't work" is a bit vague.

    Note that if Car is a MonoBehaviour, you can't construct it through "new Car()" - that's not how Unity's designed. All MonoBehaviours have to be attached to a GameObject, so you'll either have to get them off gameobjects in your scene (or prefabs you instantiate), or by adding the script to GameObjects through AddComponent<Car>
     
  3. nerdares

    nerdares

    Joined:
    Aug 4, 2015
    Posts:
    18
    Hey, thanks for the reply!

    The reason I asked this question is because I'm making my own survival system for a game.
    It will be a bit difficult to explain so I'll try to break it down here:

    My survival systems has multiple scripts. The one that will be attached to a character is called SurvivalCore.

    Now if you think of games like minecraft or the forest, they have many survival properties like health, hunger, thirst, temperature etc.

    I decided to make it so that each of those survival components is treated as their own class. This way, the user can dynamically make new survival components by making a new class and defining some properties.

    Every survival component has some common properties so I made an abstract class that each new survival component you make must inherit from. It's called SurvivalComponent.

    Now currently, I have made two survival components. HungerComponent and HealthComponent. These are their own classes but they both inherit Survival Component.

    HungerComponent and HealthComponent have their own unique methods and properties which should be called in SurvivalCore


    Now in SurvivalCore, I'm creating a list called Survival_Components and that list will take in anything of type "SurvivalComponent" (or anything inheriting from it). When I add the HungerComponent, I cannot call their methods.


    Here is their code:


    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. namespace SurvivalSystem
    5. {
    6.     public abstract class SurvivalComponent
    7.     {
    8.         /// <summary>
    9.         /// The name of the survival property to be displayed in the inspector
    10.         /// </summary>
    11.         public string Name { get; set; }
    12.  
    13.         /// <summary>
    14.         /// Determines if survival property should even be used or not
    15.         /// </summary>
    16.         public bool UseProperty { get; set; }
    17.  
    18.         /// <summary>
    19.         /// Max value of the survival property
    20.         /// </summary>
    21.         public float MaxValue { get; set; }
    22.  
    23.         /// <summary>
    24.         /// Current value of the survival property
    25.         /// </summary>
    26.         public float CurrentValue { get; set; }
    27.  
    28.  
    29.         /// <summary>
    30.         /// Rate of the survival property to increase or decrease, depending on
    31.         /// how it is used
    32.         /// </summary>
    33.         public float Rate { get; set; }
    34.  
    35.         /// <summary>
    36.         /// Value to multiply property's stats based on level
    37.         /// </summary>
    38.         public float Multiplier { get; set; }
    39.     }
    40.  
    41. }
    42.  


    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using SurvivalSystem;
    4.  
    5. public class HungerComponent : SurvivalComponent
    6. {
    7.  
    8.     //Add additional stats here
    9.  
    10.     #region Properties
    11.  
    12.     /// <summary>
    13.     /// Value to set how much health is dropped per second
    14.     /// when this property is set to 0
    15.     /// </summary>
    16.     public float HealthDropRate;
    17.  
    18.     /// <summary>
    19.     /// Bool to set true if property is at 0
    20.     /// false if not
    21.     /// </summary>
    22.     public bool CriticalCondition;
    23.     #endregion
    24.  
    25.     //Unique methods go here
    26.     #region Methods
    27.  
    28.     /// <summary>
    29.     /// Drops the characters hunger according to what
    30.     /// the Rate property is set to.
    31.     /// </summary>
    32.     public void Starve()
    33.     {
    34.         if (this.CurrentValue > 0)
    35.         {
    36.             float time = (this.Rate * Time.deltaTime);
    37.             this.CurrentValue = Mathf.MoveTowards(this.CurrentValue, 0, time);
    38.         }
    39.  
    40.     }
    41.  
    42.     /// <summary>
    43.     /// Drops the characters health when this survival
    44.     /// component is at 0, according to what
    45.     /// the HealthDropRate is set to.
    46.     /// </summary>
    47.     /// <param name="comp">The HealthComponent used in survival core to drop health from</param>
    48.     public void DepleteHealth(HealthComponent comp)
    49.     {
    50.         if (this.CurrentValue <= 0)
    51.         {
    52.             float time = (this.HealthDropRate * Time.deltaTime);
    53.             comp.CurrentValue = Mathf.MoveTowards(comp.CurrentValue, 0, time);
    54.         }
    55.     }
    56.     #endregion
    57. }
    58.  
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using SurvivalSystem;
    4.  
    5. public class HealthComponent : SurvivalComponent
    6. {
    7.  
    8.     // Add additional stats here
    9.     #region Variables
    10.  
    11.  
    12.     /// <summary>
    13.     /// Set how much health can be recovered after each regeneration round
    14.     /// </summary>
    15.     [Tooltip("Set how much health can be recovered after each regeneration round")]
    16.     public float HealthRecovered = 0f;
    17.  
    18.     /// <summary>
    19.     /// Value that updates the t value of lerp in the starve method
    20.     /// </summary>
    21.     [Tooltip("Set a delay between each health regeneration round")]
    22.     public float healthRecoveryDelay = 0f;
    23.  
    24.     /// <summary>
    25.     /// Current delay value on health recovery
    26.     /// </summary>
    27.     [HideInInspector]
    28.     public float currentHealthDelay = 0.0f;
    29.  
    30.     /// <summary>
    31.     /// Check if character is dead
    32.     /// </summary>
    33.     [Tooltip("Check if character is dead")]
    34.     public bool isDead;
    35.     #endregion
    36.  
    37.     //Unique methods to the component go here
    38.     #region Methods
    39.  
    40.  
    41.  
    42.  
    43.     #endregion
    44. }
    45.  
    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEditor;
    3. using System.Collections;
    4. using System;
    5. using System.Collections.Generic;
    6. using Invector;
    7. using Invector.CharacterController;
    8. using System.Linq;
    9.  
    10.  
    11.  
    12. namespace SurvivalSystem
    13. {
    14.     public class SurvivalCore : MonoBehaviour
    15.     {
    16.  
    17.         public List<SurvivalComponent> Survival_Components;
    18.  
    19.  
    20.         void Start()
    21.         {
    22.  
    23.             Survival_Components = new List<SurvivalComponent>();
    24.  
    25.             Survival_Components.Add(new HealthComponent { Name = "Health", UseProperty = true, MaxValue = 100 });
    26.             Survival_Components.Add(new HungerComponent { Name = "Hunger", UseProperty = true, MaxValue = 100, HealthDropRate = 5f, Rate = 5f });
    27.  
    28.  
    29.          
    30.             Survival_Components[1].Starve();
    31.  
    32.         }
    33.  
    34.         void Update()
    35.         {
    36.         }
    37.  
    38.  
    39.     } // end SurvivalCore
    40.  
    41.  
    42. }// end SurvivalSystem
    43.  
    44.  

    If you look at survival core, there is a line saying Survival_Components[1].Starve(); That's giving me an error and idk why.
     
  4. lordconstant

    lordconstant

    Joined:
    Jul 4, 2013
    Posts:
    389
    Since the list stores it as a survival component you cant call a function of hunger component. You can solve this a few ways.

    Either make a function in survival component and override it when inheriting then you can call that from the list or cast the component from the list to its type and then you will have access to the function.

    Both methods have drawbacks but personally I would go with the ovverriding route as it makes creating new survival components simpler.
     
  5. nerdares

    nerdares

    Joined:
    Aug 4, 2015
    Posts:
    18
    The problem with that is that some components would have unique methods which shouldn't go into SurvivalComponent. So what would be the workaround for that?
     
  6. Brathnann

    Brathnann

    Joined:
    Aug 12, 2014
    Posts:
    7,187
    There are ways to figure out what script you are accessing. But, you could just simply have a method that is in all scripts that returns what type of script it is. That way if you know it's a health or hunger script, you'll know what is in it.

    http://stackoverflow.com/questions/3561202/check-if-instance-of-a-type

    This may help you as well as it gives an example of is and as in some of the answers.
     
  7. takatok

    takatok

    Joined:
    Aug 18, 2016
    Posts:
    1,496
    Are you designing this system as "middleware"? Are you trying to make it generic for other programmers to use later. Or are you trying to design a system where the player can create new objects that use these survival components. I'm assuming its the first one? Right now it seems like your survival core is going to represent your player. Will any other objects in this game use the HealthCore or HungerCore classes?
     
  8. lordconstant

    lordconstant

    Joined:
    Jul 4, 2013
    Posts:
    389
    This whole thing is pretty subjective, but I would probably do something like this:
    Have a generic update function on my survival components for things that need to happen each frame.
    Use a msging system to avoid needing to know a components type e.g. when your hunger component knows your hungers at 0 you send a damage message which is handled in your health component.
     
  9. nerdares

    nerdares

    Joined:
    Aug 4, 2015
    Posts:
    18

    Basically I'm making this survival system in such a way so that:

    The programmer can create new characters that use this survival system
    The programmer can give each new character different survival components
    The programmer can create new unique survival components for any character
     
  10. nerdares

    nerdares

    Joined:
    Aug 4, 2015
    Posts:
    18
    The reason why I'm making a list for this is because in the inspector (once I make an editor script), I want the user to be able to drag and drop components that they have written into the SurvivalCore script under a List Inspector element called Survival_Components.

    You know how if you defined a public int[] in your class, you can see a list object in the inspector and enter multiple integers from there?

    That's why I wanted to put the components in a list

    The other reason why I wanted to store everything in a list is because of this:

    There are multiple components the programmer can create for a character. In that event,
     
  11. takatok

    takatok

    Joined:
    Aug 18, 2016
    Posts:
    1,496
    I think what you are looking for is interfaces. You can create a base character class that contains certain methods and fields that every character will have no matter what. Then for all your survival components you make them interfaces. New characters you create will inherit from the base character class as well as any of the interfaces they want. This will allow you to write interface specific code without caring what the various character classes look like or how they are implemented. Here is an example of what I mean:

    The interfaces:
    Code (CSharp):
    1. public interface IHealth
    2. {
    3.     float healthRecovered { get; set; }
    4.     float healthRecoveryDelay { get; set; }
    5.     float currentHealthDelay { get; set; }
    6.  
    7.     void RecoverHealth();
    8. }
    9.  
    10. public interface IHunger
    11. {
    12.     float HealthDropRate { get; set; }
    13.     bool CriticalCondition { get; set; }
    14.     void Starve();
    15. }
    The classes:
    Code (CSharp):
    1. public abstract class SurvivalCharacter
    2. {
    3.     int ID;  // maybe you need some kind of identifier
    4.     string name;
    5.  
    6.     public SurvivalCharacter(int id, string name)
    7.     {
    8.         this.ID = id;
    9.         this.name = name;
    10.     }
    11. }
    12.  
    13. public class RegularCharacter : SurvivalCharacter, IHealth, IHunger
    14. {
    15.     float healthRecovered;
    16.     float healthRecoveryDelay;
    17.     float currentHealthDelay;
    18.     float currentHealth;
    19.     float maxHealth;
    20.  
    21.     float hungerLoss;
    22.     float currentHunger;
    23.     float maxHunger;
    24.  
    25.     public float HealthRecovered
    26.     {
    27.         get { return healthRecovered; }
    28.         set { healthRecovered = value; }
    29.     }
    30.  
    31.     public float HealthRecoveryDelay
    32.     {
    33.         get { return healthRecoveryDelay; }
    34.         set { healthRecoveryDelay = value; }
    35.     }
    36.  
    37.     public float CurrentHealthDelay
    38.     {
    39.         get { return currentHealthDelay; }
    40.         set { currentHealthDelay = value; }
    41.     }
    42.  
    43.     public float HungerLoss
    44.     {
    45.         get { return hungerLoss; }
    46.         set { hungerLoss = value; }
    47.     }
    48.  
    49.     public RegularCharacter(int id, string name)
    50.         : base(id, name) { }
    51.  
    52.     public void RecoverHealth()
    53.     {
    54.         if (currentHealthDelay > healthRecoveryDelay)
    55.             currentHealth += healthRecovered;
    56.         if (currentHealth > maxHealth)
    57.             currentHealth = maxHealth;
    58.     }
    59.  
    60.     public bool CheckHealth()
    61.     {
    62.         if (currentHealth <= 0)
    63.             return false;
    64.         else
    65.             return true;
    66.     }
    67.  
    68.     public void Starve(float deltaTime)
    69.     {
    70.         currentHunger -= deltaTime;
    71.         if (currentHunger <= 0)
    72.         {
    73.             currentHealth -= hungerLoss;
    74.         }
    75.     }
    76. }
    Now other code. Assume you had some controller class somewhere:
    Code (CSharp):
    1. // list of all the characters in the game.
    2. List<SurvivalCharacter> characters = new List<SurvivalCharacter>();
    3.  
    4. void Update()
    5. {
    6.         foreach(SurvivalCharacter character in characters)
    7.         {
    8.                 var hungerChar = chraracter as IHunger;
    9.                 if (hungerChar != null)
    10.                        hungerChar.Starve(Time.deltaTime);
    11.                 var healthChar = character as IHealth;
    12.                 if (healthChar != null)
    13.                 {
    14.                           if (!healthChar.CheckHealth())
    15.                           {
    16.                                    // Code here to kill the character
    17.                           }
    18.                           else
    19.                               healthChar.RecoverHealth();
    20.                  }
    21.           }
    22. }
    23.  
    This code keeps a list of the base Abstract class. Because it knows every character will be that. It then casts the object to the appropriate interface.

    This code will go through all your characters and call their appropriate functions if they have that interface implemented. If they don't it skips them. So for example you could create a new character , some monster in the world, that doesn't ever get Hungry. You would just create it like this:
    Code (CSharp):
    1. public class NeverHungryCharacter : SurvivalCharacter, IHealth
    2. {
    And now your Controller Class code happily processes this new character class by calling its Check Health and Recover Health, but it will skip over the Starve. The nice thing about interfaces is your Controller Code doesn't care or need to know anyting about all the classes you make. It just checks if they have an interface implemented and if it does acts upon it. It doesn't even care HOW that class implements the methods. For example you could create a character whose Starve method only drops him to 25% helath. .and never below that.

    Also note: The fields in the Interfaces should only be there if you need to set them from outside the class. Maybe a different character casts Health Regen, so you need to access the character's HealthRecovery and change it, or the character enters a zone that has 0 health regen. so you set his HealthRecovered = 0. Note the currentHealth and maxHealth are specific to the class, I need them to implement the IHealth interface, but they are specific to my implementation. So I don't put those in the interface.
     
    Last edited: Sep 27, 2016
  12. nerdares

    nerdares

    Joined:
    Aug 4, 2015
    Posts:
    18

    Wow, this is something impressive. I can't believe I haven't thought of doing my survival system like that, gosh I wish I could think like you. Thanks! I'll try to see if I can make this work!
     
  13. nerdares

    nerdares

    Joined:
    Aug 4, 2015
    Posts:
    18

    Actually, 2 quick questions:

    1) The interfaces IHunger and IHealth have some common properties like currentValue and maxValue. How can I prevent this redundancy?

    2) In the regular character, there are private variables of currentHealth and a public property of currentHealth. Same goes for other health fields. Wouldn't it be better to put those variables in the interface file itself?

    So like instead of currentHealth in regular character, put CurrentHealth { get; set; } in the interface of IHealth.
     
  14. takatok

    takatok

    Joined:
    Aug 18, 2016
    Posts:
    1,496
    1) Just call them currentHunger and maxHunger, and currentHealth, and maxHealth. its better anyway so your know whats going on.

    2) Yah you can add whatever public Properties you feel the interface needs. If you want every character who implements IHealth to have to have a currentHealth and maxHealth Property that others can see and access add it in.

    Remember Interfaces don't actually help you with saving time the way inheritance would. By that I mean if you add currentHealth/maxHealth to the interface it won't save any time when you implement all your classes. They still have to manually add those variables in whether they are part of the interface or not. The difference is if they are in the interface they MUST implement them. Admittedly I can't think of someone implementing IHealth that would not need those two variables, so sticking them in the interface seems fine.

    The key thing about the Interface idea is that its a contract. You class is guaranteeing that it will implement all the properties and methods listed in the Interface. So other code calling it can safely assume they are there. Its also exposing every Method and Property as public. (Now you can make the properties only settable, or only gettable or both as you need).

    If you tried to add this to the interface:
    Code (CSharp):
    1. float currentHealth;
    It will yell at you. Interfaces can't have any actual variables in them. They are strictly a list of the items that a class will implement. You can have this:
    Code (CSharp):
    1. public float CurrentHealth {get;}
    Since this is strictly speaking a method. Its just how we get at the variable currentHealth that the class that implements IHealth will be required to do.
    The actual class would have to do something like this:
    Code (CSharp):
    1. float currentHealth;
    2.  
    3. // this satisfies the IHealth contract
    4. public float  CurrentHealth
    5. {   get {return currentHealht;} }
     
  15. nerdares

    nerdares

    Joined:
    Aug 4, 2015
    Posts:
    18
    Ok, thanks for that that!

    One last thing: So yeah, I have the script called RegularCharacter which is what my character (imma use a cube for demo purposes) will use. RegularCharacter is not a monobehaviour so how will this attach to the cube in the inspector?
     
  16. takatok

    takatok

    Joined:
    Aug 18, 2016
    Posts:
    1,496
    Game Objects can (and should!) have multiple scripts attached to them. You can have a Game Object with a regular Character script , as all as other scripts that are monoBehaviour. These will be the ones that handle movement , colliders, etc

    You can use the default 3D cube and attach a RegularCharacter script to it. Then you can create a Movement script that handles getting keyboard and mouse input to move the cube.
     
  17. Kalladystine

    Kalladystine

    Joined:
    Jan 12, 2015
    Posts:
    227
    One small addition - in (most) IDE's, Mono and VS, you can right click the interface name in the class declaration and through the context menu (refactoring/quick actions) implement the interface.
    It will not add the actual implementation details admittedly, but at least you'll have all properties/fields/method stubs in place with just a couple of clicks.
     
    takatok likes this.
  18. takatok

    takatok

    Joined:
    Aug 18, 2016
    Posts:
    1,496
    @Kaladystine wow, I never knew that. That is a big time saver!
     
  19. nerdares

    nerdares

    Joined:
    Aug 4, 2015
    Posts:
    18

    Problem: Game objects cannot take in Scripts that don't have monobehaviour inherited in it. RegularCharacter does not inherit monobehaviour and it won't because it's already inheriting the abstract class SurvivalCharacter. What's a workaround for this?
     
  20. nerdares

    nerdares

    Joined:
    Aug 4, 2015
    Posts:
    18
    The only option I see right now is to make SurvivalCharacter inherit monobehaviour. That creates a kinda hacky way to make RegularCharacter derive from monobehaviour, but that seems like a bad idea and improper
     
  21. Kalladystine

    Kalladystine

    Joined:
    Jan 12, 2015
    Posts:
    227
    There is nothing wrong with making an abstract class that is in the middle of inheritance chain (although it does seem weird at first glance, it's actually not uncommon) as long as it makes sense for what you want to achieve.

    <personal opinion>
    If you haven't yet I'd recommend to take a step back, list all your high-level requirements/constraints and only then look at how you'll build your system. Since it looks like a composition-based structure, interfaces will probably be the way to go at some point, but which point it is should be decided by what you want to achieve. Design patterns should be used when the task at hand will benefit from it, not because they can.
    </personal opinion>
     
  22. nerdares

    nerdares

    Joined:
    Aug 4, 2015
    Posts:
    18

    Ok so I have made SurvivalCharacter inherit from monobehaviour.

    My regular character script now looks like this:

    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.UI;
    3. using System.Collections;
    4. using SurvivalSystem;
    5.  
    6. public class RegularCharacter : SurvivalCharacter, IHealth, IHunger
    7. {
    8.  
    9.     #region Health values
    10.  
    11.     float currentHealth;
    12.     float maxHealth;
    13.     float healRate;
    14.     Slider healthBar;
    15.  
    16.     /// <summary>
    17.     /// Value to set current health
    18.     /// </summary>
    19.     public float CurrentHealth
    20.     {
    21.         get { return currentHealth; }
    22.         set { currentHealth = value; }
    23.     }
    24.  
    25.     /// <summary>
    26.     /// Maximum health for the character
    27.     /// </summary>
    28.     public float MaxHealth
    29.     {
    30.         get { return maxHealth; }
    31.         set
    32.         {
    33.             maxHealth = value;
    34.         }
    35.     }
    36.  
    37.     /// <summary>
    38.     /// Value to set the amount of health to restore per second
    39.     /// </summary>
    40.     public float HealRate
    41.     {
    42.         get { return healRate; }
    43.         set { healRate = value; }
    44.     }
    45.  
    46.     /// <summary>
    47.     /// GUI slider to show health to the user
    48.     /// </summary>
    49.     public Slider HealthBar
    50.     {
    51.         get { return healthBar; }
    52.         set { healthBar = value; }
    53.     }
    54.  
    55.    
    56.     #endregion
    57.  
    58.     #region Hunger Values
    59.     float currentHunger;
    60.     float maxHunger;
    61.     float starveRate;
    62.     float healthDropRate;
    63.     bool criticalCondition;
    64.  
    65.     /// <summary>
    66.     /// Value to set current hunger
    67.     /// </summary>
    68.     public float CurrentHunger
    69.     {
    70.         get { return currentHunger; }
    71.         set { currentHunger = value; }
    72.     }
    73.  
    74.     /// <summary>
    75.     /// Maximum hunger for the character
    76.     /// </summary>
    77.     public float MaxHunger
    78.     {
    79.         get { return maxHunger; }
    80.         set { maxHunger = value; }
    81.     }
    82.  
    83.     /// <summary>
    84.     /// Value to set how much hunger should drop
    85.     /// over time
    86.     /// </summary>
    87.     public float StarveRate
    88.     {
    89.         get { return starveRate; }
    90.         set { starveRate = value; }
    91.     }
    92.  
    93.     /// <summary>
    94.     /// Value that sets how much health should drop
    95.     /// after this system is at 0
    96.     /// </summary>
    97.     public float HealthDropRate
    98.     {
    99.         get { return healthDropRate; }
    100.         set { healthDropRate = value; }
    101.     }
    102.  
    103.     /// <summary>
    104.     /// bool that set's to true if CurrentHunger is at 0
    105.     /// </summary>
    106.     public bool CriticalCondition
    107.     {
    108.         get { return criticalCondition; }
    109.         set { criticalCondition = value; }
    110.     }
    111.    
    112.     #endregion
    113.  
    114.  
    115.  
    116.     public RegularCharacter(int id, string name)
    117.         : base(id, name) { }
    118.  
    119.     #region Health Methods
    120.  
    121.     /// <summary>
    122.     /// Method to update the Health bar
    123.     /// </summary>
    124.     public void UpdateHealthBar()
    125.     {
    126.         healthBar.value = currentHealth;
    127.     }
    128.  
    129.     /// <summary>
    130.     /// Heal's the character given some constraints
    131.     /// </summary>
    132.     public void AutoHeal()
    133.     {  
    134.         if( (currentHunger / maxHunger) >= 0.60f)
    135.         {
    136.             float time = (healRate * Time.deltaTime);
    137.             currentHealth  = Mathf.MoveTowards(currentHealth, maxHealth, time);  
    138.         }
    139.                
    140.     }
    141.  
    142.     #endregion
    143.  
    144.     #region HungerMethods
    145.  
    146.     /// <summary>
    147.     /// Starves the character over time
    148.     /// </summary>
    149.     public void Starve()
    150.     {
    151.         if (currentHunger > 0)
    152.         {
    153.             float time = (starveRate * Time.deltaTime);
    154.             currentHunger = Mathf.MoveTowards(currentHunger, 0, time);
    155.         }
    156.     }
    157.  
    158.     /// <summary>
    159.     /// Depletes the characters health over time if
    160.     /// in critical condition
    161.     /// </summary>
    162.     public void DepleteHealth()
    163.     {
    164.         if(criticalCondition)
    165.         {
    166.  
    167.         }
    168.     }
    169.     #endregion
    170.  
    171.     void Start()
    172.     {
    173.     }
    174. }
    175.  
    Now, nothing shows in the inspector because properties don't actually show up. Right now I'm in the process of trying to make an editor script that will create the inspector for me:

    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEditor;
    3. using UnityEngine.UI;
    4. using SurvivalSystem;
    5. using System.Collections;
    6.  
    7. [CustomEditor(typeof(RegularCharacter))]
    8. public class RegularCharacterEditor : Editor
    9. {
    10.     RegularCharacter _regChar;
    11.  
    12.     public override void OnInspectorGUI()
    13.     {
    14.         _regChar = (RegularCharacter)target;
    15.  
    16.         EditorGUILayout.LabelField("Health Values: ");
    17.         EditorGUILayout.Space();
    18.  
    19.         _regChar.MaxHealth = EditorGUILayout.FloatField("Max Health", _regChar.MaxHealth);
    20.         EditorGUILayout.LabelField("Current Health = " + _regChar.CurrentHealth);
    21.  
    22.  
    23.         _regChar.HealRate = EditorGUILayout.FloatField("Heal Rate", _regChar.HealRate);
    24.        
    25.  
    26.     }
    27. }
    28.  
    The properties are indicated with identifiers starting with a Capital. The private variables are indicated with the lower case starting identifier. Once I set MaxHealth to 100 for example, when I start the game, Maxhealth goes back down to 0. Why does it do that?
     
  23. takatok

    takatok

    Joined:
    Aug 18, 2016
    Posts:
    1,496
    its because the private variables are what is known as the "Backing fields" of the Properties. So disregarding the editor, in basic C# when you have this:
    Code (CSharp):
    1. private float _someFloat;
    2.  
    3. public float SomeFloat
    4. {
    5.         get { return _someFloat;}
    6.         set { _someFloat = value;}
    7. }
    You are effectively doing this:
    Code (CSharp):
    1. private float _someFloat;
    2.  
    3. public float GetSomeFloat()
    4. {
    5.           return _someFloat;
    6. }
    7.  
    8. public  void SetSomeFloat(float value)
    9. {
    10.          _someFloat = value;
    11. }
    Properties in C# aren't variables, the are basically functions. that just set and return private variables. You might be thinking, whats the point of having properties then if we are just using them to expose a private variable. Why not just make the variable public in the first place. Lets say you did that with _someFloat. Later on down the line you wanted all the calling code that get access to someFloat to only use 1 decimal place of significant digits (always rounding down the 2nd digit). Well you'd either a) have to make sure all the calling code did this, or b) make sure every method in the class did this. Both are a hassle. However, since Properties are just functions, and all the calling code is using the Property to get the value,not the actual variable itself we can do this :
    Code (CSharp):
    1. Public float SomeFloat
    2. {
    3.        get { return  (int)(_someFloat*10)/10f;}
    4.        set { _someFloat = value;}
    5. }
    And now all our class's code and caller's code can be left unchanged.

    So i'm not 100% sure how the Unity Editor code works with setting that public Property. If nothing is actually setting the backing field. The next time any code calls for that Property, its going to look up whats in the backingfield (which being unset is 0f).

    The easy fix, is Unity has a simple way to display private variables in the editor without making them public. You can just rip out all the code you have for the public Properties and add this code to the private variables you want to initialize in the editor:

    Code (CSharp):
    1.  float currentHunger;
    2. [SerializeField]
    3. float maxHunger;
    4. [SerializeField]
    5. float starveRate;
    6. [SerializeField]
    7. float healthDropRate;
    8. bool criticalCondition;
    Now maxHunger, starveRate, and healthDropRate are settable in the editor. currentHunger, and criticalCondition will still not show up.
     
  24. nerdares

    nerdares

    Joined:
    Aug 4, 2015
    Posts:
    18

    Thanks for that! Now, from here, I have implemented a thirst interface. I'm now concerned about some redundancy in terms of what I have to write for the editor script and script for RegularCharacter. If I wanted to make a new template, I have to make new editor scripts as well and repeat code over and over again. How would I encapsulate this?

    For example: almost every system has a depleteHealth kind of method, except for health. The problem is, I have to implement these methods in RegularCharacter and clutter the space because these systems are interfaces. Any tips on encapsulating these?
     
  25. takatok

    takatok

    Joined:
    Aug 18, 2016
    Posts:
    1,496
    Create a private method on your Survival character like this:
    Code (CSharp):
    1. private void AdjustAttribute(float current, float adjustFactor, float max, float min)
    2. {
    3.          current+=adjustFactor;
    4.          Mathf.Clamp(current,min,max);
    5. }
    Then IThirst and other like it could be like this
    Code (CSharp):
    1. public interface IThirst
    2. {
    3.        float CurrentThirst;
    4.        float MaxThirst;
    5.        float ThirstRate;
    6.  
    7.        void UpdateThirst(float deltaTime);
    8. }
    9.  
    10. // and in your character code:
    11. public void UpdateThirst(float deltaTime)
    12. {           AdjustAttribute(currentThirst,thirstFactor*deltaTime,MaxThirst,0f);
    13. }
    Edit: there is no way around having to implement UpdateThirst, UpdateHealth, UpdateWhatever. Because the code that is checking if these Interfaces are implemented then needs a hook to call. You can't make them into some kind of big array because a character might only implement one of them. Thats the point. If you knew for a fact that every character would have Thirst and Health, then don't make them interfaces make them part of the base class.