Search Unity

Weird issue with delegate functions

Discussion in 'Scripting' started by Sobrtonog, Nov 15, 2019.

  1. Sobrtonog

    Sobrtonog

    Joined:
    Sep 28, 2019
    Posts:
    15
    Code (CSharp):
    1.  
    2.  
    3. public class UnitControllerGraves : UnitControllerCore
    4. {
    5.  public void InitializeGraves(){
    6.  
    7.         BuffBase.CreateNewBuff("ToughMechanism", "Tough Mechanism", sv, 3f, bf, -1f, null, ToughMechanism_OnStart, ToughMechanism_OnEnd, null, 1, true);
    8. }
    9.  public void OnDashEnd(){
    10.         anim.SetTrigger("DashEnded");
    11.         dashing = false;
    12.         agent.speed = stats.moveSpeed;
    13.         agent.angularSpeed = 1000;
    14.         agent.radius = .25f;
    15.         agent.obstacleAvoidanceType = ObstacleAvoidanceType.HighQualityObstacleAvoidance;
    16.         rotateTowardsDash = false;
    17.         ResetAttackPeriod();
    18.  
    19.         BuffBase.AddBuff(gameObject, gameObject, "ToughMechanism", 1);
    20.     }
    21.  
    22.     public void ToughMechanism_OnStart(){
    23.         if (toughMechCor != null){
    24.             StopCoroutine(toughMechCor);
    25.         }
    26.         if (mainModel == null){
    27.             Debug.Log("?");
    28.         }
    29.         agent.speed = 20f;
    30.         //toughMechCor = StartCoroutine(ToughMechanism_Blend(true));
    31.     }
    32.     public void ToughMechanism_OnEnd(){
    33.         if (toughMechCor != null){
    34.             StopCoroutine(toughMechCor);
    35.         }
    36.         //toughMechCor = StartCoroutine(ToughMechanism_Blend(false));
    37.  
    38.     }
    39. }
    40. public class UnitControllerCore : MonoBehaviour
    41. {
    42.     // Modifyable information
    43.     private const int c_attackAnimIntRangeSize = 2;
    44.     public int[] attackAnimIntRange = new int[c_attackAnimIntRangeSize];
    45.  
    46.     // A list of buffs
    47.     public List<BuffBase> buffContainer = new List<BuffBase>();
    48.  
    49.     // I'm not sure this needs to be public, but whatever
    50.     public PhotonView PV;
    51.     public Animator anim;
    52.     public StatusModule stats;
    53.     public NavMeshAgent agent;
    54.  
    55.     public GameObject weaponLaunchSource;
    56.     public GameObject weaponLaunchFX;
    57.     public GameObject weaponImpactFX;
    58.     public GameObject weaponMissileFX;
    59.     public GameObject[] abilFX;
    60.     public AudioClip[] abilSound;
    61.     public Material[] materialRefs;
    62.     public GameObject mainModel;
    63. }
    64. public class BuffBase : MonoBehaviour
    65. {
    66.     // Public vars
    67.  
    68.     // Buff name
    69.     public string buffName                                        = "?un-namedBuff!";
    70.  
    71.     // Buff id
    72.     public string buffId                                        = "?buffId!";
    73.  
    74.     // Buff flags
    75.     public BuffType buffFlags                                    = BuffType.None;
    76.  
    77.     // StatusVars that will be added at the beginning and subtracted at the end to the target
    78.     public StatusVars svars                                     = new StatusVars();
    79.  
    80.     // Source of the buff
    81.     public GameObject source;
    82.  
    83.     // Target of the buff
    84.     public GameObject target;
    85.  
    86.     // Length of buff
    87.     public float timer                                            = 0f;
    88.  
    89.     // Every periodicTick time passed, OnPeriodicTick is called
    90.     public float periodicTick                                    = -1f;
    91.  
    92.     // Delegate functions
    93.     public delegate void OnEventGenericFunction();
    94.  
    95.     // Called every periodicTick loop
    96.     public OnEventGenericFunction onPeriodicTick;
    97.  
    98.     // Called when buff starts
    99.     public OnEventGenericFunction onBuffStart;
    100.  
    101.     // Called when buff ends, regardless of the timer fully reached or not
    102.     public OnEventGenericFunction onBuffEnd;
    103.  
    104.     // Called when buff finishes, only when the timer is fully reached
    105.     public OnEventGenericFunction onBuffFinish;
    106.  
    107.     // Private vars
    108.  
    109.     // Tick left before periodic is called again
    110.     private float m_storedTick                                     = 0f;
    111.  
    112.     // Base duration of the buff, readonly
    113.     private float m_baseDuration                                = -1f;
    114.     public float baseDuration{
    115.         get {return m_baseDuration;}
    116.     }
    117.  
    118.     // List of predefined buffs
    119.     protected static List<string> definedId                     = new List<string>();
    120.     protected static List<string> definedName                    = new List<string>();
    121.     protected static List<StatusVars> definedSvars                = new List<StatusVars>();
    122.     protected static List<int> definedMaxStack                    = new List<int>();
    123.     protected static List<bool> definedRefreshStacks            = new List<bool>();
    124.     protected static List<float> definedTimer                    = new List<float>();
    125.     protected static List<float> definedTick                    = new List<float>();
    126.     protected static List<BuffType> definedFlags                = new List<BuffType>();
    127.     protected static List<OnEventGenericFunction> definedOPT    = new List<OnEventGenericFunction>();
    128.     protected static List<OnEventGenericFunction> definedOBS    = new List<OnEventGenericFunction>();
    129.     protected static List<OnEventGenericFunction> definedOBE    = new List<OnEventGenericFunction>();
    130.     protected static List<OnEventGenericFunction> definedOBF    = new List<OnEventGenericFunction>();
    131.  
    132.     // When creating a new type of buff use this
    133.     public static int CreateNewBuff(string id, string n, StatusVars info, float baseDur, BuffType flags, float pt, OnEventGenericFunction f1, OnEventGenericFunction f2, OnEventGenericFunction f3, OnEventGenericFunction f4, int maxStacks, bool refresh){
    134.         OnEventGenericFunction tf;
    135.  
    136.         // If string id already exists, return null;
    137.         if (definedId.Contains(id)){
    138.             Debug.Log(id + ", id has already been used to create a buff!");
    139.             return -1;
    140.         }
    141.  
    142.         // Add all the new information
    143.         definedId.Add(id);
    144.         definedName.Add(n);
    145.         definedSvars.Add(info);
    146.         definedTimer.Add(baseDur);
    147.         definedFlags.Add(flags);
    148.         definedTick.Add(pt);
    149.  
    150.         definedMaxStack.Add(maxStacks);
    151.         definedRefreshStacks.Add(refresh);
    152.  
    153.         // Add delegates
    154.         definedOPT.Add(f1);
    155.         definedOBS.Add(f2);
    156.         definedOBE.Add(f3);
    157.         definedOBF.Add(f4);
    158.  
    159.         // Return index
    160.         return definedId.IndexOf(id);
    161.     }
    162.     public static List<BuffBase> GetBuffsOfType(GameObject target, string id){
    163.         List<BuffBase> list = new List<BuffBase>(target.GetComponents<BuffBase>());
    164.         List<BuffBase> fList = new List<BuffBase>();
    165.         foreach (BuffBase b in list){
    166.             if (b.buffId == id){
    167.                 fList.Add(b);
    168.             }
    169.         }
    170.         return fList;
    171.     }
    172.  
    173.     // When you want to add a buff to a target use this
    174.     public static void AddBuff(GameObject source, GameObject target, string id, int amt){
    175.         if (!definedId.Contains(id)){
    176.             Debug.Log("AddBuff: Invalid id entered!");
    177.             return;
    178.         }
    179.  
    180.         int i = definedId.IndexOf(id);
    181.  
    182.         // Remove previous stacks if we are
    183.         int savedStacks;
    184.         List<BuffBase> curStacks = GetBuffsOfType(target, id);
    185.         savedStacks = curStacks.Count;
    186.         if (savedStacks + amt > definedMaxStack[i]){
    187.             foreach (BuffBase temp in curStacks){
    188.                 temp.DestroyBuff(false);
    189.                 savedStacks -= 1;
    190.                 if (savedStacks + amt <= definedMaxStack[i]){
    191.                     break;
    192.                 }
    193.             }
    194.         }
    195.  
    196.         if (definedRefreshStacks[i]){
    197.             foreach(BuffBase b in GetBuffsOfType(target, id)){
    198.                 b.timer = b.m_baseDuration;
    199.             }
    200.         }
    201.  
    202.         for (int lc = amt; lc > 0; lc--){
    203.             BuffBase newBuff = target.AddComponent(typeof(BuffBase)) as BuffBase;
    204.             newBuff.buffName = definedName[i];
    205.             newBuff.buffId = definedId[i];
    206.             newBuff.svars = definedSvars[i];
    207.             newBuff.buffFlags = definedFlags[i];
    208.             newBuff.timer = definedTimer[i];
    209.             newBuff.periodicTick = definedTick[i];
    210.  
    211.             newBuff.onPeriodicTick = definedOPT[i];
    212.             newBuff.onBuffStart = definedOBS[i];
    213.             newBuff.onBuffEnd = definedOBE[i];
    214.             newBuff.onBuffFinish = definedOBF[i];
    215.  
    216.             newBuff.source = source;
    217.             newBuff.target = target;
    218.             newBuff.m_baseDuration = newBuff.timer;
    219.  
    220.             StatusModule tStats = target.GetComponent<StatusModule>();
    221.             tStats.AddStatusVars(newBuff.svars, 1f);
    222.         }
    223.     }
    224.  
    225.     // Use this to destroy the buff
    226.     public void DestroyBuff(){
    227.         StatusModule tStats = target.GetComponent<StatusModule>();
    228.         tStats.AddStatusVars(this.svars, -1f);
    229.         if (this.onBuffEnd != null){
    230.             this.onBuffEnd();
    231.         }
    232.         UnityEngine.Object.Destroy(this);
    233.     }
    234.  
    235.     // Does not call the end func depending on param
    236.     public void DestroyBuff(bool doEndFunc){
    237.         StatusModule tStats = target.GetComponent<StatusModule>();
    238.         tStats.AddStatusVars(this.svars, -1f);
    239.         if (this.onBuffEnd != null && doEndFunc){
    240.             this.onBuffEnd();
    241.         }
    242.         UnityEngine.Object.Destroy(this);
    243.     }
    244.  
    245.     // Start is called before the first frame update
    246.     void Start()
    247.     {
    248.         if (this.onBuffStart != null){
    249.             this.onBuffStart();
    250.             //this.GetComponent<UnitControllerGraves>().ToughMechanism_OnStart();
    251.         }
    252.  
    253.     }
    254.  
    255.     // Update is called once per frame
    256.     void Update()
    257.     {
    258.         // Firstly check if we need to do any periodic functions
    259.         if (onPeriodicTick != null){
    260.  
    261.             // Reset the tick if we need to and call functions as necessary
    262.             if (m_storedTick > 0f){
    263.                 m_storedTick = periodicTick;
    264.                 onPeriodicTick();
    265.             }
    266.  
    267.             // Otherwise reset the tick
    268.             else {
    269.                 m_storedTick -= Time.deltaTime;
    270.             }
    271.         }
    272.  
    273.         // Decrease the timer if necessary
    274.         if (baseDuration != -1f){
    275.             timer -= Time.deltaTime;
    276.  
    277.             // If buff has ended, call the onBuffEnd and destroy the buff
    278.             if (timer <= 0f){
    279.                 this.DestroyBuff();
    280.             }
    281.         }
    282.     }
    283.  
    I have this weird issue with delegate functions in these particular scripts. I use delegates to store functions to call when a buff for a character has started/ended. But for some reason when I call them, I can not properly retrieve gameObject member of MonoBehaviour class and a bunch of other public variables that is originally class members. I think the delegate function is just not using proper class instance to call the function (kind of?), but while I think this some members like floats are being retrieved properly so I'm confused...

    I know I'm wording this poorly, but basically the delegate functions they only half work, they work in the sense that they're called but when I try to do anything with changing some class members ( they are public ), it throws errors and tells me the class instance has been destroyed or nulled even though it's not.

    This is strange because, in another pack of scripts this same technique works fine and works completely as intended. Also it is able to read some publicly declared variables like materials.

    TLDR:
    Look at line 19 - That adds a buff and it has a few delegate functions passed in that will be called when buff starts/ends.

    Look at line 26 - That is one of the functions being called, that line specifically references a variable 'mainModel' in the parent class. Even though it's set (I've checked many many times), it says the reference is a null reference. It also says the class instance of the UnitControllerGraves has been destroyed - even though it clearly isn't during live tests. I've tried checking other stored variables to see if maybe the class instance is being stored properly, GameObject types specifically have this issue. I also can not even reference gameObject, one of the default members of monoBehaviour.

    Perhaps delegates are not meant to be used in this manner and I've just gotten lucky in my other attempt. I've removed a lot of the scripts, but these are the important parts that will help you understand what it is they're doing.

    I've been banging my head at this for hours with no progress. I was hoping someone could shed light on this issue.

    Also, I'm not a seasoned programmer with strong coding fundamentals so if you know of a better way to go about approaching this please let me know! I understand the code can seem kind of long, but it's very simple if you take a glance at it.

    Just about everything is working perfectly, except the delegate part T_T.
    I'm using 2019.2 version
     
    Last edited: Nov 15, 2019
  2. Madgvox

    Madgvox

    Joined:
    Apr 13, 2014
    Posts:
    1,317
    Make sure that the instance that you call CreateNewBuff on is the same instance that you call AddBuff on. I wouldn't architect the buff system in this way, as you're tying the created buff directly to a specific class instance, creating a very strong coupling that isn't necessary and is very bug prone (as you have found out). When you pass in the methods as delegates, you are not just passing in a reference to any version of the method, you are passing in a reference to the specific method of that specific class instance. If you want to keep CreateNewBuff instance-neutral, but still be able to call the passed method names on whatever target you give it, you'll need to use reflection or the SendMessage API (I don't recommend this).

    I would probably approach this by passing the delegates to AddBuff instead of CreateNewBuff. If that's distasteful to you, since you're trying to define all of the parameters in CreateNewBuff -- which I totally understand -- then delegates simply don't work that way, and you will need to reconsider how you've architected this system.
     
    Sobrtonog likes this.
  3. Sobrtonog

    Sobrtonog

    Joined:
    Sep 28, 2019
    Posts:
    15
    Wow just when I thought my logic was perfect, I realized you are completely right!

    I just realized when I initialized all the buff data in CreateNewBuff, I was passing in UnitControllerGraves instance that has been long destroyed before a real unit actually uses the buff, and that was causing issues.

    Thank you very much for the advice!

    I would give a cookie or rep if there were any :).