Got stuck

Guys, it’s my first game and my question on the forum. The point is - I’m making 2D top down shooter, where enemies spawns above the screen after two second from the start and move down. Everything worked fine with this code(only one enemy prefab available),

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

public class GameManager : MonoBehaviour
{
   
    public bool gameIsActive;
    public bool gameOver;
    public bool startGame;
    private float countDownTimer;
    private Enemy enemyScript;
    private SpawnManager spawnManager;
    private Button restartButton;
    private int score;
    public TextMeshProUGUI scoreText;

    public GameObject enemyPref;
    public GameObject loseText;


    // Start is called before the first frame update
    void Start()
    {
        score = 0;
        scoreText.text = "Score:" + 0;
        gameIsActive = true;
        countDownTimer = 20f;
        enemyScript = enemyPref.GetComponent<Enemy>();
        enemyScript.enemySpeed = 1;
        spawnManager = GameObject.Find("Spawn Manager").GetComponent<SpawnManager>();
    }

    // Update is called once per frame
    void Update()
    {
        countDownTimer -= Time.deltaTime;
        if (countDownTimer < 0)
        {
            countDownTimer = 20f;
            ChangeDifficulty();
        }
    }
    public void GameOver()
    {
      
        loseText.gameObject.SetActive(true);
        restartButton = GameObject.FindGameObjectWithTag("RestartButton").GetComponent<Button>();
        restartButton.onClick.AddListener(RestartGame);
    }
    void ChangeDifficulty()
    {
        enemyScript.enemySpeed++;
        spawnManager.enemiesMaxAmount++;
    } // more code down here

However, when I decided to implement 2 prefabs for enemies, I stuck : I cannot change the same component correctly inside the array of several game objects.
I cannot understand how works GetComponent in (un?not?)Instantiated prefabs
What to do the best here?

I can’t understand what you’re trying to do, or what the problem is.

I suggest you make your best try at it, then post the (non-working) code, and explain exactly what error you get, or how the resulting behavior is different from what you want.

1 Like

Thanks , I’ll try to explain and show as I can.
I want my enemies to spawn above the screen, and then move downwards based on gameManager timer. As soon as every enemy spawn I’d like to give them random starting speed, and when the countdown timer is up, multiply the speed of every enemy on the screen based on waveCounter.
In the two scripts below everything works if I remove line gameManager.isReadyForNextWave = false; each enemy on the screen quickly gets infinite speed and rushes down when the countdown time is up. However, when I implement the isReadyForNextWave cheking this increases the speed of only ONE enemy.

public class Enemy : MonoBehaviour
{
    public float enemySpeed;
    private Rigidbody2D enemyRb;
    private GameManager gameManager;

    // Start is called before the first frame update
    void Start()
    {
        enemySpeed = Random.Range(0.5f, 1f);
        enemyRb = GetComponent<Rigidbody2D>();
        gameManager = GameObject.Find("Game Manager").GetComponent<GameManager>();

    }

    // Update is called once per frame
    private void FixedUpdate()
    {
        enemyRb.MovePosition(enemyRb.position + Vector2.down * enemySpeed * Time.fixedDeltaTime);
    }
    private void OnCollisionEnter2D(Collision2D collision)
    {
        if (collision.gameObject.CompareTag("Sensor"))
        {
            gameManager.LosingSecuence();
            Destroy(gameObject);
        }
        else if (collision.gameObject.CompareTag("Bullet"))
        {
            Destroy(gameObject);
            gameManager.UpdateScore(20);
        }
    }
    private void Update()
    {
     
        if (gameManager.isReadyForNextWave)
        {
            SetSpeedPerWave();
        }
    }
    void SetSpeedPerWave()
    {
        enemySpeed *= gameManager.waveCount;
        gameManager.isReadyForNextWave = false;
        Debug.Log("Changed!Speed: " + enemySpeed);
    }
}
public class GameManager : MonoBehaviour
{

    public bool gameIsActive;
    public bool gameOver;
    public bool startGame;
    public bool isReadyForNextWave;
    public float countDownTimer;
    public int waveCount;

    private SpawnManager spawnManager;
    private Button restartButton;
    private int score;
    public TextMeshProUGUI scoreText;
    public GameObject loseText;


    // Start is called before the first frame update
    void Start()
    {
        score = 0;
        scoreText.text = "Score:" + 0;
        gameIsActive = true;
        countDownTimer = 5f;
        waveCount = 1;
        spawnManager = GameObject.Find("Spawn Manager").GetComponent<SpawnManager>();

    }

    // Update is called once per frame
    void Update()
    {

        SetWavePerTime();
    }

    private void SetWavePerTime()
    {
        countDownTimer -= Time.deltaTime;
        if (countDownTimer < 0)
        {
            countDownTimer = 20f;
            waveCount++;
            isReadyForNextWave = true;
            ChangeDifficulty();
            Debug.Log(waveCount);
        }
    }

    public void GameOver()
    {
        loseText.gameObject.SetActive(true);
        restartButton = GameObject.FindGameObjectWithTag("RestartButton").GetComponent<Button>();
        restartButton.onClick.AddListener(RestartGame);
    }
    void ChangeDifficulty()
    {
        spawnManager.enemiesMaxAmount++;
    }
    void RestartGame()
    {
        SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex);
    }
    void StartGame()
    {
        SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex + 1);
    }
    public void LosingSecuence()
    {
        gameIsActive = false;
        Invoke("GameOver", 1f); //string ref
    }
    public void UpdateScore(int scoreToAdd)
    {
        score += scoreToAdd;
        scoreText.text = "Score:" + score;
    }
}

I know my code looks terrible, and probably, it’s the reason why I got stuck. But I think I really close to solve it. Thank you in advance.
P.S. I don’t mind any pieces of advise to help improve my coding skills. I’ve been studying unity hard for last 2 month via tutorials + YouTube(Brackeys,codemonkey) and c# dedicated literature. Is there anything else you want to reccomend?

OK, so the thing is that you have many instances of your Enemy script — one for each enemy on the screen. And enemySpeed is a normal instance variable, so there is a separate copy of it for each enemy. All your enemies have their own Update methods called every frame. So that’s why, if you use gameManager.readyForNextWave to avoid speeding up more than once, only one enemy (the very first one to run its Update method) speeds up.

So yes, you’re very close. It’s just that the Enemy script should not, under any circumstances, be mucking about with the gameManager’s data! It should be the GameManager that decides when readyForNextWave should be true, and when it should be false.

You already have GameManager setting readyForNextWave to true. That’s good. Now you just need to make it set that back to false on the next frame. Change your Update method to:

    void Update() {
        if (readyForNextWave) readyForNextWave = false;
        SetWavePerTime();
    }

(Technically the if above is not necessary, but I think it makes the intent clearer.) So now simulate this in your head: initially readyForNextWave is false. At some point your countDownTimer runs out, and SetWavePerTime sets readyForNextWave = true. That’s the last thing GameManager.Update does, so now Unity goes and runs the Update method of other scripts, such as your enemies, who will see the flag set and double their speed. Then on the next frame, the if statement above (in GameManager) will see and clear readyForNextWave. And then all the enemy updates will see the flag as clear, and not double their speed again until the proper time.

Note that the order in which different scripts get their Update method called isn’t obvious, but it doesn’t matter. If some of your Enemy scripts update before GameManager, it still works. Work through some examples on paper if needed to convince yourself this is true.

(There are probably cleaner ways to solve this… for example, GameManager should probably have some event it fires when it’s next-wave time, and enemies should register themselves for that event. Using a temporarily-set flag like this in place of an event is a bit of a hack. But it is simple and has gotten you 90% of the way there, so I say just go with it!)[/code]

2 Likes

Joe, thanks a lot! As soon as I understood an update workflow, I implemented this via couroutine. Everything works just fine!

Yikes. I wouldn’t recommend doing it that way. I’ve seen a lot of beginners get twisted up in problems caused by coroutines. In rare cases they really are the cleanest solution to a problem, but usually not.

But I’m glad you got something working anyway.

1 Like