Search Unity

AI Coroutine causes the editor to freeze

Discussion in 'Scripting' started by BTCallahan, Jun 24, 2017.

  1. BTCallahan

    BTCallahan

    Joined:
    Jun 6, 2015
    Posts:
    71
    Okay, this has bee driving me insane. My AI script started out using a state machine, but that quickly became a pain to read, so I switched to using coroutines. The problem is that it freezes with I try to run it. To be specific, it pauses on precisely one of the many Debug.Break() methods that I set in the code, then freezes when I press the step button. The problem, I suspect, has to do the method iEnumeratorChain that I wrote.


    iEnumeratorChain works like this: First an IEnumerator variable is declared in the body of the class, and two or more IEnumerator methods are defined. When the Start method is called, the value of the IEnumerator variable is set to the first IEnumerator methods. StartCoroutine is then called with the IEnumerator variable as an argument, starting the first coroutine. This coroutine checks one or more conditions, and if the condition(s) is/are true, iEnumeratorChain is called with the second IEnumerator method passed into it as a parameter. iEnumeratorChain then stops the coroutine that called it by calling StopCoroutine with the IEnumerator variable as an argument, then setting the value of the IEnumerator variable as the parameter that was passed in, and then calling StartCoroutine with the IEnumerator variable as the argument.


    The following is an example:
    Code (CSharp):
    1. bool Alive = true;
    2. bool condition = false;
    3. IEnumerator mainIEnumerator;
    4.  
    5. IEnumerator checkConditions(){
    6.    while(Alive){
    7.         if(condition){
    8.             iEnumeratorChain(doSomethingIfConditionIsTrue());
    9.         }
    10.     }
    11. }
    12.  
    13. IEnumerator doSomethingIfConditionIsTrue(){
    14.     while(Alive){
    15.         //do something
    16.         if(!condition){
    17.             iEnumeratorChain(checkConditions);
    18.         }
    19.     }
    20. }
    21.  
    22. void iEnumeratorChain(IEnumerator iEnumerator){
    23.     StopCoroutine(mainIEnumerator);
    24.     mainIEnumerator = iEnumerator;
    25.     StartCoroutine(mainIEnumerator);
    26. }
    27.  
    28. void Start(){
    29.     mainIEnumerator = checkConditions();
    30.     StartCoroutine(mainIEnumerator);
    31. }
    In my effort to debug the code I placed several Debug.Log and Debug.Break functions. The output for the logs is shown below:

    Beginning check jump
    Starting Jump
    Looking For Target
    Layermask to use to find target 8192, searching for object on this layer 13 using this layermask 8192
    Target aquired while in air
    About to stop current behavourCoroutine
    About to restart behavourCoroutine
    Moving into a good shooting position
    Reaching end of loop for move in to good position
    Reaching end of loop for looking for target

    Then it hits one of the breaks and pauses, even though it should have paused after it hit the first debug.break after “Beginning check jump”. Hitting the step button then causes it to freeze.

    As using a rigid body for character movement would be counter productive, the script uses a character controller. The settings for it are as follows:

    Slope limit: 45
    Step offset: 0.3
    Skin width: 0.1
    Min move distance: 0.001
    Center X: 0 Y: 0 Z: 0
    Radius: 0.5
    Height: 2

    I have tried to keep the script as readable as possible, but the complexity of what I need it to do limits my ability to do so. For the sake of readability I have placed most non-coroutine methods at the end of the script. Everything related to the sprite renders or animators can be safely ignored. Here it is:
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. [RequireComponent(typeof(CharacterController))]
    6. public class EnemyGrunt : EnemyEntity, IDualBody {
    7.  
    8.     public CharacterController Con;
    9.  
    10.     public enum GruntType{
    11.         Red,//faster reload speed
    12.         Green,//unusial Weapon Type
    13.         Blue,////extra hits
    14.         Yellow//no extra atributes
    15.     };
    16.  
    17.     private static Directions[] SidescrollDirections = { Directions.SidescrollLeft, Directions.SidescrollRight };
    18.  
    19.     private static Directions[] OverheadDirections = {
    20.         Directions.OverheadUp,
    21.         Directions.OverheadUpRight,
    22.         Directions.OverheadRight,
    23.         Directions.OverheadDownRight,
    24.         Directions.OverheadDown,
    25.         Directions.OverheadDownLeft,
    26.         Directions.OverheadLeft,
    27.         Directions.OverheadUpLeft
    28.     };
    29.        
    30.     public GruntType Grunt_Type;
    31.  
    32.     private float _reloadSpeed;
    33.     private bool _flippedSprite;
    34.  
    35.     public Animator BodyAnimator, LegsAnimator;
    36.     public SpriteRenderer BodyRenderer, LegsRenderer;
    37.  
    38.     public SpriteColorAjuster BodyColorAjuster, LegColorAjuster;
    39.  
    40.     public WeaponMono MainWeapon, OffWeapon;
    41.  
    42.     private WeaponMono _usedWeapon;
    43.  
    44.     private CharState _state = CharState.Default;
    45.  
    46.     private Pathfindng _actionToTakeAtTargetPosition;
    47.  
    48.     private Directions _facingDirection;
    49.  
    50.     private Vector3 _startingPosition;
    51.  
    52.     void OnCollisionEnter(Collision collision){
    53.  
    54.         if (collision.gameObject.layer == layerOfPossibleTarget) {
    55.             TakeDamage (10, transform.position);
    56.         }
    57.     }
    58.  
    59.     public float GetStandingHeight{
    60.         get{
    61.             return 2f;
    62.         }
    63.     }
    64.  
    65.     public float GetJumpHeight{
    66.         get{
    67.             return 1.75f;
    68.         }
    69.     }
    70.  
    71.     public float GetCrouchHeight{
    72.         get{
    73.             return 1f;
    74.         }
    75.     }
    76.  
    77.     void Start(){
    78.         _startingPosition = transform.position;
    79.         //Debug.Log ("Started");
    80.         GrabGlobalController ();
    81.  
    82.         Initalize ();
    83.     }
    84.  
    85.     public void Initalize(){
    86.         _facingDirection = RandomArray.RandomArrayEntry<Directions> (SidescrollDirections);
    87.  
    88.         transform.position = _startingPosition;
    89.  
    90.         if (_facingDirection.FlipSpriteOrNot < 0) {
    91.             GetSetFlippedSprite = true;
    92.         } else {
    93.             GetSetFlippedSprite = false;
    94.         }
    95.  
    96.         checkLayers();
    97.         currentHits = MaxHits;
    98.  
    99.         if (Grunt_Type == GruntType.Blue)
    100.             currentHits += 10;
    101.  
    102.         if (Grunt_Type == GruntType.Green) {
    103.             _usedWeapon = OffWeapon;
    104.         } else {
    105.             _usedWeapon = MainWeapon;
    106.         }
    107.  
    108.         if (Grunt_Type == GruntType.Red) {
    109.             _reloadSpeed = _usedWeapon.GetRapidReloadTime;
    110.         } else {
    111.             _reloadSpeed = _usedWeapon.ReloadTime;
    112.         }
    113.  
    114.         _usedWeapon.Controller = Controller;
    115.  
    116.         StartCoroutine (checkJump ());
    117.  
    118.         targetCoroutine = detectionCheck ();
    119.         StartCoroutine (targetCoroutine);
    120.  
    121.         if (!Con.isGrounded) {
    122.  
    123.             _state = CharState.InAir;
    124.  
    125.             //jumpCoroutine = startJump ();
    126.  
    127.             StartCoroutine (jumpCoroutine);
    128.         } else {
    129.             _state = CharState.Default;
    130.         }
    131.  
    132.         moveToThisPosition = infVector;
    133.         behavourCoroutine = lookingForTarget ();
    134.         StartCoroutine (behavourCoroutine);
    135.     }
    136.  
    137.     /// <summary>
    138.     /// Starts the jump for fall. While the character controllers isGrounded value is false, it will continue.
    139.     /// </summary>
    140.     /// <returns>An IEnumerator.</returns>
    141.     /// <param name="fallspeed">The characters y axis movment. If a value greater then -9.8 is used for a "jump", everytime the loop runs it will decrease by
    142.     /// 0.1 until it is less then or equal to -9.8. The defult values is -1.5.</param>
    143.     /// <param name="xDirection">X direction. Used for directional jumps, that is, almost every time the entity is falling.</param>
    144.     /// <param name="zDirection">Z direction. Used for directional jumps only when in overhead mode.</param>
    145.     public IEnumerator startJump(float fallspeed=-1.5f, float xDirection=0f, float zDirection=0f){
    146.         Debug.Log ("Starting jump");
    147.         Debug.Break ();
    148.  
    149.         //Debug.DebugBreak ();
    150.  
    151.         _state = CharState.InAir;
    152.  
    153.         Con.height = GetJumpHeight;
    154.  
    155.         BodyAnimator.SetBool ("InAir", true);
    156.         LegsAnimator.SetBool ("InAir", true);
    157.  
    158.         //fallSpeed = fallspeed;
    159.  
    160.         if (fallspeed > 0f) {
    161.             Con.SimpleMove (new Vector3(xDirection, Mathf.Clamp(fallspeed, -0.15f, 0.15f), zDirection) * globalSpeed);
    162.         }
    163.  
    164.         while (!Con.isGrounded) {
    165.  
    166.             if (fallspeed < -9.8f) {
    167.                 fallspeed = -9.8f;
    168.             } else if (fallspeed > -9.8f) {
    169.                 fallspeed -= 0.1f;
    170.             }
    171.  
    172.             Con.SimpleMove (new Vector3(xDirection, Mathf.Clamp(fallspeed, -0.15f, 0.15f), zDirection) * globalSpeed);
    173.  
    174.             yield return null;
    175.         }
    176.  
    177.         BodyAnimator.SetBool ("InAir", false);
    178.         LegsAnimator.SetBool ("InAir", false);
    179.         Debug.Log ("Character is grounded, ending jump");
    180.  
    181.         Con.height = GetStandingHeight;
    182.         _state = CharState.Default;
    183.     }
    184.  
    185.     /// <summary>
    186.     /// While the character is alive, checks if the character has just transition from being
    187.     /// grounded to being in the air, and if so begins the startJump coroutine.
    188.     /// </summary>
    189.     /// <returns>An IEnumerator.</returns>
    190.     public IEnumerator checkJump(){
    191.         Debug.Log ("Beginning check jump");
    192.         Debug.Break ();
    193.         while (Alive) {
    194.             if (!Con.isGrounded && _state != CharState.InAir) {
    195.  
    196.                 jumpCoroutine = startJump ();
    197.                 yield return StartCoroutine (jumpCoroutine);
    198.             }
    199.             yield return null;
    200.         }
    201.     }
    202.  
    203.     public void endJumpOrFall(){//ignore this method, it's no longer used,
    204.         Debug.Log ("Ending jump//fall");
    205.         StopCoroutine (jumpCoroutine);
    206.         BodyAnimator.SetBool ("InAir", false);
    207.         LegsAnimator.SetBool ("InAir", false);
    208.         _state = CharState.Default;
    209.         Con.height = GetStandingHeight;
    210.     }
    211.  
    212.     public void FireWeapon(Directions dirDirection){
    213.         if (firingDelay <= 0f) {
    214.  
    215.             amnoLeft--;
    216.  
    217.             if (dirDirection.MoveDirection == Vector3.zero) {
    218.  
    219.                 dirDirection = _facingDirection;
    220.             }
    221.  
    222.             BodyAnimator.SetTrigger ("Fire");
    223.             _usedWeapon.Fire (dirDirection, projectileLayer, transform.position, dirDirection.MoveDirection != Vector3.zero, OverheadMode, _state, 0);
    224.  
    225.             if (amnoLeft <= 0) {
    226.                     firingDelay = _reloadSpeed;
    227.                 amnoLeft = _usedWeapon.Amno;
    228.             } else {
    229.                 firingDelay = _usedWeapon.TimeBetweenShots;
    230.             }
    231.         }
    232.     }
    233.  
    234.     protected IEnumerator fireInPlace(Directions fireDirection){
    235.         Debug.Log ("Firing in place");
    236.         Debug.Break ();
    237.         bool firstFiring = true;
    238.         while(Alive){
    239.  
    240.             if (Con.isGrounded) {
    241.  
    242.                 if (!firstFiring) {
    243.                     if (!hasTarget) {
    244.  
    245.                         iEnumeratorChain (lookingForTarget ());
    246.                     }
    247.  
    248.                     if (!checkIfInRange ) {
    249.  
    250.                         iEnumeratorChain (moveInToRange ());
    251.                     }
    252.  
    253.                     fireDirection = checkFiringSolution ();
    254.  
    255.                     if (CheckShot (fireDirection) != Pathfindng.Clear || CheckShot (fireDirection) != Pathfindng.DestroyableObjectInWay) {
    256.  
    257.                         iEnumeratorChain (moveInToGoodPosition ());
    258.  
    259.                     } else if (checkFiringSolution () == Directions.SidescrollUp || checkFiringSolution () == Directions.SidescrollDown) {
    260.  
    261.                         iEnumeratorChain (moveInToGoodPosition ());
    262.                     }
    263.                     firstFiring = false;
    264.                 }
    265.  
    266.                 BodyAnimator.SetFloat ("MovementBlendX", fireDirection.AnimatorVector.x);
    267.                 BodyAnimator.SetFloat ("MovementBlendY", fireDirection.AnimatorVector.y);
    268.  
    269.                 LegsAnimator.SetFloat ("MovementBlendX", fireDirection.AnimatorVector.x);
    270.  
    271.                 FireWeapon (fireDirection);
    272.             }
    273.             Debug.Log ("Reaching end of loop for fire in place");
    274.             Debug.Break ();
    275.  
    276.             yield return null;
    277.         }
    278.     }
    279.  
    280.     private IEnumerator fireOnce(Directions fireDirection){
    281.         Debug.Log ("Firing once");
    282.         Debug.Break ();
    283.  
    284.         if (firingDelay > 0f)
    285.             yield return new WaitForSeconds (firingDelay);
    286.  
    287.         FireWeapon (fireDirection);
    288.  
    289.         yield return new WaitForSeconds (_usedWeapon.TimeBetweenShots);//if _usedWeapon has been comented out, use 0.25f for the WaitForSeconds
    290.     }
    291.  
    292.     private IEnumerator moveToNewPosition(Vector3 position, IEnumerator behavour){
    293.         Debug.Log ("Moving to a new position");
    294.         Debug.Break ();
    295.  
    296.         moveToThisPosition = position;
    297.         byte timeLimit = 0;
    298.         while (Alive && timeLimit < 200){
    299.             if (Con.isGrounded) {
    300.  
    301.                 Con.SimpleMove (_facingDirection.MoveDirection * MoveSpeed * globalSpeed);
    302.  
    303.                 BodyAnimator.SetFloat ("MovementBlendX", _facingDirection.AnimatorVector.x);
    304.                 BodyAnimator.SetFloat ("MovementBlendY", _facingDirection.AnimatorVector.y);
    305.  
    306.                 LegsAnimator.SetFloat ("MovementBlendX", _facingDirection.AnimatorVector.x);
    307.                 LegsAnimator.SetFloat ("MovementBlendY", _facingDirection.AnimatorVector.y);
    308.  
    309.                 timeLimit++;
    310.  
    311.                 if (Vector3.Distance (transform.position, moveToThisPosition) < 0.5f) {
    312.  
    313.                     //stop
    314.                     //Debug.Log("Stopping movment");
    315.                     moveToThisPosition = infVector;
    316.                     BodyAnimator.SetFloat ("MovementBlendX", Directions.Center.AnimatorVector.x);
    317.                     BodyAnimator.SetFloat ("MovementBlendY", Directions.Center.AnimatorVector.y);
    318.  
    319.                     LegsAnimator.SetFloat ("MovementBlendX", Directions.Center.AnimatorVector.x);
    320.                     LegsAnimator.SetFloat ("MovementBlendY", Directions.Center.AnimatorVector.y);
    321.  
    322.                     iEnumeratorChain (behavour);
    323.                 }
    324.             }
    325.             Debug.Log ("Reaching end of loop for move to new position");
    326.             Debug.Break ();
    327.             yield return null;
    328.         }
    329.  
    330.         if(timeLimit >= 200){
    331.             Debug.Log ("Time limit exceeded");
    332.             Debug.Break ();
    333.             if (hasTarget) {
    334.                 if (checkIfInRange ) {
    335.  
    336.                     iEnumeratorChain (moveInToGoodPosition ());
    337.                     //ieum move to good position
    338.                 } else {
    339.                     iEnumeratorChain (moveInToRange ());
    340.                 }
    341.             } else {
    342.                 iEnumeratorChain (lookingForTarget ());
    343.             }
    344.         }
    345.     }
    346.  
    347.     private IEnumerator moveInToGoodPosition(){
    348.         Debug.Log ("Moving into a good shooting position");
    349.         Debug.Break ();
    350.  
    351.         while(Alive){
    352.  
    353.             if (Con.isGrounded) {
    354.                 if (!checkIfInRange ) {
    355.                     iEnumeratorChain (moveInToRange ());
    356.                 } else if (!hasTarget) {
    357.                     iEnumeratorChain (lookingForTarget ());
    358.                 }
    359.  
    360.                 Vector3 dif = target.transform.position - transform.position;
    361.                 Directions dir = sidescrollDirectionToTarget ();
    362.                 float remainder;
    363.  
    364.                 Directions diri = checkFiringSolution ();
    365.                 bool goodPosition = false;
    366.  
    367.                 switch (diri.Identifier) {
    368.                 case "SUL":
    369.                 case "SUR":
    370.                 case "SDL":
    371.                 case "SDR":
    372.                 case "SL":
    373.                 case "SR":
    374.                     goodPosition = CheckShot (diri) == Pathfindng.Clear;
    375.                     break;
    376.                 default:
    377.                     break;
    378.                 }
    379.  
    380.                 if (goodPosition) {
    381.                     iEnumeratorChain (fireInPlace (diri));
    382.                 }
    383.  
    384.                 PathfinderPosition path;
    385.  
    386.                 if (Mathf.Abs (dif.x) > Mathf.Abs (dif.y)) {//if the x distance is greater then the y distance
    387.  
    388.                     remainder = dif.x - dif.y;
    389.  
    390.                     if (dif.x > 0) {
    391.                         path = CheckMovement (Directions.SidescrollRight, remainder);
    392.                     } else {
    393.                         path = CheckMovement (Directions.SidescrollLeft, remainder);
    394.                     }
    395.                     switch (path.PathType) {
    396.                     case Pathfindng.Clear:
    397.                         iEnumeratorChain (moveToNewPosition (path.Position, moveInToGoodPosition ()));
    398.                         break;
    399.                     case Pathfindng.CanJumpOver:
    400.  
    401.                         jumpCoroutine = startJump (4.9f * 1f * globalSpeed, dir.MoveDirection.x);
    402.                         yield return StartCoroutine (jumpCoroutine);
    403.  
    404.                         Debug.Log ("Jump finished, resuming moving to good shooting position");
    405.                         Debug.Break ();
    406.                         break;
    407.                     //move to position
    408.                     case Pathfindng.DestroyableObjectInWay:
    409.                         yield return StartCoroutine (fireOnce (diri));
    410.                         Debug.Log ("Fired, resuming moving to good shooting position");
    411.                         Debug.Break ();
    412.                         break;
    413.                     default:
    414.                     //use method "if the y distance is greater then the x distance"
    415.                         remainder = Mathf.Abs (dif.x);
    416.  
    417.                         if (dif.y > 0f) {//if target is higher then entity
    418.                             if (dif.x > 0f) {//if target is to the right of entity
    419.  
    420.                                 path = CheckMovement (Directions.SidescrollUpRight, remainder);
    421.                             } else {//if target is to the left of entity
    422.  
    423.                                 path = CheckMovement (Directions.SidescrollUpLeft, remainder);
    424.                             }
    425.                         } else {//if target is lower then the entity
    426.  
    427.                             if (dif.x > 0f) {//if target is to the right of entity
    428.  
    429.                                 path = CheckMovement (Directions.SidescrollDownRight, remainder);
    430.  
    431.                             } else {
    432.  
    433.                                 path = CheckMovement (Directions.SidescrollDownLeft, remainder);
    434.                             }
    435.                         }
    436.                         break;
    437.                     }
    438.                    
    439.                 } else {//if the y distance is greater then the x distance
    440.                     remainder = Mathf.Abs (dif.x);
    441.  
    442.                     if (dif.y > 0f) {//if target is higher then entity
    443.  
    444.                         if (dif.x > 0f) {//if target is to the upper right of entity
    445.  
    446.                             path = CheckMovement (Directions.SidescrollUpRight, remainder);
    447.                         } else {//if target is to the upper left of entity
    448.  
    449.                             path = CheckMovement (Directions.SidescrollUpLeft, remainder);
    450.                         }
    451.                     } else {//if target is lower then the entity
    452.                         if (dif.x > 0f) {//if target is to the lower right of entity
    453.  
    454.                             path = CheckMovement (Directions.SidescrollDownRight, remainder);
    455.                         } else {//if target is to the lower left of entity
    456.  
    457.                             path = CheckMovement (Directions.SidescrollDownLeft, remainder);
    458.                         }
    459.                     }
    460.                 }
    461.  
    462.                 switch (path.PathType) {
    463.                 case Pathfindng.Clear:
    464.                 case Pathfindng.OutOfRange:
    465.                     iEnumeratorChain (moveToNewPosition (path.Position, moveInToGoodPosition ()));
    466.                     break;
    467.  
    468.                 case Pathfindng.CanJumpOver:
    469.                     jumpCoroutine = startJump (4.9f * 1f * globalSpeed, dir.MoveDirection.x);
    470.                     yield return StartCoroutine (jumpCoroutine);
    471.                     Debug.Log ("Jump finished, resuming moving to good shooting position");
    472.                     Debug.Break ();
    473.                     break;
    474.                 case Pathfindng.DestroyableObjectInWay:
    475.                 //fire
    476.                     yield return StartCoroutine (fireOnce (_facingDirection));
    477.                     Debug.Log ("Fired, resuming moving to good shooting position");
    478.                     Debug.Break ();
    479.                     break;
    480.                 default:
    481.                     iEnumeratorChain (moveToNewPosition (path.Position, moveInToGoodPosition ()));
    482.                     break;
    483.                 }
    484.             }
    485.             Debug.Log ("Reaching end of loop for move in to good position");
    486.             Debug.Break ();
    487.             yield return null;
    488.         }
    489.     }
    490.  
    491.     private IEnumerator moveInToRange(){
    492.         Debug.Log ("Moving into range");
    493.         Debug.Break ();
    494.  
    495.         moveToThisPosition = infVector;
    496.         while(Alive){
    497.  
    498.             if (Con.isGrounded) {
    499.  
    500.                 if (!hasTarget) {
    501.                     iEnumeratorChain (lookingForTarget ());
    502.                 } else if (checkIfInRange ) {
    503.                     iEnumeratorChain (moveInToGoodPosition ());
    504.                 }
    505.  
    506.                 Directions dir = sidescrollDirectionToTarget ();
    507.  
    508.                 PathfinderPosition pos = CheckMovement (dir, 10f);
    509.  
    510.                 switch (pos.PathType) {
    511.                 case Pathfindng.Clear:
    512.                 case Pathfindng.OutOfRange:
    513.  
    514.                     if (Vector3.Distance (transform.position, moveToThisPosition) < 0.5f) {//stop
    515.                         moveToThisPosition = pos.Position;
    516.                         BodyAnimator.SetFloat ("MovementBlendX", Directions.Center.AnimatorVector.x);
    517.                         BodyAnimator.SetFloat ("MovementBlendY", Directions.Center.AnimatorVector.y);
    518.  
    519.                         LegsAnimator.SetFloat ("MovementBlendX", Directions.Center.AnimatorVector.x);
    520.                         LegsAnimator.SetFloat ("MovementBlendY", Directions.Center.AnimatorVector.y);
    521.                     } else {
    522.                         Con.SimpleMove (_facingDirection.MoveDirection * MoveSpeed * globalSpeed);
    523.  
    524.                         BodyAnimator.SetFloat ("MovementBlendX", _facingDirection.AnimatorVector.x);
    525.                         BodyAnimator.SetFloat ("MovementBlendY", _facingDirection.AnimatorVector.y);
    526.  
    527.                         LegsAnimator.SetFloat ("MovementBlendX", _facingDirection.AnimatorVector.x);
    528.                         LegsAnimator.SetFloat ("MovementBlendY", _facingDirection.AnimatorVector.y);
    529.                     }
    530.                     break;
    531.                 case Pathfindng.WallInWay:
    532.                 case Pathfindng.DangerousHazard:
    533.                 case Pathfindng.BottomlessPit:
    534.                     if (dir != sidescrollDirectionToTarget ()) {
    535.                        
    536.                         iEnumeratorChain (fireInPlace (_facingDirection));
    537.                     } else {
    538.                         Pathfindng path = calculateResultOfJump (Con, dir.MoveDirection.x, JumpHeight * 4.9f, dir.MoveDirection.z);
    539.  
    540.                         if (path == Pathfindng.Clear) {
    541.                             moveToThisPosition = infVector;
    542.                             jumpCoroutine = startJump (dir.MoveDirection.x, JumpHeight * 4.9f);
    543.                             yield return StartCoroutine (jumpCoroutine);
    544.                         } else {
    545.  
    546.                             _facingDirection = randomDirection ();
    547.                         }
    548.                     }
    549.                     break;
    550.                 case Pathfindng.DestroyableObjectInWay:
    551.                     yield return StartCoroutine(fireOnce (dir));
    552.                     break;
    553.                 default:
    554.                     break;
    555.                 }
    556.             }
    557.             Debug.Log ("Reacing end of loop for move into range");
    558.             Debug.Break ();
    559.  
    560.             yield return null;
    561.         }
    562.     }
    563.  
    564.     private IEnumerator lookingForTarget(){
    565.  
    566.         Debug.Log ("Looking For Target");
    567.         Debug.LogFormat ("Layermask to use to find target {0}, searching for object on this layer {1} using this layermask {2}",
    568.             1<<GlobalController.LAYER_PLAYER, layerOfPossibleTarget, 1<<layerOfPossibleTarget);
    569.  
    570.         while (Alive) {
    571.             //Debug.Log ("Alive");
    572.  
    573.             if (Con.isGrounded) {
    574.  
    575.                 if (hasTarget) {
    576.                     Debug.Log ("Target aquired");
    577.                     iEnumeratorChain (moveInToGoodPosition ());
    578.                 }else if (moveToThisPosition == infVector) {
    579.  
    580.                     Debug.Log ("Assigning new position");
    581.  
    582.                     switch (_actionToTakeAtTargetPosition) {
    583.                     case Pathfindng.Clear:
    584.                     case Pathfindng.OutOfRange:
    585.                         Directions dir = randomDirection ();
    586.  
    587.                         PathfinderPosition pos = CheckMovement (dir, DetectionRadius);
    588.  
    589.                         _actionToTakeAtTargetPosition = pos.PathType;
    590.  
    591.                         moveToThisPosition = pos.Position;
    592.                         break;
    593.                     case Pathfindng.WallInWay:
    594.                     case Pathfindng.DangerousHazard:
    595.                     case Pathfindng.BottomlessPit:
    596.  
    597.                         _facingDirection = randomDirection ();
    598.  
    599.                         if (_facingDirection.FlipSpriteOrNot < 0) {
    600.                             GetSetFlippedSprite = true;
    601.                         } else {
    602.                             GetSetFlippedSprite = false;
    603.                         }
    604.                         _actionToTakeAtTargetPosition = Pathfindng.Clear;
    605.                         break;
    606.  
    607.                     case Pathfindng.CanJumpOver:
    608.  
    609.                         jumpCoroutine = startJump (JumpHeight * 4.9f, _facingDirection.MoveDirection.x);
    610.                         yield return StartCoroutine (jumpCoroutine);
    611.  
    612.                         _actionToTakeAtTargetPosition = Pathfindng.Clear;
    613.  
    614.                         break;
    615.                     case Pathfindng.DestroyableObjectInWay:
    616.  
    617.                         yield return StartCoroutine (fireOnce (_facingDirection));
    618.                         _actionToTakeAtTargetPosition = Pathfindng.Clear;
    619.  
    620.                         break;
    621.                     default:
    622.                         _actionToTakeAtTargetPosition = Pathfindng.Clear;
    623.                         break;
    624.                     }
    625.                 } else {
    626.                     Debug.Log ("Try and move to new position");
    627.                     iEnumeratorChain (moveToNewPosition (moveToThisPosition, lookingForTarget ()));
    628.                 }
    629.             } else {
    630.                 if (hasTarget) {
    631.                     Debug.Log ("Target aquired while in air");
    632.                     iEnumeratorChain (moveInToGoodPosition ());
    633.                 } else {
    634.                    
    635.                     Debug.Log ("In air");
    636.                 }
    637.             }
    638.             Debug.Log ("Reaching end of loop for looking for target");
    639.             Debug.Break ();
    640.  
    641.             yield return null;
    642.         }
    643.     }
    644.  
    645.     void FixedUpdate(){
    646.         if (firingDelay > 0f) {
    647.             firingDelay -= Time.deltaTime;
    648.         }
    649.         //Debug.LogFormat ("State: {0}, grounded: {1}", _state, Con.isGrounded);
    650.  
    651.     }
    652.  
    653.     /// <summary>
    654.     /// Examins the diffrence between the targets position and the transform position.
    655.     /// </summary>
    656.     /// <returns>A static Directions object.</returns>
    657.     Directions checkFiringSolution(){
    658.  
    659.         Debug.Log ("Checking for firing solution");
    660.         Debug.Break ();
    661.  
    662.         Vector3 diffrence = target.transform.position - transform.position;
    663.         float dif = Mathf.Abs (Mathf.Abs (diffrence.x) - Mathf.Abs (diffrence.y));
    664.         float overheadDif =  (Mathf.Abs (diffrence.x) - Mathf.Abs (diffrence.z));
    665.         /* Assume that overheadDif = Mathf.Abs(8.2) - Mathf.Abs(-0.3)
    666.          * overheadDif = 7.9
    667.          *
    668.          */
    669.         if (OverheadMode) {
    670.  
    671.             if (Mathf.Abs (overheadDif) < 1f) {
    672.  
    673.                 if (diffrence.x > 0f) {//if targ is on right side
    674.  
    675.                     if (diffrence.z > 0f) {
    676.                         return Directions.OverheadUpRight;
    677.                     } else {
    678.                         return Directions.OverheadDownRight;
    679.                     }
    680.                 } else {//if targ is on the left side
    681.  
    682.                     if (diffrence.z > 0f) {
    683.                         return Directions.OverheadUpLeft;
    684.                     } else {
    685.                         return Directions.OverheadDownLeft;
    686.                     }
    687.                 }
    688.             } else {
    689.  
    690.                 if (overheadDif > 0f) {//if it's on the x axis
    691.                     if (diffrence.x > 0) {//if it's to the right
    692.                         if (diffrence.z > -0.5f && diffrence.z < 0.5f)
    693.                             return Directions.OverheadRight;
    694.                     } else {//if it's to the left
    695.                         if (diffrence.z > -0.5f && diffrence.z < 0.5f)
    696.                             return Directions.OverheadLeft;
    697.                     }
    698.                 } else {//if it's on the z axis
    699.                     if (diffrence.z > 0f) {//if it's forward
    700.                         if (diffrence.x > -0.5f && diffrence.x < 0.5f)
    701.                             return Directions.OverheadUp;
    702.                     } else {
    703.                         if (diffrence.x > -0.5f && diffrence.x < 0.5f)
    704.                             return Directions.OverheadDown;
    705.                     }
    706.                 }
    707.             }
    708.             return Directions.Center;
    709.         } else {
    710.  
    711.             if (diffrence.x > 0f) {//if targ is on right side
    712.  
    713.                 if (diffrence.y > -0.5f && diffrence.y < 0.5f)
    714.                     return Directions.SidescrollRight;
    715.  
    716.                 if (dif < 1.25f) {
    717.  
    718.                     if (diffrence.y > 0f)
    719.                         return Directions.SidescrollUpRight;
    720.  
    721.                     return Directions.SidescrollDownRight;
    722.  
    723.                 } else {
    724.                     if (diffrence.y > 0f)
    725.                         return Directions.SidescrollUp;
    726.  
    727.                     return Directions.SidescrollDown;
    728.                 }
    729.             } else {//if targ is on left side
    730.  
    731.                 if (diffrence.y > -0.5f && diffrence.y < 0.5f)
    732.                     return Directions.SidescrollLeft;
    733.  
    734.                 if (dif < 1.25f) {
    735.  
    736.                     if (diffrence.y > 0f)
    737.                         return Directions.SidescrollUpLeft;
    738.  
    739.                     return Directions.SidescrollDownLeft;
    740.  
    741.                 } else {
    742.                     if (diffrence.y > 0f)
    743.                         return Directions.SidescrollUp;
    744.  
    745.                     return Directions.SidescrollDown;
    746.                 }
    747.             }
    748.         }
    749.     }
    750.  
    751.     private Directions RemoveUpOrDownDirection(Directions dir){
    752.  
    753.         if (_flippedSprite) {
    754.  
    755.             switch (dir.Identifier) {
    756.             case "SU":
    757.                 return Directions.SidescrollUpLeft;
    758.             case "C":
    759.                 return Directions.SidescrollLeft;
    760.             case "SD":
    761.                 return Directions.SidescrollDownLeft;
    762.             default:
    763.                 return dir;
    764.             }
    765.         }else{
    766.             switch(dir.Identifier){
    767.             case "SU":
    768.                 return Directions.SidescrollUpRight;
    769.             case "C":
    770.                 return Directions.SidescrollRight;
    771.             case "SD":
    772.                 return Directions.SidescrollDownRight;
    773.             default:
    774.                 return dir;
    775.             }
    776.         }
    777.     }
    778.  
    779.     public PathfinderPosition CheckMovement(Directions direction, float maxDistance){
    780.  
    781.         Debug.Log ("Checking movment");
    782.         Debug.Break ();
    783.         int hitLayers = 1 << GlobalController.GetCollisionLayerMask;
    784.  
    785.         Vector3 retPosition = transform.position;
    786.  
    787.         for (int i = 2; i < maxDistance; i += 2) {
    788.  
    789.             RaycastHit hit;
    790.  
    791.             retPosition = transform.position + direction.MoveDirection * i;
    792.  
    793.             if (Physics.Linecast (transform.position, retPosition, out hit, hitLayers)) {
    794.                 RaycastHit newHit;
    795.  
    796.                 switch (hit.collider.gameObject.layer) {
    797.                 case GlobalController.LAYER_DESTROYABLE_TERRAIN:
    798.  
    799.                     if (!Physics.Linecast (retPosition + Directions.SidescrollUp.MoveDirection * 2f, retPosition + Directions.SidescrollUp.MoveDirection * 2f + direction.MoveDirection * 2f, out newHit, hitLayers)) {
    800.                         //may be able to jump over
    801.                         return new PathfinderPosition(Pathfindng.CanJumpOver, retPosition - direction.MoveDirection * 2f);
    802.                     } else {
    803.                         return new PathfinderPosition (Pathfindng.DestroyableObjectInWay, retPosition - direction.MoveDirection * 2f);
    804.                     }
    805.  
    806.                 case GlobalController.LAYER_TERRAIN:
    807.  
    808.                     if (!Physics.Linecast (retPosition + Directions.SidescrollUp.MoveDirection * 2f, retPosition + Directions.SidescrollUp.MoveDirection * 2f + direction.MoveDirection * 2f, out newHit, hitLayers)) {
    809.                         //may be able to jump over
    810.                         return new PathfinderPosition(Pathfindng.CanJumpOver, retPosition - direction.MoveDirection * 2f);
    811.  
    812.                     } else {
    813.                         return new PathfinderPosition (Pathfindng.WallInWay, retPosition - direction.MoveDirection * 2f);
    814.                     }
    815.  
    816.                 case GlobalController.LAYER_DEATHZONE:
    817.                     return new PathfinderPosition (Pathfindng.DangerousHazard, retPosition - direction.MoveDirection * 2f);
    818.  
    819.                 default:
    820.                     break;
    821.                 }
    822.             }
    823.             if (CheckBottomlessPit (retPosition))
    824.                 return new PathfinderPosition (Pathfindng.BottomlessPit, retPosition - direction.MoveDirection * 2f);
    825.         }
    826.         return new PathfinderPosition (Pathfindng.Clear, retPosition);
    827.     }
    828.  
    829.     private Directions sidescrollDirectionToTarget(){
    830.  
    831.         if(target == null || target.Alive)
    832.             return Directions.Center;
    833.  
    834.         if (target.transform.position.x > transform.position.x)
    835.             return Directions.SidescrollRight;
    836.  
    837.         if (target.transform.position.x < transform.position.x)
    838.             return Directions.SidescrollLeft;
    839.  
    840.         return Directions.Center;
    841.     }
    842.  
    843.     public Pathfindng CheckShot(Directions direction){
    844.  
    845.         int maxDistance = Mathf.RoundToInt (_usedWeapon.Proj.GetSpeedTimesLifetime);//replace with 7
    846.  
    847.         RaycastHit hit;
    848.  
    849.         if (!Physics.Linecast (transform.position, transform.position + direction.MoveDirection * maxDistance, out hit, 1<<layerOfPossibleTarget | GlobalController.GetShotCollisionLayerMask))
    850.             return Pathfindng.OutOfRange;
    851.  
    852.         if (hit.collider.gameObject.layer == layerOfPossibleTarget)
    853.             return Pathfindng.Clear;
    854.  
    855.         if (hit.collider.gameObject.layer == GlobalController.LAYER_TERRAIN)
    856.             return Pathfindng.WallInWay;
    857.  
    858.         if (hit.collider.gameObject.layer == GlobalController.LAYER_DESTROYABLE_TERRAIN)
    859.             return Pathfindng.DestroyableObjectInWay;
    860.  
    861.         return Pathfindng.OutOfRange;
    862.     }
    863.  
    864.     public bool CheckBottomlessPit(Vector3 pos){
    865.  
    866.         RaycastHit hit;
    867.         if (Physics.Linecast (pos, pos + new Vector3 (0f, -20f, 0f), out hit, 1 << GlobalController.LAYER_DESTROYABLE_TERRAIN | 1 << GlobalController.LAYER_TERRAIN | 1 << GlobalController.LAYER_DEATHZONE)) {
    868.  
    869.             if (hit.collider.gameObject.layer == GlobalController.LAYER_DEATHZONE)
    870.                 return true;
    871.             return false;
    872.         }
    873.         return true;
    874.     }
    875.  
    876.     public override void die (Vector3 hitPosition, byte playerNo = 0)
    877.     {
    878.         target = null;
    879.         if (playerNo != 0) {
    880.             Controller.GetPlayer (playerNo).AddToScore = ScoreValue;
    881.         }
    882.  
    883.         if (!Con.isGrounded) {
    884.  
    885.             StopCoroutine (jumpCoroutine);
    886.         }
    887.  
    888.         Alive = false;
    889.  
    890.         if (hitPosition.x > transform.position.x) {
    891.  
    892.             jumpCoroutine = startJump (4.9f, -2.5f);
    893.         } else {
    894.  
    895.             jumpCoroutine = startJump (4.9f, 2.5f);
    896.         }
    897.         StartCoroutine (jumpCoroutine);
    898.     }
    899.  
    900.     public override void TakeDamage (byte damage, Vector3 hitPosition,byte playerNo = 0)
    901.     {
    902.         if (currentHits - damage < 0) {
    903.             die (transform.position, playerNo);
    904.         } else {
    905.             currentHits -= damage;
    906.         }
    907.     }
    908.  
    909.     public void CopyInfo(EnemyGrunt grunt){//coment out the body of the script
    910.  
    911.         Grunt_Type = grunt.Grunt_Type;
    912.         MainWeapon = grunt.MainWeapon;
    913.         OffWeapon = grunt.OffWeapon;
    914.         gameObject.layer = grunt.gameObject.layer;
    915.         Initalize ();
    916.     }
    917.  
    918.     public byte GetHits {
    919.         get{
    920.             return currentHits;
    921.         }
    922.     }
    923.  
    924.     public bool GetSetFlippedSprite{
    925.         get{
    926.             return _flippedSprite;
    927.         }
    928.         set{
    929.             _flippedSprite = value;
    930.             BodyRenderer.flipX = value;
    931.             LegsRenderer.flipX = value;
    932.         }
    933.     }
    934.  
    935.     private Directions randomDirection(){
    936.         if(OverheadMode){
    937.             switch (_facingDirection.Identifier) {
    938.             case "OU":
    939.                 return RandomArray.RandomArrayEntry (Directions.OverheadDown, Directions.OverheadLeft, Directions.OverheadRight, Directions.OverheadDownLeft, Directions.OverheadDownRight);
    940.             case "OUR":
    941.                 return RandomArray.RandomArrayEntry (Directions.OverheadDownLeft, Directions.OverheadDownRight, Directions.OverheadUpLeft, Directions.OverheadDown, Directions.OverheadLeft);
    942.             case "OR":
    943.                 return RandomArray.RandomArrayEntry (Directions.OverheadLeft, Directions.OverheadDown, Directions.OverheadUp, Directions.OverheadDownLeft, Directions.OverheadUpLeft);
    944.             case "ODR":
    945.                 return RandomArray.RandomArrayEntry (Directions.OverheadUpLeft, Directions.OverheadDownLeft, Directions.OverheadDownRight, Directions.OverheadUp, Directions.OverheadLeft);
    946.             case "OD":
    947.                 return RandomArray.RandomArrayEntry (Directions.OverheadUp, Directions.OverheadLeft, Directions.OverheadRight, Directions.OverheadUpLeft, Directions.OverheadUpRight);
    948.             case "ODL":
    949.                 return RandomArray.RandomArrayEntry (Directions.OverheadUpRight, Directions.OverheadDownRight, Directions.OverheadUpLeft, Directions.OverheadUp, Directions.OverheadRight);
    950.             case "OL":
    951.                 return RandomArray.RandomArrayEntry (Directions.OverheadRight, Directions.OverheadDown, Directions.OverheadUp, Directions.OverheadUpRight, Directions.OverheadDownRight);
    952.             case "OUL":
    953.                 return RandomArray.RandomArrayEntry (Directions.OverheadDownRight, Directions.OverheadUpRight, Directions.OverheadUpLeft, Directions.OverheadDown, Directions.OverheadRight);
    954.             default:
    955.                 return Directions.Center;
    956.             }
    957.         }else{
    958.             if (_facingDirection.AnimatorVector.x > 0f)
    959.                 return Directions.SidescrollLeft;
    960.             if (_facingDirection.AnimatorVector.x < 0f)
    961.                 return Directions.SidescrollRight;
    962.             return RandomArray.RandomArrayEntry (Directions.SidescrollLeft, Directions.SidescrollRight);
    963.         }
    964.     }
    965.  
    966.     bool checkIfInRange {
    967.         get {
    968.             if (!target)
    969.                 return false;
    970.             if (!target.Alive)
    971.                 return false;
    972.             return Vector3.Distance (target.transform.position, transform.position) < _usedWeapon.Proj.GetSpeedTimesLifetime;
    973.         }
    974.     }
    975. }
    Right now I'm just hoping the someone can tell me if I'm not using coroutines correctly, or if I've made one of those mistakes which is obvious in hindsight.
     
  2. Billy4184

    Billy4184

    Joined:
    Jul 7, 2014
    Posts:
    6,015
    Your while loops will likely freeze the editor since they never yield, and the frame will not be allowed to finish. A while loop in a coroutine should pretty much always be accompanied by a 'yield return null' inside the loop, which is the equivalent of saying "wait until the next frame before looping again".

    Code (CSharp):
    1. IEnumerator SomeCoroutine()
    2. {
    3.     while (someBool){
    4.  
    5.         // Your code
    6.  
    7.         // Without this the loop will continue forever in the current frame and freeze
    8.         yield return null;
    9.  
    10.     }
    11. }
     
    Kiwasi likes this.
  3. BTCallahan

    BTCallahan

    Joined:
    Jun 6, 2015
    Posts:
    71
    Are you sure? I looked over all of the IEumerator methods in the class, and they all have a yield return null at the end of the loop. The only exception is the method detectionCheck() which uses a yield return new WaitForSeconds (TimeBetweenDetectionChecks) instead, with TimeBetweenDetectionChecks be equal to 0.1. The reason detectionCheck isn't in the EnemyGrunt class is because that it's located in the abstract EnemyEntity class that EnemyGrunt inherits from. Could that be causing it?
    Code (CSharp):
    1.     protected IEnumerator detectionCheck(){
    2.         //Debug.Log ("Begun detection check");
    3.  
    4.         while (Alive) {
    5.  
    6.             if(target == null){
    7.                
    8.                 Collider[] hostileColliders = Physics.OverlapSphere (transform.position, DetectionRadius, 1<<layerOfPossibleTarget);
    9.                 if (hostileColliders.Length > 0) {
    10.                    
    11.                     //Debug.LogFormat ("{0} hostiles detected on layer {1}", hostileColliders.Length, layerOfPossibleTarget);
    12.  
    13.                     float cloestDist = DetectionRadius;
    14.                     foreach (Collider col in hostileColliders) {
    15.                         if (Vector3.Distance (transform.position, col.transform.position) < cloestDist) {
    16.                             BasicEntity entity = col.GetComponent<BasicEntity> ();
    17.                             if (entity.Alive) {
    18.  
    19.                                 cloestDist = Vector3.Distance (transform.position, col.transform.position);
    20.  
    21.                                 target = col.gameObject.GetComponent<BasicEntity> ();
    22.                             }
    23.                         }
    24.                     }
    25.                 }
    26.             } else {
    27.                 if (!target.Alive || Vector3.Distance (transform.position, target.transform.position) > DetectionRadius) {
    28.                     target = null;
    29.                     break;
    30.                 }
    31.             }
    32.             yield return new WaitForSeconds (TimeBetweenDetectionChecks);
    33.         }
    34.     }
     
  4. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,353
    Code (CSharp):
    1.  // ....
    2. IEnumerator checkConditions(){
    3.    while(Alive){
    4.         if(condition){
    5.             iEnumeratorChain(doSomethingIfConditionIsTrue());
    6.         }
    7.     }
    8. }
    9.  
    This construction makes me shiver...
    What would happen if the condition is false? (Answer: An infinite loop which will freeze Unity)
     
    Kiwasi likes this.
  5. BTCallahan

    BTCallahan

    Joined:
    Jun 6, 2015
    Posts:
    71
    But Unity's official tutorial on coroutines uses a while loop, and the thing is, if the conditions false then I dan't want the coroutine to do anything except what for the next frame. Also, the yield return null statement is telling Unity to stop and wait for the next frame, so it shouldn't be causing it to freeze.
     
  6. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,353
    @BTCallahan
    When 'Alive' is true and 'condition' is false, the checkConditions function is equivalent to:
    Code (CSharp):
    1. while(true){
    2. }
    3.  
    No yield statements. An infinite loop that will cause Unity to freeze.

    Not sure if that's causing your actual problem, but it is a good candidate.
     
    TaleOf4Gamers, xVergilx and Kiwasi like this.
  7. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    Ummm. That's not what you posted in the OP. You have two while loops there with no yields.
     
    Last edited: Jun 26, 2017
  8. ThermalFusion

    ThermalFusion

    Joined:
    May 1, 2011
    Posts:
    906
    You guys only read the example (which is missing yields) the actual code was posted further down in the op, and that seems to have them.
     
  9. Billy4184

    Billy4184

    Joined:
    Jul 7, 2014
    Posts:
    6,015
    The 'example' represents a set of methods in a different script that IS being called by the main script, yet the OP has not posted this other script except for the 'example'. Since the example doesn't have yields, it seems entirely possible that this mysterious other script doesn't either, and is crashing when called by the main script.

    Frankly, the main script looks like a nightmare to me. If it isn't a case of lack of yield, it's probably a case of recursive spawning of coroutines since there are a number of times that 'StartCoroutine' is being called inside a while loop, which seems like a horrible idea to me.

    OP, my best suggestion would be:
    • Create coroutine 'containers' at the top of your script for all of your coroutines such as jumpCoroutine;
    • Transfer all of your 'StartCoroutine' calls to a separate coroutine manager method which deals only with these containers^ (do not declare any new coroutines anywhere in your script), and which explicitly kills, reassigns and restarts these container coroutines;
    • Don't post confusing examples without yields, especially when this omission would cause the exact problem you're dealing with.
     
    Kiwasi likes this.
  10. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    Got ya. I just made the assumption the the OP would post example code that was representative of the actual problem.

    But the principle is the same. Unity only ever completely freezes on an infinite loop. Infinite loops are most commonly caused by missing yields in co routines, but they can also be caused by infinite recursion, messing with the iterator in a for loop, pathfinding that doesn't have a proper exit case and so on. Its also worth noting that excessive processing time, especially in a loop, can give the appearance of freezing Unity. Excessive debugging in a big loop can cause this.

    The actual code is a mess, an example of all sorts of bad ways to use coroutines. I'm not going to debug a thousand lines of code without getting paid. My suggestion to the OP would be to forget about the problem for a moment and fix the underlying code structure. Break things up into consistent and short methods. Break functionality out into separate classes.

    Then if the problem still occurs, it should be isolated by disabling all functionality, then turning it on one line at a time.
     
    cstooch and Billy4184 like this.
  11. Billy4184

    Billy4184

    Joined:
    Jul 7, 2014
    Posts:
    6,015
    Not to mention that spawning coroutines based on a slew of individual boolean conditions not really a great idea. Example:

    Code (CSharp):
    1. while (Alive) {
    2.        if (!Con.isGrounded && _state != CharState.InAir) {
    3.    
    4.            jumpCoroutine = startJump ();
    5.            yield return StartCoroutine (jumpCoroutine);
    6.       }
    7.      yield return null;
    8. }
    what if Con doesn't change it's grounded state immediately? I just had a look and startJump doesn't explicitly reset these conditions to prevent mass spawning. Really the whole architecture of the script needs to be reset in such a way that coroutines are spawned and killed in a dedicated method which checks for and handles all the possible issues that could cause recursion.
     
  12. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    In this case yielding a coroutine will mean that the loop won't cycle through again until the first coroutine returns. So its not the source of the infinite loop directly. That still doesn't make it easy to read or follow.
     
  13. Billy4184

    Billy4184

    Joined:
    Jul 7, 2014
    Posts:
    6,015
    Sure, my point is that it doesn't clearly prevent mass spawning, although it wouldn't be recursive. IMO for any script with a lot of coroutines (and/or coroutines that start and stop regularly) a manager method is a must - otherwise things get very messy very quickly.
     
  14. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,353
    Implementing complex logic using coroutines can quickly lead to unreadable and unmaintainable code, as you are experiencing. If you are looking for alternative techniques, I suggest to have a look at Behaviour Tree. It's an appropriate technique for implementing complex logics that runs over long period of time. If you are new to BT, have a look at those resources.
     
    Last edited: Jun 26, 2017
    BTCallahan and Kiwasi like this.
  15. BTCallahan

    BTCallahan

    Joined:
    Jun 6, 2015
    Posts:
    71
    The only coroutine that is being called that isn't defined in the main script is the method detectionCheck, which is defined in the abstract class EnemyEntity, which EnemyGrunt inherent from, and since I've tried the script with only it running I know that it isn't the problem. If you wan't me to post those classes, I'm happy to do so.


    Code (CSharp):
    1.     protected IEnumerator detectionCheck(){
    2.  
    3.         //Debug.Log ("Begun detection check");
    4.  
    5.  
    6.         while (Alive) {
    7.  
    8.  
    9.             if(target == null){
    10.  
    11.                
    12.  
    13.                 Collider[] hostileColliders = Physics.OverlapSphere (transform.position, DetectionRadius, 1<<layerOfPossibleTarget);
    14.  
    15.                 if (hostileColliders.Length > 0) {
    16.  
    17.                    
    18.  
    19.                     //Debug.LogFormat ("{0} hostiles detected on layer {1}", hostileColliders.Length, layerOfPossibleTarget);
    20.  
    21.  
    22.                     float cloestDist = DetectionRadius;
    23.  
    24.                     foreach (Collider col in hostileColliders) {
    25.  
    26.                         if (Vector3.Distance (transform.position, col.transform.position) < cloestDist) {
    27.  
    28.                             BasicEntity entity = col.GetComponent<BasicEntity> ();
    29.  
    30.                             if (entity.Alive) {
    31.  
    32.  
    33.                                 cloestDist = Vector3.Distance (transform.position, col.transform.position);
    34.  
    35.  
    36.                                 target = col.gameObject.GetComponent<BasicEntity> ();
    37.  
    38.                             }
    39.  
    40.                         }
    41.  
    42.                     }
    43.  
    44.                 }
    45.  
    46.             } else {
    47.  
    48.                 if (!target.Alive || Vector3.Distance (transform.position, target.transform.position) > DetectionRadius) {
    49.  
    50.                     target = null;
    51.  
    52.                     break;
    53.  
    54.                 }
    55.  
    56.             }
    57.  
    58.             yield return new WaitForSeconds (TimeBetweenDetectionChecks);
    59.  
    60.         }
    61.  
    62.     }

    1. I was actually already doing that. I have jumpCoroutine defined in the abstract class BasicEntity, and targetCoroutine and behavourCoroutine defined in EnemyEntity, and they act as containers for detectionCheck and lookingForTarget respectively (well sort of, behavourCoroutine acts as a container for which behavior script is needed at the moment).

    2. Again, I'm already doing that to an extent with the iEnumeratorChain method, which stops/kills the behavourCoroutine container, reassigns another behavior coroutine to it, and thens starts it. The Unity documentation mentions nothing about restarting coroutines, so I had no idea that it was even possible.

    3. Fatigue and two weeks worth of frustration with freezes - even after improving the the original code - make it hard to know when one's example is faulty.


    Code (CSharp):
    1. jumpCoroutine = startJump ();
    2.  
    3. yield return StartCoroutine (jumpCoroutine);
    4.  
    5.  
    I have trying to fix this on my own for over two weeks, looking at tutorials (Alan Zucconi has been quite helpful), searching the forums and Unity answers, and the shear frustration and from problem's continued reoccurrence, not to mention exhaustion, conspired against me when I wrote the example. Your sarcasm is completely unneeded, and borderline trollish to boot.

    I assumer when you say excessive debugging you are referring to the Dubug.Log/Dubug.Break calls rathe then the MonoDevelop debugger? If so, I'll have to get rid of most of the ones I've got. A pity, as they proven quite helpful in isolating where I suspect the problem is. What is really annoying is that only one of the Debug.Breaks have actually had any effect so I might as well get rid of those. The current state of the code is due to both my attempts at fixing the problem (which resulted following bad advice I found in Unity answers), and a lack of planning (which admittedly was my own fault). I'm not asking you to debug the thing, just identify bad coding practices, and most importantly, what to replace said practices with. I've thrown out code that doesn't work plenty of times before, heck when I started out with this class it used a state machine in the Update function. Breaking the functions up into smaller subfunctions looks promising, but as I've been told too man many function calls can cause slowdown.

    I'd reply to more posts, in this thread, but I gotta hit the sack.
     
  16. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    My apologies. No sarcasm or trolling was intended.

    I was trying to throw up a bunch of suggestions that can normally cause Unity to freeze. Scattered Debug.Log statements are fine. But if you try and put 1000 or so in a loop, Unity will appear to freeze. If you are sure there are no infinite loops in your code then a long loop might be the culprit.

    Slow and working beats fast and broken. Optimization is for later, after your core functionality is working.

    Complex chaining of coroutines is a bad practice. Coroutines are great for simple tasks, but they become incredibally hard to track and debug for complex ones. I'm sure you are now painfully aware of this.

    For moderately complex tasks, consider using a FSM. This works particularly well for things like character controllers that are already naturally tied to animation state machines.

    For highly complex tasks, consider a BT. There are plenty of decent frames works on the asset store.

    I would approach this by commenting everything out. Then add methods back one at a time, testing for your infinite loop each time. It will take a few hours this way, but you should be able to isolate where your infinite loop is occurring. Once you have that, post back with an isolated section of code that we can collectively debug.

    Rest is super important for debugging. Best of luck with this.
     
    ericbegue and ThermalFusion like this.
  17. Billy4184

    Billy4184

    Joined:
    Jul 7, 2014
    Posts:
    6,015
    @BTCallahan I second @BoredMormon's suggestion of starting your coroutines one at a time and testing thoroughly.

    However before you do that that, I think it would be a great idea to re-organise your code as a state machine. What makes me very uncomfortable is that there are coroutines being spawned deep inside if/else statements, where the conditions are individually quite ambiguous and not explicitly being reset to prevent spawning huge amounts of the same coroutine. What would help a lot is to completely quarantine not just the starting/stopping of the coroutines but also the conditional checks, according to individual states.

    Here's an example where you want to jump:

    1. Call a separate method to check the conditions for jump, e.g., CheckJumpConditions()

    2. EXPLICITLY enter the jump state in such a way that the jump coroutine cannot be called again straight away - e.g., currentState = State.Jump, where currentState is checked before spawning a new jump coroutine.

    3. If the jump coroutine fails for some reason, make it exit EXPLICITLY into the grounded state by calling a separate method, e.g. EnterGroundedState(), which kills any jump coroutines, sets the currentState to State.Grounded, etc

    I hope this is clear, basically you want to cleanly separate the states in such a way that you are always clearly in a state, and not just dependent on slews of boolean conditions happening to all align properly at any given time. It should help you to much more easily debug the code as well, e.g. you can then but a break point in the EnterJumpState method and then if you crash immediately afterward you know what's going on.
     
    Last edited: Jun 27, 2017
    Kiwasi likes this.
  18. BTCallahan

    BTCallahan

    Joined:
    Jun 6, 2015
    Posts:
    71
    Sorry for not replying until, now. Things have been kind of touct and go at my end.


    Excellent point. I had actually never heard about behavior trees until now. Thanks for the links, I'm looking into the links you provided. I feel like these



    No harm dome. Thanks for your advice so far, right now I'm working on transplanting (well, some of transplanted, some of it is going to completely rewritten) the pathfinding code into its own class. Part of the problem was that I found coroutines to be so useful, say when spawning a bunch of time-delayed randomly placed explosions in sphere-shaped area, or having a homing missile check for any nearby enemies every so often, that I just went overboard with them. Right now I think I might be able to get away with using a FSM by sacrificing some flexibility, but even if I don’t und up using it, reading up on how to use a BT is a good idea.

    Good points. I have something slightly similar for use in the class for player controlled gameObjects, but I didn't use it because of my (admittedly irrational) belief that using the Update/FixedUpdate function would eat up CPU cycles and my underestimation of how complex the coroutines would end up becoming. Implementing the "if character is grounded" should hopefully work just as well as does in the player class. However, right now reorganizing/rewriting the code is top priority, so it might a while before I get around to attacking the "on ground" problem.

    Big thanks to everyone who replied to my post. I feel like I have a much better ‘plan of attack’ now, and a greater understanding of not only whats causing the problem, but how to fix it.
     
    Billy4184 and Kiwasi like this.