0
\$\begingroup\$

I've been working on the TANKS! tutorial from unity, attempting to add a U.I overlay that displays the scores of each player.

I cannot seem to get it working with the current setup the game has:

  • Each instance of the tanks being spawned in will have the same elements that can be changed in the inspector

    • Color
    • spawn
    • and what I am trying to add: Score display)
  • I have U.I text elements in the Scene that are being plugged in, but I just can't seem to get the code working

The problem is in the Scorekeeper method of GameManager, where the compiler doesn't recognize the Text property of m_PlayerScore.

Game Manager

using System.Collections;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

namespace Complete
{
    public class GameManager : MonoBehaviour
    {
        public int m_NumRoundsToWin = 5;            // The number of rounds a single player has to win to win the game.
        public float m_StartDelay = 3f;             // The delay between the start of RoundStarting and RoundPlaying phases.
        public float m_EndDelay = 3f;               // The delay between the end of RoundPlaying and RoundEnding phases.
        public CameraControl m_CameraControl;       // Reference to the CameraControl script for control during different phases.
        public Text m_MessageText;                  // Reference to the overlay Text to display winning text, etc.
        public GameObject m_TankPrefab;             // Reference to the prefab the players will control.
        public TankManager[] m_Tanks;               // A collection of managers for enabling and disabling different aspects of the tanks.
        public TankManager m_PlayerScore;

        public Text Player1Score;                   // Score for player 1 shown on the scorekeeper
        public Text Player2Score;                   // Score for player 2 shown on the scorekeeper

        public int m_RoundNumber;                  // Which round the game is currently on.
        public WaitForSeconds m_StartWait;         // Used to have a delay whilst the round starts.
        public WaitForSeconds m_EndWait;           // Used to have a delay whilst the round or game ends.
        public TankManager m_RoundWinner;          // Reference to the winner of the current round.  Used to make an announcement of who won.
        public TankManager m_GameWinner;           // Reference to the winner of the game.  Used to make an announcement of who won.
        public int LevelLoaded;

        private GameObject LightingObject;




        private void Start()
        {
            // Create the delays so they only have to be made once.
            m_StartWait = new WaitForSeconds(m_StartDelay);
            m_EndWait = new WaitForSeconds(m_EndDelay);

            SpawnAllTanks();
            SetCameraTargets();
            Scorekeeper();
            //Scorekeeper1();
            //Scorekeeper2();


            // Once the tanks have been created and the camera is using them as targets, start the game.
            StartCoroutine(GameLoop());
        }

        private void SpawnAllTanks()
        {

            for (int i = 0; i < m_Tanks.Length; i++)
            {
                // ... create them, set their player number and references needed for control.
                m_Tanks[i].m_Instance =
                    Instantiate(m_TankPrefab, m_Tanks[i].m_SpawnPoint.position, m_Tanks[i].m_SpawnPoint.rotation) as GameObject;
                m_Tanks[i].m_PlayerNumber = i + 1;
                m_Tanks[i].Setup();
            }
        }


        private void SetCameraTargets()
        {
            // Create a collection of transforms the same size as the number of tanks.
            Transform[] targets = new Transform[m_Tanks.Length];

            // For each of these transforms...
            for (int i = 0; i < targets.Length; i++)
            {
                // ... set it to the appropriate tank transform.
                targets[i] = m_Tanks[i].m_Instance.transform;
            }

            // These are the targets the camera should follow.
            m_CameraControl.m_Targets = targets;
        }


        // This is called from start and will run each phase of the game one after another.
        private IEnumerator GameLoop ()
        {
            // Start off by running the 'RoundStarting' coroutine but don't return until it's finished.
            yield return StartCoroutine (RoundStarting ());

            // Once the 'RoundStarting' coroutine is finished, run the 'RoundPlaying' coroutine but don't return until it's finished.
            yield return StartCoroutine (RoundPlaying());

            // Once execution has returned here, run the 'RoundEnding' coroutine, again don't return until it's finished.
            yield return StartCoroutine (RoundEnding());

            // This code is not run until 'RoundEnding' has finished.  At which point, check if a game winner has been found.
            if (m_GameWinner != null)
            {
                // If there is a game winner, restart the level.
                SceneManager.LoadScene (0);
            }
            else
            {
                // If there isn't a winner yet, restart this coroutine so the loop continues.
                // Note that this coroutine doesn't yield.  This means that the current version of the GameLoop will end.
                StartCoroutine (GameLoop ());
            }
        }


        private IEnumerator RoundStarting ()
        {
            //Calling method meant to load in additive levels. Seems to work...
            LoadMapForRound();
            // As soon as the round starts reset the tanks and make sure they can't move.
            ResetAllTanks ();
            DisableTankControl ();

            // Snap the camera's zoom and position to something appropriate for the reset tanks.
            m_CameraControl.SetStartPositionAndSize ();

            // Increment the round number and display text showing the players what round it is.
            m_RoundNumber++;
            m_MessageText.text = "ROUND " + m_RoundNumber;

            // Wait for the specified length of time until yielding control back to the game loop.
            yield return m_StartWait;
        }

        //Method meant to load in additive levels.
        private void LoadMapForRound()
        {
            int mapChosen = Random.Range(2, 6);
            LevelLoaded = mapChosen;
            SceneManager.LoadScene(LevelLoaded, LoadSceneMode.Additive);
            SceneManager.LoadScene(1, LoadSceneMode.Additive);
            //Debug.Log("SceneLoaded");
        }
        //Method meant to unload levels

        private IEnumerator RoundPlaying ()
        {
            // As soon as the round begins playing let the players control the tanks.
            EnableTankControl ();

            // Clear the text from the screen.
            m_MessageText.text = string.Empty;

            // While there is not one tank left...
            while (!OneTankLeft())
            {
                // ... return on the next frame.
                yield return null;
            }
        }

        private void DestroyRoundMap()
        {
            SceneManager.UnloadSceneAsync(LevelLoaded);
            SceneManager.UnloadSceneAsync(1);
        }

        private IEnumerator RoundEnding ()
        {
            // Stop tanks from moving.
            DisableTankControl();

            // Clear the winner from the previous round.
            m_RoundWinner = null;

            // See if there is a winner now the round is over.
            m_RoundWinner = GetRoundWinner();

            // If there is a winner, increment their score.
            if (m_RoundWinner != null)
                m_RoundWinner.m_Wins++;
                m_RoundWinner.m_PlayerScore.text = string.Format("{0}: {1}",
                                          m_RoundWinner.m_ColoredPlayerText,
                                          m_RoundWinner.m_Wins
                                    );

            // Now the winner's score has been incremented, see if someone has one the game.
            m_GameWinner = GetGameWinner();

            // Get a message based on the scores and whether or not there is a game winner and display it.
            string message = EndMessage();
            m_MessageText.text = message;

            // Wait for the specified length of time until yielding control back to the game loop.
            yield return m_EndWait;

            //Unload Maps
            DestroyRoundMap();

        }



        // This is used to check if there is one or fewer tanks remaining and thus the round should end.
        public bool OneTankLeft()
        {
            // Start the count of tanks left at zero.
            int numTanksLeft = 0;

            // Go through all the tanks...
            for (int i = 0; i < m_Tanks.Length; i++)
            {
                // ... and if they are active, increment the counter.
                if (m_Tanks[i].m_Instance.activeSelf)
                    numTanksLeft++;
            }

            // If there are one or fewer tanks remaining return true, otherwise return false.
            return numTanksLeft <= 1;
        }


        // This function is to find out if there is a winner of the round.
        // This function is called with the assumption that 1 or fewer tanks are currently active.
        public TankManager GetRoundWinner()
        {
            // Go through all the tanks...
            for (int i = 0; i < m_Tanks.Length; i++)
            {
                // ... and if one of them is active, it is the winner so return it.
                if (m_Tanks[i].m_Instance.activeSelf)
                    return m_Tanks[i];
            }

            // If none of the tanks are active it is a draw so return null.
            return null;
        }


        // This function is to find out if there is a winner of the game.
        public TankManager GetGameWinner()
        {
            // Go through all the tanks...
            for (int i = 0; i < m_Tanks.Length; i++)
            {
                // ... and if one of them has enough rounds to win the game, return it.
                if (m_Tanks[i].m_Wins == m_NumRoundsToWin)
                    return m_Tanks[i];
            }

            // If no tanks have enough rounds to win, return null.
            return null;
        }


        // Returns a string message to display at the end of each round.
        private string EndMessage()
        {
            // By default when a round ends there are no winners so the default end message is a draw.
            string message = "DRAW!";

            // If there is a winner then change the message to reflect that.
            if (m_RoundWinner != null)
                message = m_RoundWinner.m_ColoredPlayerText + " WINS THE ROUND!";

            // Add some line breaks after the initial message.
            message += "\n\n\n\n";

            // Go through all the tanks and add each of their scores to the message.
            for (int i = 0; i < m_Tanks.Length; i++)
            {
                message += m_Tanks[i].m_ColoredPlayerText + ": " + m_Tanks[i].m_Wins + " WINS\n";
            }

            // If there is a game winner, change the entire message to reflect that.
            if (m_GameWinner != null)
                message = m_GameWinner.m_ColoredPlayerText + " WINS THE GAME!";

            return message;

        }
        private void Scorekeeper()

        {
            for (int i = 0; i < m_Tanks.Length; i++)
            {
                m_PlayerScore.Text = m_Tanks[i].m_ColoredPlayerText + ": " + m_Tanks[i].m_Wins;
            }
        }


        /*   
            // This function is used to constantly show the score between both tanks.
            private void Scorekeeper1()

            {
                for (int i = 0; i < m_Tanks.Length; i++)
                {
                    Player1Score.text = m_Tanks[0].m_ColoredPlayerText + ": " + m_Tanks[0].m_Wins;
                }
            }

            private void Scorekeeper2()

            {
                for (int i = 0; i < m_Tanks.Length; i++)
                {
                    Player2Score.text = m_Tanks[1].m_ColoredPlayerText + ": " + m_Tanks[1].m_Wins;
                }
            }
          */
        // This function is used to turn all the tanks back on and reset their positions and properties.
        private void ResetAllTanks()
        {
            for (int i = 0; i < m_Tanks.Length; i++)
            {
                m_Tanks[i].Reset();
            }
        }


        private void EnableTankControl()
        {
            for (int i = 0; i < m_Tanks.Length; i++)
            {
                m_Tanks[i].EnableControl();
            }
        }


        private void DisableTankControl()
        {
            for (int i = 0; i < m_Tanks.Length; i++)
            {
                m_Tanks[i].DisableControl();
            }

        }
        private void Update()
        {
            Scorekeeper();
            //Scorekeeper2();
        }
    }
}

Tank Manager

    using System;
    using UnityEngine;
    using UnityEngine.UI;

    namespace Complete
    {
        [Serializable]
        public class TankManager
        {
            // This class is to manage various settings on a tank.
            // It works with the GameManager class to control how the tanks behave
            // and whether or not players have control of their tank in the 
            // different phases of the game.

            public Color m_PlayerColor;                             // This is the color this tank will be tinted.
            public Transform m_SpawnPoint;                          // The position and direction the tank will have when it spawns.
            public Text m_PlayerScore;
            [HideInInspector] public int m_PlayerNumber;            // This specifies which player this the manager for.
            [HideInInspector] public string m_ColoredPlayerText;    // A string that represents the player with their number colored to match their tank.
            [HideInInspector] public GameObject m_Instance;         // A reference to the instance of the tank when it is created.
            [HideInInspector] public int m_Wins;                    // The number of wins this player has so far.


            private TankMovement m_Movement;                        // Reference to tank's movement script, used to disable and enable control.
            private TankShooting m_Shooting;                        // Reference to tank's shooting script, used to disable and enable control.
            private GameObject m_CanvasGameObject;                  // Used to disable the world space UI during the Starting and Ending phases of each round.

            public string Text { get; internal set; }

            public void Setup ()
            {
                // Get references to the components.
                m_PlayerScore = m_Instance.GetComponent<Text>();
                m_Movement = m_Instance.GetComponent<TankMovement> ();
                m_Shooting = m_Instance.GetComponent<TankShooting> ();
                m_CanvasGameObject = m_Instance.GetComponentInChildren<Canvas> ().gameObject;

                // Set the player numbers to be consistent across the scripts.
                m_Movement.m_PlayerNumber = m_PlayerNumber;
                m_Shooting.m_PlayerNumber = m_PlayerNumber;

                // Create a string using the correct color that says 'PLAYER 1' etc based on the tank's color and the player's number.
                m_ColoredPlayerText = "<color=#" + ColorUtility.ToHtmlStringRGB(m_PlayerColor) + ">PLAYER " + m_PlayerNumber + "</color>";

                // Get all of the renderers of the tank.
                MeshRenderer[] renderers = m_Instance.GetComponentsInChildren<MeshRenderer> ();

                // Go through all the renderers...
                for (int i = 0; i < renderers.Length; i++)
                {
                    // ... set their material color to the color specific to this tank.
                    renderers[i].material.color = m_PlayerColor;
                }
            }

            // Used during the phases of the game where the player shouldn't be able to control their tank.
            public void DisableControl ()
            {
                m_Movement.enabled = false;
                m_Shooting.enabled = false;

                m_CanvasGameObject.SetActive (false);
            }


            // Used during the phases of the game where the player should be able to control their tank.
            public void EnableControl ()
            {
                m_Movement.enabled = true;
                m_Shooting.enabled = true;

                m_CanvasGameObject.SetActive (true);
            }


            // Used at the start of each round to put the tank into it's default state.
            public void Reset ()
            {
                m_Instance.transform.position = m_SpawnPoint.position;
                m_Instance.transform.rotation = m_SpawnPoint.rotation;

                m_Instance.SetActive (false);
                m_Instance.SetActive (true);
            }
        }
    }
\$\endgroup\$
  • \$\begingroup\$ public TankManager m_PlayerScore; Have this line at the top of the Game Manager script to reference the variable in Tank Manager. \$\endgroup\$ – M.A.Babcock May 16 at 7:22
  • \$\begingroup\$ Please include your code as text, not as an image. Be sure to show us your TankManager code too so we can see where you defined your Text variable. \$\endgroup\$ – DMGregory May 16 at 11:39
  • \$\begingroup\$ Is this better? \$\endgroup\$ – M.A.Babcock May 17 at 2:20
0
\$\begingroup\$

I can't see anywhere you actually use the value that's been assigned to TankManager's string Text property.

Moreover, assigning to the same variable repeatedly in a loop like this would result in each iteration's value getting stomped by the next one, so that only the final iteration's value remains at the end of the method. Probably not what you want?

private void Scorekeeper()
{
    for (int i = 0; i < m_Tanks.Length; i++)
    {
        m_PlayerScore.Text = m_Tanks[i].m_ColoredPlayerText + ": " + m_Tanks[i].m_Wins;
    }
}

Rather, it looks like you want to assign this text to each player's own text object:

private void Scorekeeper()
{
    for (int i = 0; i < m_Tanks.Length; i++)
    {
        m_Tanks[i].m_PlayerScore.text = m_Tanks[i].m_ColoredPlayerText + ": " + m_Tanks[i].m_Wins;
    }
}

Moreover, you probably want to assign this text only when someone's win number changes, otherwise you're constructing new strings every frame, creating a lot of garbage that will eventually trigger the garbage collector to put your game on pause and sweep it all up.

// If there is a winner, increment their score.
if (m_RoundWinner != null) {
    m_RoundWinner.m_Wins++;
    m_RoundWinner.m_PlayerScore.text = string.Format("{0}: {1}", 
                                              m_RoundWinner.m_ColoredPlayerText,
                                              m_RoundWinner.m_Wins
                                        );
}

Or this could be placed entirely inside a Win() method inside TankManager for better encapsulation. So GameManager only needs to know someone won, and TankManager can manipulate its own internal state to reflect that.

\$\endgroup\$
  • \$\begingroup\$ I forgot to add in a line, the error is still there. It's weird m_RoundWinner has an instance being applied to it, only until this additional line is added does it say there is no instance being referenced. \$\endgroup\$ – M.A.Babcock May 23 at 2:25
  • \$\begingroup\$ You don't need to report each discovery to me - just edit your question so that it contains a minimal complete verifiable example of the problem, and users can post or update an answer based on that detail. \$\endgroup\$ – DMGregory May 23 at 2:28

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.