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

Gameloop not loading/playing correctly?

Discussion in 'Scripting' started by ltrout1999, Jan 2, 2016.

  1. ltrout1999

    ltrout1999

    Joined:
    Oct 11, 2015
    Posts:
    125
    I created a basic Gameloop to tell the game to respawn both tanks, change the message text, and decide a winner. It does the first part fine, but the text stays stuck on the screen, the tanks (and music) are glitchy, stutter, and don't move, but they are able to shoot (the player's tank is, the enemy tank is not in range so it hasn't been able to shoot). The code looks fine to me and has no errors from what I can see. I'm not sure why it is doing this but here is the code:

    Code (csharp):
    1.  
    2. using System.Collections;
    3. using UnityEngine;
    4. using UnityEngine.UI;
    5. using PlayerInfo;
    6. using EnemyInfo;
    7.  
    8. namespace Management
    9. {
    10.     public class GameManager : MonoBehaviour
    11.     {
    12.         public int NumRoundsToWin = 5;            
    13.         public float StartDelay = 3f;          
    14.         public float EndDelay = 3f;              
    15.         public CameraControl CameraControl;    
    16.         public Text MessageText;                
    17.         public GameObject PlayerPrefab;      
    18.         public GameObject EnemyPrefab;
    19.         public TankManager[] Tanks;              
    20.  
    21.         private int RoundNumber;                
    22.         private WaitForSeconds StartWait;        
    23.         private WaitForSeconds EndWait;          
    24.         private TankManager RoundWinner;        
    25.         private TankManager GameWinner;          
    26.  
    27.         private void Start()
    28.         {
    29.             StartWait = new WaitForSeconds (StartDelay);
    30.             EndWait = new WaitForSeconds (EndDelay);
    31.  
    32.             SpawnAllTanks();
    33.             SetCameraTargets();
    34.  
    35.             StartCoroutine (GameLoop ());
    36.         }
    37.  
    38.         private void SpawnAllTanks()
    39.         {
    40.             for (int i = 0; i < Tanks.Length; i++)
    41.             {
    42.                 if (i == 0)
    43.                     Tanks[i].Instance = Instantiate (PlayerPrefab, Tanks[i].SpawnPoint.position, Tanks[i].SpawnPoint.rotation) as GameObject;
    44.                 else
    45.                     Tanks[i].Instance = Instantiate (EnemyPrefab, Tanks[i].SpawnPoint.position, Tanks[i].SpawnPoint.rotation) as GameObject;
    46.                
    47.                 Tanks[i].Setup();
    48.             }
    49.         }
    50.  
    51.         private void SetCameraTargets()
    52.         {
    53.             Transform[] targets = new Transform[Tanks.Length];
    54.  
    55.             for (int i = 0; i < targets.Length; i++)
    56.             {
    57.                 targets[i] = Tanks[i].Instance.transform;
    58.             }
    59.  
    60.             CameraControl.Targets = targets;
    61.         }
    62.  
    63.         private IEnumerator GameLoop ()
    64.         {
    65.             yield return StartCoroutine (RoundStarting ());
    66.  
    67.             yield return StartCoroutine (RoundPlaying());
    68.  
    69.             yield return StartCoroutine (RoundEnding());
    70.  
    71.             if (GameWinner != null)
    72.             {
    73.                 Application.LoadLevel (Application.loadedLevel);
    74.             }
    75.             else
    76.             {
    77.                 StartCoroutine (GameLoop ());
    78.             }
    79.         }
    80.  
    81.         private IEnumerator RoundStarting ()
    82.         {
    83.             ResetAllTanks ();
    84.             DisableTankControl ();
    85.  
    86.             CameraControl.SetStartPositionAndSize ();
    87.  
    88.             RoundNumber++;
    89.             MessageText.text = "ROUND " + RoundNumber;
    90.  
    91.             yield return StartWait;
    92.         }
    93.  
    94.         private IEnumerator RoundPlaying ()
    95.         {
    96.             EnableTankControl ();
    97.  
    98.             MessageText.text = string.Empty;
    99.  
    100.             while (!OneTankLeft())
    101.             {
    102.                 yield return null;
    103.             }
    104.         }
    105.  
    106.         private IEnumerator RoundEnding ()
    107.         {
    108.             // Stop tanks from moving.
    109.             DisableTankControl ();
    110.  
    111.             RoundWinner = null;
    112.  
    113.             RoundWinner = GetRoundWinner ();
    114.  
    115.             if (RoundWinner != null)
    116.                 RoundWinner.Wins++;
    117.  
    118.             GameWinner = GetGameWinner ();
    119.  
    120.             string message = EndMessage ();
    121.             MessageText.text = message;
    122.  
    123.             yield return EndWait;
    124.         }
    125.  
    126.         private bool OneTankLeft()
    127.         {
    128.             // Start the count of tanks left at zero.
    129.             int numTanksLeft = 0;
    130.  
    131.             for (int i = 0; i < Tanks.Length; i++)
    132.             {
    133.                 if (Tanks[i].Instance.activeSelf)
    134.                     numTanksLeft++;
    135.             }
    136.  
    137.             return numTanksLeft <= 1;
    138.         }
    139.  
    140.         private TankManager GetRoundWinner()
    141.         {
    142.             for (int i = 0; i < Tanks.Length; i++)
    143.             {
    144.                 if (Tanks[i].Instance.activeSelf)
    145.                     return Tanks[i];
    146.             }
    147.  
    148.             return null;
    149.         }
    150.  
    151.         private TankManager GetGameWinner()
    152.         {
    153.             for (int i = 0; i < Tanks.Length; i++)
    154.             {
    155.                 if (Tanks[i].Wins == NumRoundsToWin)
    156.                     return Tanks[i];
    157.             }
    158.  
    159.             return null;
    160.         }
    161.  
    162.         private string EndMessage()
    163.         {
    164.             string message = "DRAW!";
    165.  
    166.             if (RoundWinner != null)
    167.                 message = RoundWinner.ColoredPlayerText + " WINS THE ROUND!";
    168.  
    169.             // Add some line breaks after the initial message.
    170.             message += "\n\n\n\n";
    171.  
    172.             for (int i = 0; i < Tanks.Length; i++)
    173.             {
    174.                 message += Tanks[i].ColoredPlayerText + ": " + Tanks[i].Wins + " WINS\n";
    175.             }
    176.  
    177.             if (GameWinner != null)
    178.                 message = GameWinner.ColoredPlayerText + " WINS THE GAME!";
    179.  
    180.             return message;
    181.         }
    182.  
    183.         private void ResetAllTanks()
    184.         {
    185.             for (int i = 0; i < Tanks.Length; i++)
    186.             {
    187.                 Tanks[i].Reset();
    188.             }
    189.         }
    190.  
    191.         private void EnableTankControl()
    192.         {
    193.             for (int i = 0; i < Tanks.Length; i++)
    194.             {
    195.                 Tanks[i].EnableControl();
    196.             }
    197.         }
    198.  
    199.         private void DisableTankControl()
    200.         {
    201.             for (int i = 0; i < Tanks.Length; i++)
    202.             {
    203.                 Tanks[i].DisableControl();
    204.             }
    205.         }
    206.     }
    207. }
    208.  

    I will try to capture a short video of what it is doing when I press play just because it is hard to explain.
     
  2. ltrout1999

    ltrout1999

    Joined:
    Oct 11, 2015
    Posts:
    125


    There's a link to a video of the problem. Sorry that the video is super fast, I was having issues recording.
     
  3. ltrout1999

    ltrout1999

    Joined:
    Oct 11, 2015
    Posts:
    125
    Anyone know why?
     
  4. ltrout1999

    ltrout1999

    Joined:
    Oct 11, 2015
    Posts:
    125
    Bumping...
     
  5. ltrout1999

    ltrout1999

    Joined:
    Oct 11, 2015
    Posts:
    125
    Any help??
     
  6. apsdsm

    apsdsm

    Joined:
    Sep 26, 2013
    Posts:
    56
    I'm just stabbing in the dark here, but I think might because you're starting a game loop coroutine from inside the game loop coroutine.

    On a side note, can somebody explain to me why so many new coders are using coroutines like crazy with Unity lately? Which tutorial told them it was a good idea? Is this like the FixedUpdate for 2016? Or am I just missing the benefits here and I'll be using coroutines for everything next game?
     
  7. ltrout1999

    ltrout1999

    Joined:
    Oct 11, 2015
    Posts:
    125
    The reason I am using it is because the tutorial I followed uses it and it makes sense in a game like this.

    Also, that is the exact code that the Tanks! game has (expect for a minor portion I edited to spawn multiple different tanks) and it works flawlessly? Not sure why mine isn't working when the games do basically the same thing except mine is 1 vs CPU and theirs is 1 vs 1.
     
    Last edited: Jan 3, 2016
  8. apsdsm

    apsdsm

    Joined:
    Sep 26, 2013
    Posts:
    56
    So it's that Tanks! game that should be held responsible.

    Well, that aside, it certainly is weird. And yes, you're right, the code is the same as Tanks! so in retrospect, yeah, looks like that may not have anything to do with it.

    I can only suggest trying to remove parts of the game logic until you find the thing which is making everything stutter, which might make the issue easier to debug. Sorry I can't be of more help.
     
  9. ltrout1999

    ltrout1999

    Joined:
    Oct 11, 2015
    Posts:
    125
    Okay I'll try that. To me it seems like it has to do with the GameLoop. It does the first three lines of code in the RoundStarting method, then it just stops trying to do anything. I'll start removing parts and update if I find a fix.
     
  10. ltrout1999

    ltrout1999

    Joined:
    Oct 11, 2015
    Posts:
    125
    I've narrowed it down to this chunk of code:

    Code (csharp):
    1.  
    2. private IEnumerator RoundStarting ()
    3.         {
    4.             ResetAllTanks ();
    5.             DisableTankControl ();
    6.  
    7.             CameraControl.SetStartPositionAndSize ();
    8.  
    9.             RoundNumber++;
    10.             MessageText.text = "ROUND " + RoundNumber;
    11.  
    12.             yield return StartWait;
    13.         }
    14.  
    I'm going through that small piece now, but whenever I comment out the part that calls this function, the game runs smoothly for the most part, but the text doesn't change, and I have trouble moving. Whenever I only commented out the DisableTankControl() function, the game didn't stutter and I could move and everything, but the rounds would continually reset and continually go up without any winners.
     
  11. ltrout1999

    ltrout1999

    Joined:
    Oct 11, 2015
    Posts:
    125
    Okay, it is a problem with the Enemy Classes. It doesn't like the OnEnable / OnDisable function, but I don't know why. Here are the two classes:

    EnemyMovement:
    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4. using PlayerInfo;
    5. using Shell;
    6.  
    7. namespace EnemyInfo
    8. {
    9.     public class EnemyMovement : MonoBehaviour
    10.     {
    11.         public Transform ObjectToFollow;
    12.         public Transform target;
    13.         public Transform PlayerTank;
    14.         public Transform EnemyTank;
    15.         public int AttackRange = 35;
    16.         public bool inRange;
    17.         public int TooClose = 10;
    18.  
    19.         Transform player;
    20.         NavMeshAgent agent;
    21.         static PlayerHealth pHealth;
    22.         static EnemyHealth eHealth;
    23.         Rigidbody Rigidbody;
    24.  
    25.         void Awake ()
    26.         {
    27.             player = GameObject.FindWithTag ("Player").transform;
    28.             pHealth = player.GetComponent<PlayerHealth> ();
    29.             eHealth = GetComponent<EnemyHealth> ();
    30.             Rigidbody = GetComponent<Rigidbody> ();
    31.         }
    32.  
    33.         void OnEnable ()
    34.         {
    35.             Rigidbody.isKinematic = false;
    36.         }
    37.  
    38.  
    39.         void OnDisable ()
    40.         {
    41.             Rigidbody.isKinematic = true;
    42.         }
    43.  
    44.         void Update ()
    45.         {
    46.             NavMeshAgent agent = GetComponent<NavMeshAgent> ();
    47.  
    48.             transform.LookAt (target);
    49.  
    50.             if (pHealth.CurrentHealth > 0 && eHealth.CurrentHealth > 0)
    51.             {
    52.                 agent.SetDestination (player.position);
    53.             }
    54.             else
    55.             {
    56.                 agent.enabled = false;
    57.             }
    58.  
    59.             if (Vector3.Distance (PlayerTank.position, EnemyTank.position) < AttackRange) {
    60.                 if (Vector3.Distance (PlayerTank.position, EnemyTank.position) > TooClose) {
    61.                     inRange = true;
    62.                 }
    63.             }
    64.             else if (Vector3.Distance (PlayerTank.position, EnemyTank.position) > AttackRange)
    65.             {
    66.                 inRange = false;
    67.             }
    68.         }
    69.     }
    70. }
    71.  

    EnemyAttack:
    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4. using UnityEngine.UI;
    5. using PlayerInfo;
    6. using Shell;
    7.  
    8. namespace EnemyInfo
    9. {
    10.     public class EnemyAttack : MonoBehaviour
    11.     {
    12.         public float timeBetweenAttacks = 0.75f;
    13.         public float attackDamage;
    14.         public Rigidbody Shell;
    15.         public Transform FireTransform;
    16.         public AudioSource ShootingAudio;
    17.         public AudioClip Firing;
    18.         public float LaunchForce;
    19.  
    20.         GameObject player;
    21.         PlayerHealth pHealth;
    22.         EnemyHealth eHealth;
    23.         PlayerAttack pShoot;
    24.         EnemyAttack eShoot;
    25.         float timer;
    26.         EnemyMovement eMove;
    27.         ShellExplosion sExplo;
    28.  
    29.  
    30.         void OnEnable ()
    31.         {
    32.             player = GameObject.FindGameObjectWithTag ("Player");
    33.             pHealth = player.GetComponent<PlayerHealth> ();
    34.             eHealth = GetComponent<EnemyHealth> ();
    35.             eMove = GetComponent<EnemyMovement> ();
    36.             sExplo = GetComponent<ShellExplosion> ();
    37.  
    38.         }
    39.  
    40.         void Update ()
    41.         {
    42.             timer += Time.deltaTime;
    43.  
    44.             if (timer >= timeBetweenAttacks && eMove.inRange && eHealth.CurrentHealth > 0)
    45.             {
    46.                 Attack ();
    47.             }
    48.  
    49.         }
    50.  
    51.         void Attack ()
    52.         {
    53.             LaunchForce = Random.Range (15, 30);
    54.  
    55.             timer = 0f;
    56.  
    57.             Rigidbody shellInstance = Instantiate (Shell, FireTransform.position, FireTransform.rotation) as Rigidbody;
    58.  
    59.             shellInstance.velocity = LaunchForce * FireTransform.forward;
    60.  
    61.  
    62.         }
    63.     }
    64. }
    65.  

    The OnEnable/OnDisable function is almost identical between the two except in the EnemyAttack function. On the PlayerAttack it is just updating the slider which I did not code into the Enemy because I felt that would be pretty difficult, but I feel like I should now just to make the classes identical (with the exceptions of functions only needed for the enemy).
     
  12. ltrout1999

    ltrout1999

    Joined:
    Oct 11, 2015
    Posts:
    125
    I'm still having difficulties with this. I've recoded parts of the Enemy scripts, disabled TankManager from using it, and nothing. It is a problem in the RoundStarting() I know for sure and I also know it is a problem with the Enemy Tank, but I don't know what the problem is.
     
  13. apsdsm

    apsdsm

    Joined:
    Sep 26, 2013
    Posts:
    56
    Can you try running while using the debugger, and stepping through each of the suspicious parts of the code? You might be able to find something by checking out the variables along the way.
     
  14. ltrout1999

    ltrout1999

    Joined:
    Oct 11, 2015
    Posts:
    125
    Also, I have found it to be the DisableTankControl() function. Whenever I comment that out, no stutter or anything.

    I'm running the debugger now.

    The debugger isn't showing anything? When I run it and direct it to the RoundStarting() function, the stuttering goes away, but the game doesn't do anything but sit there playing the sounds.

    If I was to put the project on dropbox or something would you be able to identify the problem easier?
     
    Last edited: Jan 4, 2016
  15. apsdsm

    apsdsm

    Joined:
    Sep 26, 2013
    Posts:
    56
    Throw it up on Github - that's a good place to look at all the parts and how they work. Or else on dropbox if the files are too big and link it back here.
     
  16. ltrout1999

    ltrout1999

    Joined:
    Oct 11, 2015
    Posts:
    125
    Alright Here's the GitHub link.
     
  17. apsdsm

    apsdsm

    Joined:
    Sep 26, 2013
    Posts:
    56
    I've forked the repo - I'll have a look and see if I can reproduce the bug, or track it down.
     
  18. ltrout1999

    ltrout1999

    Joined:
    Oct 11, 2015
    Posts:
    125
    Okay. Thank you :)
     
  19. apsdsm

    apsdsm

    Joined:
    Sep 26, 2013
    Posts:
    56
    I sent you a pull request.

    The culprit was in the TankManager code. Basically, when you are initializing that component, you are asking it to fetch all of the PMove, PAttack, EMove, and EAttack components.

    Code (CSharp):
    1.             PMove = Instance.GetComponent<PlayerMovement> ();
    2.             PAttack = Instance.GetComponent<PlayerAttack> ();
    3.             EMove = Instance.GetComponent<EnemyMovement> ();
    4.             EAttack = Instance.GetComponent<EnemyAttack> ();
    However, the PAttack and PMove components only exist on the Player (I assume) and the EMove and EAttack only exist on the enemy (I assume). This means that each one of your tanks will have at least two null components.

    Later on, when you Enable or Disable control, calling the 'enable' flag on the components which are null is making your game bork.

    I fixed this by adding null checks, but you should consider that a quick fix - IMHO I think you should find a way to split the TankManager into a PlayerTankManager and an EnemyTankManager. Perhaps by inheriting from the base TankManager.

    Here's the code that I included in the pull request:

    Code (CSharp):
    1.         public void DisableControl ()
    2.         {
    3.             if ( PMove != null && PAttack != null )
    4.             {
    5.                 PMove.enabled = false;
    6.                 PAttack.enabled = false;
    7.             }
    8.  
    9.             if ( EMove != null && EAttack != null )
    10.             {
    11.                 EMove.enabled = false;
    12.                 EAttack.enabled = false;
    13.             }
    14.  
    15.             CanvasGameObject.SetActive( false );
    16.            
    17.         }
    18.  
    19.         public void EnableControl ()
    20.         {
    21.             if ( PMove != null && PAttack != null )
    22.             {
    23.                 PMove.enabled = true;
    24.                 PAttack.enabled = true;
    25.             }
    26.  
    27.             if ( EMove != null && EAttack != null )
    28.             {
    29.                 EMove.enabled = true;
    30.                 EAttack.enabled = true;
    31.             }
    32.  
    33.             CanvasGameObject.SetActive (true);
    34.         }
    I tested this after making the changes and it worked, but I didn't test it too in depth, so there are probably still bugs in there somewhere. In any case I hope this helps out!
     
  20. ltrout1999

    ltrout1999

    Joined:
    Oct 11, 2015
    Posts:
    125
    Yeah I just checked it as soon as I got the email. That makes sense now. And I did try making it into two separate classes at the beginning but I didn't know how to handle it in the OneTankLeft() function inside of GameManager because there would always be one tank left in each of those classes.
     
  21. apsdsm

    apsdsm

    Joined:
    Sep 26, 2013
    Posts:
    56
    If both PlayerTankManager and EnemyTankManager inherit from the same base class, then you can store them in a linked list or array that references the base class.

    But for now maybe just keep things as they are so you can get a better feel for other parts of the game mechanics and logi without getting bogged down in the core architecture.
     
  22. ltrout1999

    ltrout1999

    Joined:
    Oct 11, 2015
    Posts:
    125
    Okay so I've encountered one problem. The enemy does not turn and face the player and the enemy does not shoot. Which are each in one of the classes the TankManager uses (EnemyMovement and EnemyAttack)

    The Enemy just follows the NavMeshAgent and doesn't physically turn (look) at the player and doesn't attack when it is in range.
     
  23. ltrout1999

    ltrout1999

    Joined:
    Oct 11, 2015
    Posts:
    125
    Bumping this.
     
  24. ltrout1999

    ltrout1999

    Joined:
    Oct 11, 2015
    Posts:
    125
    Do you know why it's stopping the enemy from attacking when they are in range and why the enemy is just driving and not turning towards the player?
     
  25. apsdsm

    apsdsm

    Joined:
    Sep 26, 2013
    Posts:
    56
    I can take a look at the code later on, but probably won't have a chance for a few days, busy as heck here. In the meantime try debugging it and see if you can narrow it down a little. If I can figure it out I'll post here and send a pull request on github :)
     
  26. ltrout1999

    ltrout1999

    Joined:
    Oct 11, 2015
    Posts:
    125
    Alright thank you :).
     
  27. ltrout1999

    ltrout1999

    Joined:
    Oct 11, 2015
    Posts:
    125
    So I was debugging it, and it's saying that everything is going fine and that is is looking at the tank and is firing, but it isn't. When the game first starts it turns towards the tank and then stays at that rotation and just drives towards the tank at that point. Then, when it is in range, it doesn't shoot.


    Edit: I seem to have fixed it looking at the player. I changed (target) to (target.position), no luck. changed it to (player.position), fixed. Now to work on it shooting again.

    Edit 2: Seem to have fixed it shooting as well. It looks like it didn't like the word target too much so I changed it all to player.position, etc. Game works good again. Thank you for your help @apsdsm!!

    Final edit, I promise :p. I see why it wasn't liking target but liked player. Player was public while target was private so EnemyAttack wasn't actually able to find the target.position. But what's weird is it was working with that setup before I added the Managers. Anyhoo, it is fixed.
     
    Last edited: Jan 7, 2016
  28. apsdsm

    apsdsm

    Joined:
    Sep 26, 2013
    Posts:
    56
    Great!! I'm really glad to hear that!

    Did you update the code at GitHub? I would be cool to see the changes you made to fix this :)
     
  29. ltrout1999

    ltrout1999

    Joined:
    Oct 11, 2015
    Posts:
    125
    Yes, I actually had to put on a new repo because I was having issues with the folder on the last one. Here is the new project repo.

    The main thing I changed was I got rid of the Transform target in both the EnemyMovement and EnemyAttack class and replaced it with the public transform player.

    Also, I have a quick question. In the EnemyAttack function I made it pick a random number between 15-30 (the range the enemy can shoot) to come up with a range for the shell to fire because I didn't want to have the enemy hit the player every time, is there a way I can make it to where instead of it finding a random number it has a % of chances it will miss. Because I want to make 3 or 4 levels and have Easy have a 35% chance of missing but have hard/impossible only have a 1-5% chance of missing. I'm thinking if I just call the distance to the player tank from the enemy tank I would get a number but then I don't know how I would make the % of miss. Sorry if this isn't very clear, it's hard to explain over words.
     
  30. apsdsm

    apsdsm

    Joined:
    Sep 26, 2013
    Posts:
    56
    What I would do is start by generating a random number to represent your hit/miss ratio instead of the range. So first make a random number between 0 and 1, and for the easy difficulty, if the number is between 0 and 0.35 you consider it a miss. For difficult if the value is between 0 and 0.05 it's a miss.

    After that you calculate the actual shooting range.
     
  31. ltrout1999

    ltrout1999

    Joined:
    Oct 11, 2015
    Posts:
    125
    Alright, I'll test that after I figure out what this new bug is. After any tank (player or enemy) wins the round, the EnemyTank spawns and doesn't move giving me the following error:
    Code (csharp):
    1.  
    2. "SetDestination" can only be called on an active agent that has been placed on a NavMesh.
    3. UnityEngine.NavMeshAgent:SetDestination(Vector3)
    4. EnemyInfo.EnemyMovement:Update() (at Assets/Scripts/EnemyTank/EnemyMovement.cs:46)
    5.  
    Nevermind Fixed the bug!
     
    Last edited: Jan 11, 2016