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

Question Combat Script Not Working

Discussion in 'Editor & General Support' started by bwool, Jul 27, 2023.

  1. bwool

    bwool

    Joined:
    Mar 30, 2023
    Posts:
    20
    Hello!

    I am trying to create a combat system for my units to use in an RTS style 2d game, but the actual attack loops are not firing even though the parameters of the if statements are met.

    Below is the script, I've put together so far. It's very basic right now, and functions by detecting if the unit is within a certain distance from the enemy, which will stop unit movement and begin the combat loop, cycling between a special, heavy, and light attack depending on values for cooldown and stamina. The cooldown and stamina values are used for the attacks and regenerate on timers.

    Note: I haven't figured out yet how to load my different attack sprite sheets as animations on the unit, so the placeholder output function in the script below is simply for a text saying which attack type it is.

    Code:

    Code (CSharp):
    1. using System;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using Unity.VisualScripting;
    5. using UnityEngine;
    6. using static BasicUnitMovement;
    7.  
    8. public class BasicCombatController : MonoBehaviour
    9. {
    10.     // Object and variable declarations
    11.     public GameObject unit;
    12.     public GameObject enemy;
    13.     public GameObject heavyAttackText;
    14.     public GameObject lightAttackText;
    15.     public GameObject specialAttackText;
    16.  
    17.     private decimal cooldown = 3m;
    18.     decimal stamina = 10m;
    19.  
    20.     // starts combat loop
    21.     private void Start()
    22.     {
    23.         StartCoroutine(CombatTimer());
    24.  
    25.         specialAttackText.SetActive(false);
    26.         heavyAttackText.SetActive(false);
    27.         lightAttackText.SetActive(false);
    28.     }
    29.  
    30.     // Loop calling StopMovement() each frame
    31.     private void FixedUpdate()
    32.     {
    33.         StopMovement();
    34.     }
    35.  
    36.     // Function stops unit movement, called when unit's x is +/- d from enemy's x
    37.     private void StopMovement()
    38.     {
    39.         float enemyx = enemy.transform.position.x;
    40.         float unitx = unit.transform.position.x;
    41.         decimal enemyxd = Convert.ToDecimal(enemyx);
    42.         decimal unitxd = Convert.ToDecimal(unitx);
    43.         decimal d = 1m;
    44.  
    45.         if (unitxd >= (enemyxd - d) && unitxd <= (enemyxd + d))
    46.         {
    47.             BasicUnitMovement.sbool = true;
    48.         }
    49.     }
    50.  
    51.     // Combat loop **
    52.     private void CombatLoop()
    53.     {  
    54.         float enemyx = enemy.transform.position.x;
    55.         float unitx = unit.transform.position.x;
    56.         decimal enemyxd = Convert.ToDecimal(enemyx);
    57.         decimal unitxd = Convert.ToDecimal(unitx);
    58.         decimal d = 1m;
    59.  
    60.         // If chain cycling between heavy and light attacks, called when unit's x is +/- 1 from enemy's x
    61.         /*
    62.          * Attack once per second, every third attack is a heavy attack
    63.          * Seperate special attack with unique cooldown per unit
    64.          */
    65.         if (unitxd >= (enemyxd - d) && unitxd <= (enemyxd + d))
    66.         {
    67.             FlipFlopOneSecond();
    68.  
    69.             if (cooldown == 3m && stamina == 6m)
    70.             {
    71.                 specialAttackText.SetActive(true);
    72.                 cooldown -= 3m;
    73.                 stamina -= 6m;
    74.             }
    75.             else if (cooldown == 3m)
    76.             {
    77.                 heavyAttackText.SetActive(true);
    78.                 cooldown -= 3m;
    79.             }
    80.             else
    81.             {
    82.                 lightAttackText.SetActive(true);
    83.             }
    84.         }
    85.     }
    86.  
    87.     // Flip flop function for starting OneSecondTimer, called at first enemy contact
    88.     private void FlipFlopOneSecond()
    89.     {
    90.         int num = 0;
    91.         num++;
    92.         if (num == 0)
    93.         {
    94.             StartCoroutine(OneSecondTimer());
    95.         }
    96.        
    97.     }
    98.  
    99.     IEnumerator CombatTimer()
    100.     {
    101.         CombatLoop();
    102.         yield return new WaitForSeconds(1);
    103.     }
    104.  
    105.     IEnumerator OneSecondTimer()
    106.     {
    107.         CooldownFunction();
    108.         StaminaFunction();
    109.         yield return new WaitForSeconds(1);
    110.     }
    111.  
    112.     // if chain increasing cooldown by 1 per second to a max of 3
    113.     private void CooldownFunction()
    114.     {
    115.         if (cooldown < 0m)
    116.         {
    117.             cooldown = 0m;
    118.         }
    119.         else if (cooldown >= 0m && cooldown < 3m)
    120.         {
    121.             ++cooldown;
    122.         }
    123.         else if (cooldown == 3m)
    124.         {
    125.         }
    126.         else if (cooldown > 3m)
    127.         {
    128.             cooldown = 3m;
    129.         }
    130.     }
    131.  
    132.     // if chain increasing stamina by 2 per second to a maximum of 10
    133.     private void StaminaFunction()
    134.     {
    135.         if (stamina < 0m)
    136.         {
    137.             stamina = 0m;
    138.         }
    139.         else if (stamina >= 0m && stamina < 10m)
    140.         {
    141.             ++stamina;
    142.             ++stamina;
    143.         }
    144.         else if (stamina == 10m)
    145.         {
    146.         }
    147.         else if (stamina > 10m)
    148.         {
    149.             stamina = 10m;
    150.         }
    151.     }
    152.  
    153. }
    154.  
    155.  
    Thank you!
     
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,954
    Sounds like it is ...

    Time to start debugging! Here is how you can begin your exciting new debugging adventures:

    You must find a way to get the information you need in order to reason about what the problem is.

    Once you understand what the problem is, you may begin to reason about a solution to the problem.

    What is often happening in these cases is one of the following:

    - the code you think is executing is not actually executing at all
    - the code is executing far EARLIER or LATER than you think
    - the code is executing far LESS OFTEN than you think
    - the code is executing far MORE OFTEN than you think
    - the code is executing on another GameObject than you think it is
    - you're getting an error or warning and you haven't noticed it in the console window

    To help gain more insight into your problem, I recommend liberally sprinkling
    Debug.Log()
    statements through your code to display information in realtime.

    Doing this should help you answer these types of questions:

    - is this code even running? which parts are running? how often does it run? what order does it run in?
    - what are the names of the GameObjects or Components involved?
    - what are the values of the variables involved? Are they initialized? Are the values reasonable?
    - are you meeting ALL the requirements to receive callbacks such as triggers / colliders (review the documentation)

    Knowing this information will help you reason about the behavior you are seeing.

    You can also supply a second argument to Debug.Log() and when you click the message, it will highlight the object in scene, such as
    Debug.Log("Problem!",this);


    If your problem would benefit from in-scene or in-game visualization, Debug.DrawRay() or Debug.DrawLine() can help you visualize things like rays (used in raycasting) or distances.

    You can also call Debug.Break() to pause the Editor when certain interesting pieces of code run, and then study the scene manually, looking for all the parts, where they are, what scripts are on them, etc.

    You can also call GameObject.CreatePrimitive() to emplace debug-marker-ish objects in the scene at runtime.

    You could also just display various important quantities in UI Text elements to watch them change as you play the game.

    Visit Google for how to see console output from builds. If you are running a mobile device you can also view the console output. Google for how on your particular mobile target, such as this answer for iOS: https://forum.unity.com/threads/how-to-capturing-device-logs-on-ios.529920/ or this answer for Android: https://forum.unity.com/threads/how-to-capturing-device-logs-on-android.528680/

    If you are working in VR, it might be useful to make your on onscreen log output, or integrate one from the asset store, so you can see what is happening as you operate your software.

    Another useful approach is to temporarily strip out everything besides what is necessary to prove your issue. This can simplify and isolate compounding effects of other items in your scene or prefab.

    If your problem is with OnCollision-type functions, print the name of what is passed in!

    Here's an example of putting in a laser-focused Debug.Log() and how that can save you a TON of time wallowing around speculating what might be going wrong:

    https://forum.unity.com/threads/coroutine-missing-hint-and-error.1103197/#post-7100494

    "When in doubt, print it out!(tm)" - Kurt Dekker (and many others)

    Note: the
    print()
    function is an alias for Debug.Log() provided by the MonoBehaviour class.
     
    bwool likes this.
  3. bwool

    bwool

    Joined:
    Mar 30, 2023
    Posts:
    20
    Thanks for your response, Kurt! This is definitely something I've been needing to learn.

    I plugged in Debugs through the script, and saw that everything's is being called up until the If statement on line 65. None of the nested if statements or the Stamina/Cooldown timers are going off because of this.

    Would you be able to tell why this If statement isn't functioning? The same proximity detection if statement is successfully used on line 45 to stop movement, so I'm unsure why it is not working on 65. I'll keep working on it.
     
  4. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,954
    Now you're getting somewhere!!!

    Looking further down, looks like you're comparing floating point numbers for equality. That's never gonna work. Here's why:

    Floating (float) point imprecision:

    Never test floating point (float) quantities for equality / inequality. Here's why:

    https://starmanta.gitbooks.io/unitytipsredux/content/floating-point.html

    https://forum.unity.com/threads/debug-log-2000-0f-000-1f-is-200.1153397/#post-7399994

    https://forum.unity.com/threads/why-doesnt-this-code-work.1120498/#post-7208431

    "Think of [floating point] as JPEG of numbers." - orionsyndrome on the Unity3D Forums

    You also should not use
    Decimal
    (unless you have some justified need). Just use
    float
    .
     
  5. bwool

    bwool

    Joined:
    Mar 30, 2023
    Posts:
    20
    Woohoo!

    I had read about the limitations of comparing floats by inequality, which is why I had tried to convert to decimal, thinking that would solve the issue. But I guess that's not right.

    So, the better way to approach this would then be to retain the vectors as floats but modify the conditions on my if statements to include only greater than and less than statements, and no equivalency statements?
     
  6. bwool

    bwool

    Joined:
    Mar 30, 2023
    Posts:
    20
    I've removed all equality statements and reverted all decimals back to floats, but I have the exact same functions not firing. So I either did that wrong, or that wasn't the issue.

    The logic in all the code seems correct to me, but maybe it's a syntax error or something?

    Code:
    Code (CSharp):
    1. using System;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using Unity.VisualScripting;
    5. using UnityEngine;
    6. using static BasicUnitMovement;
    7.  
    8. public class BasicCombatController : MonoBehaviour
    9. {
    10.     // Object and variable declarations
    11.     public GameObject unit;
    12.     public GameObject enemy;
    13.     public GameObject heavyAttackText;
    14.     public GameObject lightAttackText;
    15.     public GameObject specialAttackText;
    16.  
    17.     private float cooldown = 3f;
    18.     private float stamina = 10f;
    19.  
    20.     // starts combat loop
    21.     private void Start()
    22.     {
    23.         StartCoroutine(CombatTimer());
    24.  
    25.         specialAttackText.SetActive(false);
    26.         heavyAttackText.SetActive(false);
    27.         lightAttackText.SetActive(false);
    28.     }
    29.  
    30.     // Loop calling StopMovement() each frame
    31.     private void FixedUpdate()
    32.     {
    33.         StopMovement();
    34.     }
    35.  
    36.     // Function stops unit movement, called when unit's x is +/- d from enemy's x
    37.     private void StopMovement()
    38.     {
    39.         float enemyx = enemy.transform.position.x;
    40.         float unitx = unit.transform.position.x;
    41.         float d = 1f;
    42.  
    43.         if (unitx > (enemyx - d) && unitx < (enemyx + d))
    44.         {
    45.             BasicUnitMovement.sbool = true;
    46.             Debug.Log("StopMovement");
    47.         }
    48.        
    49.     }
    50.  
    51.     // Combat loop **
    52.     private void CombatLoop()
    53.     {
    54.         Debug.Log("CombatLoop0");
    55.         float enemyx = enemy.transform.position.x;
    56.         float unitx = unit.transform.position.x;
    57.         float d = 1f;
    58.  
    59.         // If chain cycling between heavy and light attacks, called when unit's x is +/- 1 from enemy's x
    60.         /*
    61.          * Attack once per second, every third attack is a heavy attack
    62.          * Seperate special attack with unique cooldown per unit
    63.          */
    64.         if (unitx > (enemyx - d) && unitx < (enemyx + d))
    65.         {
    66.             Debug.Log("CombatLoop1");
    67.             FlipFlopOneSecond();
    68.  
    69.             if (cooldown > 2f && stamina > 9f)
    70.             {
    71.                 specialAttackText.SetActive(true);
    72.                 cooldown -= 3f;
    73.                 stamina -= 6f;
    74.                 Debug.Log("special.Attack");
    75.             }
    76.             else if (cooldown > 2f && stamina < 10f)
    77.             {
    78.                 heavyAttackText.SetActive(true);
    79.                 cooldown -= 3f;
    80.                 Debug.Log("heavy.Attack");
    81.             }
    82.             else if (cooldown < 3f && stamina < 10f)
    83.             {
    84.                 lightAttackText.SetActive(true);
    85.                 Debug.Log("light.Attack");
    86.             }
    87.         }
    88.     }
    89.  
    90.     // Flip flop function for starting OneSecondTimer, called at first enemy contact
    91.     private void FlipFlopOneSecond()
    92.     {
    93.         int num = 0;
    94.         num++;
    95.         if (num == 0)
    96.         {
    97.             StartCoroutine(OneSecondTimer());
    98.         }
    99.         Debug.Log("FlipFlop");
    100.     }
    101.  
    102.     IEnumerator CombatTimer()
    103.     {
    104.         CombatLoop();
    105.         yield return new WaitForSeconds(1);
    106.         Debug.Log("CombatTimer");
    107.     }
    108.  
    109.     IEnumerator OneSecondTimer()
    110.     {
    111.         CooldownFunction();
    112.         StaminaFunction();
    113.         yield return new WaitForSeconds(1);
    114.         Debug.Log("OneSecondTimer");
    115.     }
    116.  
    117.     // if chain increasing cooldown by 1 per second to a max of 3
    118.     private void CooldownFunction()
    119.     {
    120.         Debug.Log("CoolDownFunction");
    121.         if (cooldown < 0f)
    122.         {
    123.             cooldown = 0f;
    124.         }
    125.         else if (cooldown > -1f && cooldown < 3f)
    126.         {
    127.             ++cooldown;
    128.         }
    129.         else if (cooldown > 2f && cooldown < 4f)
    130.         {
    131.         }
    132.         else if (cooldown > 3f)
    133.         {
    134.             cooldown = 3f;
    135.         }
    136.     }
    137.  
    138.     // if chain increasing stamina by 2 per second to a maximum of 10
    139.     private void StaminaFunction()
    140.     {
    141.         Debug.Log("StaminaFunction");
    142.         if (stamina < 0f)
    143.         {
    144.             stamina = 0f;
    145.         }
    146.         else if (stamina > -1f && stamina < 10f)
    147.         {
    148.             ++stamina;
    149.             ++stamina;
    150.         }
    151.         else if (stamina > 9f && stamina < 11f)
    152.         {
    153.         }
    154.         else if (stamina > 10f)
    155.         {
    156.             stamina = 10f;
    157.         }
    158.     }
    159.  
    160. }
    161.  
     
  7. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,954
    Start printing those values going into those expressions... one of them isn't what you think, or else the aggregate of the expression isn't what you expect.

    Ideally your if statements should never be more complex than:

    Code (csharp):
    1. if (a > b)
    2. {
    3.   // do stuff for when a is greater than b
    4. }
    If you have more-complex things, distill them into temporary bool values and collapse it down step by step.

    The logic WILL work, you just have to get it correct.

    If you have more than one or two dots (.) in a single statement, you're just being mean to yourself.

    How to break down hairy lines of code:

    http://plbm.com/?p=248

    Break it up, practice social distancing in your code, one thing per line please.

    "Programming is hard enough without making it harder for ourselves." - angrypenguin on Unity3D forums

    "Combining a bunch of stuff into one line always feels satisfying, but it's always a PITA to debug." - StarManta on the Unity3D forums
     
  8. bwool

    bwool

    Joined:
    Mar 30, 2023
    Posts:
    20
    After lots of rewriting and debugging, I'm making progress. Most of my functions are firing, and the unit is starting to behave as expected.

    The flood of categorized DebugLogs and having my stamina, cooldown, and movement bool actually showing in-game as text are very useful tools. I think I should be able to work it out from here on. Thanks for all your help Kurt!
     
    Kurt-Dekker likes this.