Hi I'm currently developing a game in Unity and I run into a small problem for some reason the fourth scene I'm creating(Level 3) gets stuck and doesnt finish loading preventing my game from transitioning from scene 2(Level 2) to scene 3(Level 3). I have a scenemanager script that manages these transitions and it works fine for all other scene transitions except in the case explained above. Does anyone know what I'm doing wrong? Below you will see the the code responsible for handeling the scene transitions:
This is my scenemanager script responsible for additively loading and unloading scenes:
public static class MySceneManager
{
private static int lastLoadedScene = 0;
public static void LoadScene(int index, MonoBehaviour caller)
{
ObjectPooler objP = new ObjectPooler();
objP.ReleaseAll();
caller.StartCoroutine(loadNextScene(index));
}
private static IEnumerator loadNextScene(int index)
{
var _async = SceneManager.LoadSceneAsync(index, LoadSceneMode.Additive);
_async.allowSceneActivation = false;
while (_async.progress < 0.9f)
{
yield return null;
}
_async.allowSceneActivation = true;
while (!_async.isDone)
{
yield return null;
}
var newScene = SceneManager.GetSceneByBuildIndex(index);
if (!newScene.IsValid()) yield break;
SceneManager.SetActiveScene(newScene);
if (lastLoadedScene >= 0) SceneManager.UnloadSceneAsync(lastLoadedScene);
lastLoadedScene = index;
}
}
This is the script from where I call the scene transition:
public class HeartScript : MonoBehaviour
{
int HeartEnter = 1;
void Start()
{
DontDestroyOnLoad(this.gameObject);
}
void OnTriggerEnter2D(Collider2D other)
{
Scene scene = SceneManager.GetActiveScene();
if (other.gameObject.CompareTag("White Ball"))
{
if (HeartIndicator.numOfHearts < 3)
{
Debug.Log("Entered COLLIDER");
HeartIndicator.numOfHearts += 1;
}
if(scene.name == "Level 2" && HeartEnter == 1)
{
MySceneManager.LoadScene(3, this);
HeartEnter++;
}
this.gameObject.SetActive(false);
}
}
}
This may possibly happen due to:
Scene 3 is not added to "Scenes in build".
Or the game object holding co-routine script is not active.
Related
I'm sorry for any messy code, I'm relatively new to this. I made a working teleport in Unity but whenever I teleport from one of the teleports to the other, I wanna make it so there's a 5 second cooldown before you can use the teleporter again. So I used IEnumerator, added 5 seconds before "justTeleported" became false again, but when I teleported, I instantly got teleported back, and had to wait 5 seconds before I could try again. So my though was maybe I'm touching the trigger too quickly, before it can become false, that's why I added the two seconds. But now, whenever I get on the teleporter, it goes from true to false to true a couple times, and then I eventually get teleported back to where I came from. If anyone could help, I would be very thankful. Thank you.
{
public Transform Destination;
bool justTeleported;
public GameObject Player = GameObject.FindGameObjectWithTag("Player");
// Start is called before the first frame update
void Start()
{
justTeleported = false;
}
private void Update()
{
print(justTeleported)
}
private void OnTriggerEnter2D(Collider2D coll)
{
if (coll.gameObject.tag == "Player" && justTeleported == false)
{
StartCoroutine("Cooldown");
}
}
IEnumerator Cooldown()
{
justTeleported = true;
yield return new WaitForSeconds(2f);
Player.transform.position = Destination.transform.position;
yield return new WaitForSecondsRealtime(5f);
justTeleported = false;
}
Because each of the teleports has its own bool justTeleported, setting the first true doesn't automatically set the other true, so you need some way to tell the Destination teleport script to start it's own cooldown coroutine. Here's my test script, it works for me.
using System.Collections;
using UnityEngine;
public class Teleport : MonoBehaviour
{
public Teleport Destination = null;
public float CooldownTime = 5f;
private bool justTeleported = false;
public void StartCoolDown()
{
StartCoroutine(Cooldown());
}
private void OnTriggerEnter2D(Collider2D coll)
{
if (coll.gameObject.CompareTag("Player") && !justTeleported) //CompareTag("tag") is better than .tag == "tag"
{
TeleportPlayer(coll.gameObject);
}
}
private void TeleportPlayer(GameObject player)
{
if (Destination) //if Destination is not null
{
StartCoolDown();
Destination.StartCoolDown(); //tell Destination to cooldown
player.transform.position = Destination.transform.position;
}
}
private IEnumerator Cooldown()
{
justTeleported = true;
yield return new WaitForSeconds(CooldownTime);
justTeleported = false;
}
}
Teleport1 (Destination: Teleport2)
Teleport2 (Destination: Teleport1)
My unity project is freezing and there are no errors before clicking play button after that it freezes the code:
In this code I am trying to detect if the player is triggering enemy collider with OnTriggerStay2D
so if it does I want the code to add score every second with IEnumerator function. But if the player is not colliding with the enemy for more than 2 seconds it gets destroyed.
The code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Trigger : MonoBehaviour
{
public GameObject other;
bool touching = false;
int score = 0;
public void Update()
{
AddScore();
}
private void OnTriggerStay2D(Collider2D other)
{
Debug.Log("Touching");
touching = true;
}
public void AddScore()
{
if(touching == true)
{
while (touching == true)
{
score++;
StartCoroutine(wait());
}
Debug.Log(score);
}
else
{
Debug.Log("Not Touching");
StartCoroutine(waiter());
}
}
IEnumerator waiter()
{
yield return new WaitForSecondsRealtime(2);
Destroy(other);
}
IEnumerator wait()
{
yield return new WaitForSecondsRealtime(1);
}
}
Just delete while (touching == true), that is breaking your game.
The if condition should be enough for the logic.
I'm trying to keep the player skin, even when reloading the scene, or moving on to a new one. The player object changes every scene.
The player skin is chosen in a pause menu, with three buttons. Each one of those buttons calls a function in the script below. I'm trying to call one of these functions, based on what value the PlayerPrefs int holds, and the function does get called; But throws the error MissingReferenceException: The object of type 'GameObject' has been destroyed but you are still trying to access it
Below is what i have already tried, but this throws an error on scene reload (death)
i don't know what i'm doing wrong here.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class Pausemenu : MonoBehaviour
{
public static bool gameIsPaused = false;
public GameObject pauseMenuUI;
public Material BelgianMat;
public Material Ball2;
public Material Rainbow;
public GameObject Player;
// Update is called once per frame
void Update()
{
if (Input.GetKeyDown(KeyCode.Escape)) {
if (gameIsPaused) {
Resume();
} else {
Pause();
}
}
}
public void Resume(){
pauseMenuUI.SetActive(false);
Time.timeScale = 1f;
gameIsPaused = false;
}
void Pause() {
pauseMenuUI.SetActive(true);
Time.timeScale = 0f;
gameIsPaused = true;
}
public void LoadMenu() {
Time.timeScale = 1f;
gameIsPaused = false;
SceneManager.LoadScene("Menu");
}
public void QuitGame() {
Debug.Log("Quitting");
Application.Quit();
}
public void ApplyBelgian() {
Player.GetComponent<Renderer>().material = BelgianMat;
PlayerPrefs.SetInt("playerMat", 0);
}
public void ApplyBall2() {
Player.GetComponent<Renderer>().material = Ball2;
Debug.Log("Applied ball two");
PlayerPrefs.SetInt("playerMat", 1);
}
public void ApplyRainbow() {
Player.GetComponent<Renderer>().material = Rainbow;
PlayerPrefs.SetInt("playerMat", 2);
}
void OnEnable()
{
Debug.Log("OnEnable called");
SceneManager.sceneLoaded += OnSceneLoaded;
}
// called second
void OnSceneLoaded(Scene scene, LoadSceneMode mode)
{
Debug.Log("OnSceneLoaded: " + scene.name);
Debug.Log(mode);
if (PlayerPrefs.GetInt("playerMat") == 0) {
ApplyBelgian();
}
else if (PlayerPrefs.GetInt("playerMat") == 1) {
Debug.Log("gonna apply ball 2");
ApplyBall2();
}
else if (PlayerPrefs.GetInt("playerMat") == 2) {
ApplyRainbow();
}
}
}
I don't know why but it seems like the reference to the Player object may be broken after loading the scene. If this is the case, add a "Player" tag to your Player object and add this to the OnEnable function: Player = GameObject.FindWithTag("Player");
I am trying to implement a waves of enemies, where you need to create a new wave when the current wave has been destroyed.
Wave Code
[SerializeField] List<WaveConfig> waveConfigs;
[SerializeField] int startingWave = 0;
WaveConfig waveConfig;
void Start()
{
StartCoroutine(SpawnAllEnemyInWawe());
}
IEnumerator SpawnAllEnemyInWawe()
{
for(int waweIndex = startingWave; waweIndex < waveConfigs.Count; waweIndex++)
{
var currentWave = waveConfigs[waweIndex];
yield return StartCoroutine(SpawnAllEnemyInWawe(currentWave));
}
}
IEnumerator SpawnAllEnemyInWawe(WaveConfig waveConfig)
{
for(int enemyCount = 0; enemyCount<waveConfig.GetEnemyNumberOfParh(); enemyCount++)
{
GameObject newEnemy = Instantiate(waveConfig.GetEnemyPrefabs(),
waveConfig.GetEnemyWaiponts()[0].transform.position,
Quaternion.identity);
newEnemy.GetComponent<EnemyPath>().SetWaveConfig(waveConfig);
yield return new WaitForSeconds(waveConfig.GetTimeMegduSpawnomEnemy());
}
}
If I understand your point correctly.
this code not help you to wait previous wave was eliminate, it work flow is spawn next wave instantly after previous wave spawn completed.
IEnumerator SpawnAllEnemyInWawe()
{
for(int waweIndex = startingWave; waweIndex < waveConfigs.Count; waweIndex++)
{
var currentWave = waveConfigs[waweIndex];
yield return StartCoroutine(SpawnAllEnemyInWawe(currentWave));
}
}
IF you need to wait until current wave eliminate.
When wave begin, you should track all enemies in the wave (eg. amount, alive data, or something) and check it for start next wave.
Psudo :
void Update(){
if(enemyInWave == 0){ // Or Condition is fullfill to start next wave
NextWave();
}else {
// Do Nothing
}
}
void NextWave(){
var waveConfig = WaveConfig[currentWaveIndex]
enemyInWave = waveConfig.monsters.Amount
SpanwEnemy(waveConfig.monsters)
}
or something like that
You could use a static HashSet/List where you store all instances of created enemies. The enemies simply add and remove themselves to/from the HastSet during their lifetime.
Then you can simply wait until no enemies are left before going into the next wave.
Something like
public class YourWaveClass : MonoBehaviour
{
[SerializeField] List<WaveConfig> waveConfigs;
[SerializeField] int startingWave = 0;
private static HastSet<Enemy> currentlyAliveEnemies = new HashSet<Enemy>();
public static void Add(Enemy enemy)
{
if(!currentlyAliveEnemies.Contains(enemy)) currentlyAliveEnemies.Add(enemy);
}
public static void Remove(Enemy enemy)
{
if(currentlyAliveEnemies.Contains(enemy)) currentlyAliveEnemies.Remove(enemy);
}
void Start()
{
StartCoroutine(SpawnAllEnemyInWawe());
}
IEnumerator SpawnAllEnemyInWawe()
{
for(int waweIndex = startingWave; waweIndex < waveConfigs.Count; waweIndex++)
{
var currentWave = waveConfigs[waweIndex];
yield return StartCoroutine(SpawnAllEnemyInWawe(currentWave));
}
}
IEnumerator SpawnAllEnemyInWawe(WaveConfig waveConfig)
{
for(int enemyCount = 0; enemyCount<waveConfig.GetEnemyNumberOfParh(); enemyCount++)
{
// NOTE: It would be better/saver if you make your prefab directly of type Enemy then
var newEnemy = Instantiate(waveConfig.GetEnemyPrefabs(), waveConfig.GetEnemyWaiponts()[0].transform.position, Quaternion.identity);
newEnemy.GetComponent<EnemyPath>().SetWaveConfig(waveConfig);
yield return new WaitForSeconds(waveConfig.GetTimeMegduSpawnomEnemy());
}
// Now wait until the HashSet is empty => all enemies are dead
yield return new WaitUntil(() => currentlyAliveEnemies.Count == 0);
}
}
So what you need to have on your enemy prefabs would be a component like
public class Enemy : MonoBehaviour
{
// This is automatically called during Instantiate
private void Awake()
{
YourWaveClass.Add(this);
}
// this is called automatically when your object gets destroyed/dies
private void OnDestroy()
{
YourWaveClass.Remove(this);
}
}
I'm trying to create an inactivity timer for my game and I seem to have everything working except for one part. After a pre-count, I'm instantiating a prefab timer dialog and then in my coroutine I'm trying to update the text field 'timerTextField' located in the that prefab but it isn't updating.
I do have all of the appropriate variables assigned in the editor and my debug.log right after the 'timerTextField.text = s.ToString();' line is counting down correctly. What do I have wrong?
using UnityEngine;
using UnityEngine.UI;
using System.Collections;
using System.Collections.Generic;
using UnityEngine.SceneManagement;
public class GameManager : MonoBehaviour
{
// Create Singleton
public static GameManager gameManagerInstance = null;
// Set Default Background Color
public Color defaultColor;
// Restart variables
private Vector3 prevMousePosition;
[SerializeField]
private Text timerTextField;
public Object startingScene;
public GameObject timeOutWarningDialog;
public float countdownLength;
public float countdownDelay;
private float seconds;
private Scene currentScene;
private GameObject gameManager;
private GameObject canvas;
private GameObject timerInstance;
void Awake()
{
if (gameManagerInstance == null)
gameManagerInstance = this;
else if (gameManagerInstance != null)
Destroy(gameObject);
DontDestroyOnLoad(gameObject);
gameManager = GameObject.FindGameObjectWithTag("GameManager");
}
void Start()
{
prevMousePosition = Input.mousePosition;
currentScene = SceneManager.GetActiveScene();
}
void Update()
{
if (Input.anyKeyDown || Input.mousePosition != prevMousePosition)
{
currentScene = SceneManager.GetActiveScene();
if (currentScene.name != startingScene.name)
{
StartPreCountTimer();
if (timeOutWarningDialog != null)
{
timeOutWarningDialog.SetActive(false);
}
}
}
prevMousePosition = Input.mousePosition;
}
// GAME TIMER
public void StartPreCountTimer()
{
// Debug.Log("Pre Count Started");
CancelInvoke();
if (GameObject.FindGameObjectWithTag("Timer") == null)
Invoke("ShowRestartWarning", countdownDelay);
}
void ShowRestartWarning()
{
canvas = GameObject.FindGameObjectWithTag("Canvas");
timerInstance = Instantiate(timeOutWarningDialog);
timerInstance.transform.SetParent(canvas.transform, false);
timerInstance.SetActive(true);
if (timerInstance.activeSelf == true)
StartTimer(countdownLength);
}
public void StartTimer(float seconds)
{
StartCoroutine("RunTimer", seconds);
}
IEnumerator RunTimer(float seconds)
{
float s = seconds;
// Debug.Log("Restart Countdown Started from: " + s);
while (s > 0)
{
if (timerTextField != null)
{
timerTextField.text = s.ToString();
Debug.Log(s);
}
yield return new WaitForSeconds(1);
s -= 1;
}
if (s == 0)
{
RestartGame();
}
}
void RestartGame()
{
SceneManager.LoadScene(startingScene.name);
// Debug.Log("Game Restarted");
}
}
Your problems comes from the timerTextField reference.
It seems like you assign the Text component located inside your prefab (which is timeOutWarningDialog I think) to the timerTextField field inside Inspector.
Because of this, after instantiating a new prefab timer dialog, when you want to change the timerTextField.text value, what is changed is the Text component inside your prefab and not the one of the instantiated object.
You check this but selecting your object containing the Text component inside your prefab (int he Project tab of Unity):
(this also explains the weird values you had on start: the previous values reached when you stopped game)
For your script to work, you simply have to reference the new Text component of the instantiated prefab using something like this:
void ShowRestartWarning()
{
timerInstance = Instantiate(timeOutWarningDialog);
timerInstance.transform.SetParent(transform, false);
timerInstance.SetActive(true);
timerTextField = timerInstance.GetComponentInChildren<Text>(); // NEW LINE
if(timerInstance.activeSelf == true)
StartTimer(countdownLength);
}
Hope this helps,