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. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice

How to hunt down a really weird bug

Discussion in 'Scripting' started by malkere, Jan 25, 2016.

  1. malkere

    malkere

    Joined:
    Dec 6, 2013
    Posts:
    1,209
    1. I have a sword
    2. I have skill points I'm going to spend to buy more strength

    A. If I have the sword in my inventory and I unlock more strength, all is well.
    B. If I have the sword equipped and I unlock more strength, the sword's damage goes up like 12.....??

    I've tried putting debugs everywhere... clearly it happens only when I buy more strength while it's equipped, but I've followed every line of code that gets executed during that purchase, I've checked all references to strength and my main hand.... the two are really not at all related... especially since once the weapon is created, nothing should be able to modify it. Things access it, like:

    Code (CSharp):
    1. Weapon wep = (Weapon)EquipmentData._equipment[EquipmentSlot.Primary]._item;
    2.             foreach (Damage dam in wep._damages) {
    3.                 damage += dam._damage;
    4.                 variance += dam._damageVariance;
    5.             }
    but -nothing- should modify it directly !?!

    Can anyone give a tip or trick or past experience on how to track down elusive bugs? I cannot figure this one out!
     
  2. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,199
    Turn the weapons damage into a property, and add a debug breakpoint/Debug.Log statement on the set part:

    Code (csharp):
    1. //in the class Weapon:
    2. //I assume it's currently looking like this:
    3. public int damage;
    4.  
    5. //turn it into this:
    6. private int _damage;
    7. public int damage {
    8.     get {
    9.         return _damage;
    10.     }
    11.     set {
    12.         Debug.Log("Setting damage to: " + value);
    13.         _damage = value;
    14.     }
    That'll give you a stack trace in the Debug.Log to show where the damage change is coming from. Hope that helps!
     
    malkere likes this.
  3. Dameon_

    Dameon_

    Joined:
    Apr 11, 2014
    Posts:
    542
    You probably need to encapsulate your objects better. Ideally, no object should be accessing another object's fields directly...you need properties set up as middle men. For cases like this, it makes for very easy debugging. You just put a debug line in the set accessor, and you can see what exactly is setting the field, and when.
     
    malkere likes this.
  4. malkere

    malkere

    Joined:
    Dec 6, 2013
    Posts:
    1,209
    You guys are right and this is something I admittedly know I don't fully understand. The concept of reference. What ultimately defines a reference from a copy has always eluded me. I watched all the tutorial script videos several times over the past few years, I never really understood that though.

    I remember BergZergArcade would always do Get{} Set{} I thought it was kind of a depricated thing, I can see the problems that arise without it though now that I've brought one up myself.

    I access the Damage class a lot, but I always do it like listed above, using the variable inside the Damage instance. I found that in one of my skills I was doing this:

    Code (CSharp):
    1.         Damage damage = new Damage();
    2.         if (EquipmentData._equipment[EquipmentSlot.Primary]._item != null) {
    3.             Weapon wep = (Weapon)EquipmentData._equipment[EquipmentSlot.Primary]._item;
    4.             damage = wep._damages[0];
    5.         }
    6.         else { damage._damageType = DamageType.blunt; }
    7.         damage._damage += 2 + level + Mathf.Clamp(level * level * 0.1f, 0f, 10f);
    8.         damage._damageVariance = 0.5f * level;
    I still don't fully grasp the concept, but clearly this is assigning the damage variable as a reference to wep._damages[0] then when I apply changes to damage is is altering my primary weapon. When I use the code in the OP creating a separate int and DamageType, it copies the wep._damages[0] into the int and DamageType so I can change the newly defined variables freely.

    Is this because I've initialized them prior in the OP?

    Why does a Get{} not become a reference? There's some basic rule I'm missing in my understanding of referencing.
     
  5. Boz0r

    Boz0r

    Joined:
    Feb 27, 2014
    Posts:
    419
    I'm not exactly clear on what you're asking, but if you want to learn more about reference types, you should read the official documentation on types:

    https://msdn.microsoft.com/en-us/library/3ewxz6et.aspx

    EDIT: TL;DR: You get a copy if you return a primitive type, and a reference if you return an object type.
     
    malkere likes this.
  6. malkere

    malkere

    Joined:
    Dec 6, 2013
    Posts:
    1,209
    I see, I see. So a wrapper will turn into a reference.... hmmm.... but I could setup a function to copy all the variables of a wrapper into a fresh object and return it, right? hmmm...
     
  7. Rostam24

    Rostam24

    Joined:
    Mar 5, 2014
    Posts:
    119
    Have you also put a debug.log inside of the foreach loop? Just to check whether damage += dam._damage is called 12 times...
     
  8. malkere

    malkere

    Joined:
    Dec 6, 2013
    Posts:
    1,209
    ya I found it, it was in a skill button that was only supposed to be accessing a skills damage potential, but was actually modifying the main hand weapon (if present) because of a reference call I was making where I should have been just returning the number.

    The Weapon class contains a list of Damage s for when a weapon does fire and slashing, or a mix of piercing and blunt, etc. There aren't any weapons with 12 Damage s though
     
  9. Boz0r

    Boz0r

    Joined:
    Feb 27, 2014
    Posts:
    419
    Could you post some more code like where you use it? If you're looking to clone an object like that, you're probably doing something impractical somewhere.
     
  10. malkere

    malkere

    Joined:
    Dec 6, 2013
    Posts:
    1,209
    I fixed the problem, but where I'm accessing the items was like this:

    Code (CSharp):
    1.     public override void EngageAction (int lvl) {
    2.         Skill skill = SkillTreeDatabase.GetSkillTreeByClass(Classes.allClasses.Squire).GetSkill("Heavy Strike", lvl);
    3.         Transform player = PlayerData.player.transform;
    4.  
    5.         //find the mob in range most centered with player view
    6.         Collider[] colliders = Physics.OverlapSphere(player.position, 2f); //weapon based range?
    7.         Collider mob = null;
    8.         float dotProduct = 0.3f; //minimum angle
    9.         foreach (Collider col in colliders) {
    10.             if (col.tag == "Mob") {
    11.                 Vector3 dir = col.transform.position - player.position; //should limit to x,z?
    12.                 float dot = Vector3.Dot(dir, player.forward);
    13.                 if (dot > dotProduct) {
    14.                     dotProduct = dot;
    15.                     mob = col;
    16.                 }
    17.             }
    18.         }
    19.  
    20.         if (mob != null) {
    21.             int dmg = Mathf.CeilToInt(Random.Range(skill._damage._damage - skill._damage._damageVariance,
    22.                 skill._damage._damage + skill._damage._damageVariance));
    23.             mob.GetComponent<Enemy>().TakeDamage(dmg, skill._damage._damageType);
    24.         }
    25.  
    26.         GameObject.Destroy(gameObject);
    27.     }
    I enact my actions by spawning a gameobject and assigning the proper script to it (ActHeavyStrike) to run everything. Then it deletes itself. I worked with delegates and other lesser methods but couldn't get everything to spawn/apply as well as with this method. I have fireballs, arrows, melee and everything using the method =O some pro devs might snicker, but it' working XD

    In this case I'm calling the Skill to pull in damage while the Skill is calling on the Primary weapon for damage:

    Code (CSharp):
    1.     Skill HeavyStrike (int level) { //Heavy Strike: s/b multiplier
    2.         Skill newSkill = new Skill();
    3.         newSkill._name = "Heavy Strike";
    4.         newSkill._description = "Strike with a powerful attack";
    5.         SetupBasics(newSkill, 0, 8, level);
    6.         newSkill._type = SkillType.Damage;
    7.         newSkill._target = SkillTarget.Mob;
    8.  
    9.         float damage = 0;
    10.         float variance = 0;
    11.         DamageType damageType;
    12.         if (EquipmentData._equipment[EquipmentSlot.Primary]._item != null) {
    13.             Weapon wep = (Weapon)EquipmentData._equipment[EquipmentSlot.Primary]._item;
    14.             damage = wep._damages[0]._damage;
    15.             damageType = wep._damages[0]._damageType;
    16.         }
    17.         else { damageType = DamageType.blunt; }
    18.         damage += level + Mathf.Clamp(level * level * 0.1f, 0f, 10f);
    19.         variance = 0.5f * level;
    20.         newSkill._damage = new Damage(damageType, damage, variance);
    21.         newSkill._range = 1f;
    22.         newSkill._cooldown = 5f;
    23.         newSkill._energyCost = 2 + Mathf.FloorToInt(level * 0.5f);
    24.         return newSkill;
    25.     }
    originally I was calling the wep._damage and pulling a reference instead of the values. That caused the weapon damage to change even when I was only loading the skills tooltip in the skill window.

    I just built this part of the game over the past few days, so several parts are unfinished; the range values aren't talking to one another, and some other things. But they all work properly now that I changed this one from calling the Damage to calling the Damage._damage and damage._damageType separately.
     
  11. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,199
    You can get pretty far in programming by learning by doing, but one thing you have to sit down and read is the difference between value types and reference types, and what passing them around does.

    The short version is this:
    A reference type (all members of a class, such as GameObjects and MonoBehaviours and your Weapon class) will be passed by reference when you send it into a method. This means that the method will work on the same object, and any changes that the method does to the object will be visible outside of the method.
    A value type (numbers like ints and floats, or structs like Vector3 and Quaternions) will be passed by value when you send it into a method. This means that the value in the int is copied to the method, and changes inside the method will not be visible outside of the method.


    So, as you noticed, when you do this:
    Code (CSharp):
    1.         Damage damage = new Damage();
    2.         if (EquipmentData._equipment[EquipmentSlot.Primary]._item != null) {
    3.             Weapon wep = (Weapon)EquipmentData._equipment[EquipmentSlot.Primary]._item;
    4.             damage = wep._damages[0];
    5.         }
    6.         else { damage._damageType = DamageType.blunt; }
    7.         damage._damage += 2 + level + Mathf.Clamp(level * level * 0.1f, 0f, 10f);
    8.         damage._damageVariance = 0.5f * level;
    The line damage = wep._damages[0] makes the damage variable not point at the damage you created with new Damage(), but instead the damage object of your weapon.

    If damage was just an integer, the code above would work, because this line:
    Code (csharp):
    1. damage = wep._damages[0];
    would copy the value of the damage, instead of giving you a reference to the damage object.

    This is, by the way, why you can't do this:
    Code (csharp):
    1. transform.position.y += 1f;[Code]
    2.  
    3. The transform.position call returns a copy of the transform's position, as the Vector3 position is a value type. Setting the y-value of the copy would have no effect, so the compiler prevents you from doing that. If Vector3 had been a class instead, that code would have worked (but Unity would have been a lot slower, for reasons I can go into if you care).
    4.  
    5. [QUOTE="malkere, post: 2479997, member: 493279"]Why does a Get{} not become a reference? There's some basic rule I'm missing in my understanding of referencing.[/QUOTE]
    6.  
    7. Doing a get-set pair isn't in any way different from just having a variable - it's still a reference. So these two will work equivalently:
    8. [Code]public Damage damage;
    9. vs.
    10. public Damage damage { get; set; }
    What you can do, on the other hand, is to insert code into the get and set blocks, to do something else. I recommended that since you could get the same behaviour, but see where the code was being changed from.
     
    malkere likes this.