Unity Coroutine not working across scenes - c#

I am calling a Coroutine shown below, which is attached to a DontDestroyOnLoad objects that persists across scenes.
IEnumerator InitMaxScore()
{
Debug.Log("will wait for some time");
yield return new WaitForSeconds(1);
GameObject[] coins = GameObject.FindGameObjectsWithTag("Coin");
Debug.Log("coins length == " + coins.Length);
if (coins.Length > 0)
{
maxScoreInitialized = true;
maxScore = score + coins.Length * 10;
foreach (GameObject healthPickup in GameObject.FindGameObjectsWithTag("Health"))
{
maxScore += healthPickup.GetComponent<Pickups>().pointsForLifePickup;
}
Debug.Log("maxScore inti == " + maxScore);
}
yield return null;
}
This Coroutine is called in the OnLevelWasLoaded event of the said gameobject which is set to DontDestroyOnLoad on awake as shown below.
private void Awake()
{
int numGameSessions = FindObjectsOfType<GameSession>().Length;
if (numGameSessions > 1)
{
Destroy(gameObject);
}
else
{
DifficultyManagement.setDifficulty(Difficulty.One); // start the game with diff one always
DontDestroyOnLoad(this.gameObject);
}
}
While the log "will wait for some time" in the Coroutine is getting printed, Debug.Log("coins length == " + coins.Length) is not getting printed all the times. I am certainly not destroying the said gameobject for the entire duration of my game that might have caused the Coroutine to behave this way. The behaviour is not consistent either, sometimes it works, sometimes it does not, and I am like why can't you make up your mind.
I have been banging my head on this for a long time and could not seem to fix this, any leads would be appreciated to lift my mental block :/

OnLevelWasLoaded is deprecated, consider using sceneLoaded event:
using UnityEngine.SceneManagement;
private void Awake()
{
int numGameSessions = FindObjectsOfType<GameSession>().Length;
if (numGameSessions > 1)
{
Destroy(gameObject);
}
else
{
DifficultyManagement.setDifficulty(Difficulty.One); // start the game with diff one always
DontDestroyOnLoad(this.gameObject);
SceneManager.sceneLoaded += (scene, mode) => StartCoroutine(InitMaxScore());
}
}

Related

Infinite loop: while(Time.time < Time.time + 5f)

I am going insane. For the life of me I cannot work out why the following code is causing Unity to freeze up as soon as I press play. It's an otherwise empty project with the script attached to an empty gameobject. In the console, nothing appears, not even the initial Debug.Log("Step 1");
using UnityEngine;
public class Reels : MonoBehaviour
{ void Start()
{
Debug.Log("Step 1");
TestFunction(1f);
}
private void TestFunction(float duration)
{
float endTimer = Time.time + duration;
Debug.Log("Step 2 " + endTimer);
while (Time.time < endTimer)
{
Debug.Log("Step 3");
}
}
void Update()
{
}
}
Please save my sanity.
Time.time is a read only field that stores the time at the beginning of the frame, and the way you wrote that code, the frame never ends, because you'll never get out of that loop.

Unity - deleting a specific gameobject when mouse/pointer is over it

I am creating a virtual reality game where when you double click on an object it deletes it. However multiple objects are duplicates of eachother so when i attach my double click script to them it will delete all the objects upon double click. I want it to just delete the one the mouse is on. I will attach my script below:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class doubleClick : MonoBehaviour
{
private float firstClickTime, timebetweenClicks;
private bool coroutineAllowed;
private int clickCounter;
public GameObject toDelete;
// Start is called before the first frame update
void Start()
{
firstClickTime = 0f;
timebetweenClicks = 0.2f;
clickCounter = 0;
coroutineAllowed = true;
}
// Update is called once per frame
void Update()
{
if (Input.GetMouseButtonUp(0))
clickCounter += 1;
if (clickCounter == 1 && coroutineAllowed)
{
firstClickTime = Time.time;
StartCoroutine(DoubleClickDetection());
}
}
private IEnumerator DoubleClickDetection()
{
coroutineAllowed = false;
while (Time.time < firstClickTime + timebetweenClicks)
{
if (clickCounter == 2)
{
//Destroy(toDelete);
break;
}
yield return new WaitForEndOfFrame();
}
clickCounter = 0;
firstClickTime = 0f;
coroutineAllowed = true;
}
}
I can't see anything that jumps out in your code. Padia's answer is on the right track, though. Mouse input handling when you want to interact with objects is best done from within that object/prefab. That way, you're guaranteed that only that object is interacted with. If not, then there's likely something else going on with your code.
Unfortunately, it's a little late for me to work out a complete solution. But here's a possible solution.
Within the prefab's code, insert an OnMouseDown function. In that function, check if the double-click timer's been started. If not, set a flag to tell the prefab that the double-click timer needs to start (call it WaitingForTimerStart). You could alternatively use an enum to hold the timer state. In any event, don't start the timer yet.
Within the prefab's code, insert an OnMouseUp function. That should check if the timer start flag's (WaitingForTimerStart) been set. If it has, set another timer flag to tell it to run the timer (RunTimer).
In the prefab's Update function, run the timer. I'm sure you know what to do here.
In the prefab's OnMouseDown function (the one you added earlier), check if the timer's running. If it is, then you know a double-click's been performed. Destroy the object.
This way, there's no need to check names, use raycasting or coroutines. Each object will have its own timer and be fully independent of each other.
One main issue with your code is: You are starting the Coroutine everyframe!
You have to wrap your code block in { } after the if, otherwise the condition only applies for the one line after it!
Instead of
if (Input.GetMouseButtonUp(0))
clickCounter += 1;
if (clickCounter == 1 && coroutineAllowed)
{
firstClickTime = Time.time;
StartCoroutine(DoubleClickDetection());
}
it should probably rather be
if (Input.GetMouseButtonUp(0))
{
clickCounter += 1;
if (clickCounter == 1 && coroutineAllowed)
{
firstClickTime = Time.time;
StartCoroutine(DoubleClickDetection());
}
}
Then
I want it to just delete the one the mouse is on.
Well currently you are using Input.GetMouseButtonUp which is true global in the entire app, not only the object the mouse is currently over.
You can rather use e.g. OnMouseDown which is called when the mouse goes down over a collider or UI element.
private void OnMouseDown ()
{
clickCounter += 1;
if (clickCounter == 1 && coroutineAllowed)
{
firstClickTime = Time.time;
StartCoroutine(DoubleClickDetection());
}
}
Try this code:
private float firstClickTime, timebetweenClicks;
private bool coroutineAllowed;
private int clickCounter;
RaycastHit hit;
// Start is called before the first frame update
void Start()
{
firstClickTime = 0f;
timebetweenClicks = 0.2f;
clickCounter = 0;
coroutineAllowed = true;
}
private void OnMouseUp()
{
clickCounter += 1;
}
// Update is called once per frame
void Update()
{
if (clickCounter == 1 && coroutineAllowed)
{
firstClickTime = Time.time;
StartCoroutine(DoubleClickDetection());
}
}
private IEnumerator DoubleClickDetection()
{
coroutineAllowed = false;
while (Time.time < firstClickTime + timebetweenClicks)
{
if (clickCounter == 2)
{
if (Physics.Raycast(Camera.main.ScreenPointToRay(Input.mousePosition), out hit))
{
if (hit.collider.transform == this.transform)
{
Destroy(this.gameObject);
}
}
break;
}
yield return new WaitForEndOfFrame();
}
clickCounter = 0;
firstClickTime = 0f;
coroutineAllowed = true;
}
This code use OnMouseDown() Unity function to detect when the game object is clicked by the mouse, and add 1 to the clickCounter variable.
Also you can use OnMouseUp() function if you prefer to detect the click when the mouse is up.

Unity 3D How to check if a gameobject remained in the same position every x amount of time?

this is the code I managed to come up with, the output isn't consistent. I assume it has something to do with the code not being looped properly. Can somebody tell me what I am doing wrong please?
The issue seems to be after X seconds have passed, it will continually check positions without waiting for the time to pass.
IEnumerator checkPosition()
{
while (true)
{
yield return new WaitForSeconds(X);
newpos = this.gameObject.transform.position;
//Debug.Log(newpos);
if (oldpos == newpos)
{
Debug.Log("Player remained idle");
}else if (oldpos != newpos)
{
Debug.Log("Player moved");
}
oldpos = newpos;
}
}
void Start()
{
oldpos = this.gameObject.transform.position;
}
void Update()
{
StartCoroutine(checkPosition());
}
Your code checks object's position only once after X amount of time. You need to loop your check routine.
IEnumerator checkPosition()
{
while (true)
{
yield return new WaitForSeconds(X);
newpos = this.gameObject.transform.position;
//Debug.Log(newpos);
if (oldpos == newpos)
{
Debug.Log("Player remained idle");
}else if (oldpos != newpos)
{
Debug.Log("Player moved");
}
oldpos = newpos;
}
}
UPDATE:
You need to start your coroutine in Start. The Update method is executed every frame. So you start the coroutine without waiting for the X amount of time. When you start your coroutine in Start, it will be executed only once and will run pseudo-parallel.

Unity power-up is not working as intended

First off, I am quite new to scripting so there's probably going to be a few flaws in my script.
So basically, I've made a script for the power up, but once my shot or the player touches the power up coin the fire rate does increase however it won't go back to the normal fire rate after 5 seconds... I have no idea what might be the cause, any advice would be helpful!
using UnityEngine;
using System.Collections;
public class FireRatePowerUp : MonoBehaviour {
private bool isPowerUp = false;
private float powerUpTime = 5.0f;
private PlayerShoot playerShoot;
private void Start()
{
playerShoot = PlayerShoot.FindObjectOfType<PlayerShoot>();
}
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.gameObject.tag == "Player" || collision.gameObject.tag == "Projectile")
{
StartCoroutine(PowerUpTime());
isPowerUp = true;
Destroy(gameObject);
if (collision.gameObject.tag == "Projectile")
{
Destroy(collision.gameObject);
}
}
}
IEnumerator PowerUpTime()
{
playerShoot.fireRate -= 0.13f;
yield return new WaitForSeconds(powerUpTime);
playerShoot.fireRate += 0.13f;
}
}
I think the issue here is that you're destroying the gameobject this script is attached to (the coin) and by so doing, the script itself is destroyed, therefor its code, coroutine or otherwise won't execute.
StartCoroutine(PowerUpTime());
isPowerUp = true;
Destroy(gameObject); //oops, our script has been destroyed :(
You would have to do this very differently, basically moving the bulk of the code to the PlayerShoot class.
Something like this (this being in PlayerShoot.cs)
public void ActivatePowerupFireRate(float time, float amt) {
StartCoroutine(DoActivatePowerupFireRate(time, amt));
}
public IEnumerator ActivatePowerupFireRate(float time, float amt) {
fireRate -= amt;
yield return WaitForSeconds(time);
fireRate += amt;
}
IEumerator is definately one of the ways you can solve this issue.
However I'm not a fan of them here's my solution if you have a timer in game.
public int timePassed = 0;
public int gotPowerUp = 0;
void Start(){
InvokeRepeating("Timer", 0f, 1.0f);
//Starting at 0 seconds, every second call Timer function.
}
void Timer(){
timePassed++; // +1 second.
}
That way when you obtained the powerup you can set gotPowerUp = timePassed. So you have the exact time when powerup is activated.
then you do something like
if( (gotPowerUp + 5) == timePassed ){
//5 seconds have passed.
//deactivate powerup here
}

Unity does not update NavMeshAgent destination in coroutine

I'm working on an AI script in Unity 5 mostly constructed of coroutines to move around an AI object in my game. It actually works pretty well and is frame independent that way. I'm trying to avoid cluttering the Update function of the class.
However, I'm trying to create a function called wander, where the AI hangs around and randomly chooses a couple of Vector3s in the area of the waypoint and travel to each of them. After that the AI should go to the next waypoint and do the same thing into infinity basically. Collision detection and all that sort is for later parts. I want to get the navigating part fixed first.
void Start(){
agent = GetComponent<NavMeshAgent> ();
collisionRange = GetComponent<CapsuleCollider> ();
collisionRange.radius = detectionRadius;
if (waypoints.Length > 0) {
StartCoroutine (patrol ());
} else {
StartCoroutine (idle (idleTime));
}
}
IEnumerator patrol(){
agent.SetDestination(waypoints[waypointIndex].position);
Debug.Log ("Patrol started, moving to " + agent.destination);
while (agent.pathPending) {
Debug.Log ("not having path");
yield return null;
}
Debug.Log ("I have a path, distance is " + agent.remainingDistance);
while (float.Epsilon < agent.remainingDistance) {
//Debug.Log ("Moving...");
yield return null;
}
StartCoroutine (nextWaypoint ());
}
IEnumerator idle(int time){
Debug.Log ("Idleing for "+ time + " seconds");
agent.Stop ();
yield return new WaitForSeconds(time);
if(waypoints.Length > 2){
agent.Resume ();
StartCoroutine(patrol());
}
}
IEnumerator wander(){
agent.Stop ();
Debug.Log ("Going to look around here for a while.");
Vector3[] points = new Vector3[wanderPoints];
for (int i = 0; i < wanderPoints; i++) {
agent.Stop ();
Vector3 point = Random.insideUnitSphere * wanderRadius;
point.y = transform.position.y;
points [i] = point;
}
agent.ResetPath ();
agent.SetDestination(points[0]);
agent.Resume ();
Debug.Log ("point: " + points [0]);
Debug.Log ("Destination: " + agent.destination);
while (float.Epsilon < agent.remainingDistance) {
Debug.Log ("Moving...");
yield return null;
}
//StartCoroutine(patrol());
yield return null;
}
IEnumerator nextWaypoint(){
Debug.Log ("Arrived at my waypoint at " + transform.position);
if (waypointIndex < waypoints.Length -1) {
waypointIndex +=1;
} else {
waypointIndex = 0;
}
StartCoroutine(wander ());
yield return null;
}
If I swap the wander function with the idle function in nextWaypoint, everything works as expected, but this bit will never work:
agent.ResetPath ();
agent.SetDestination(points[0]);
agent.Resume ();
Debug.Log ("point: " + points [0]);
Debug.Log ("Destination: " + agent.destination);
This is a bit of test code (manually setting only 1 position to go point[0], but it never will travel to that destination. SetDestination will never get updated to the points I want to set them. I've tried calculating paths (NavMeshPath) up front and everything but the destination path will not change or reset weirdly enough. I've also had the while (float.Epsilon < agent.remainingDistance) loop in the wander function as well, but with no luck since it will remain in that loop forever since there's no path to go to.
I might be missing something here, or my logic is wrong in this case. Hopefully someone could give me a little push or maybe some extra debugging options, because I have no idea why the destination doesn't get updated in my wander function.
Consider using triggers on waypoints instead, as this will be even less taxing for the system. Below is an example that will allow you to have a variable number of waypoints easily, by getting all WayPoint type children from a parent transform. The only Coroutine that is used is for idle as you said you didn't want your Update function cluttered.
I've also exposed a couple of additional variables, including a min and max idle time, and a patrol chance, which should be set to a value less than or equal to 1, representing a percentage chance of patrolling vs idle. You can also choose a min and max number of patrol points for the agent to navigate to.
In RandomActivity you can also see that there is error handling for infinitely idle (no WayPoint children under parent). The agent will also not include the current Waypoint in it's list of points to navigate to, and will not navigate to a point already navigated to during it's current patrol (every time an element is added to m_targets, it's removed from m_availableTargets).
AgentController.cs
[RequireComponent(typeof(NavMeshAgent))]
public class AgentController : MonoBehaviour
{
[SerializeField]
private Transform m_waypointParent;
[SerializeField]
private float m_minIdle;
[SerializeField]
private float m_maxIdle;
[SerializeField]
private float m_patrolChance;
[SerializeField]
private int m_minPatrolPoints;
[SerializeField]
private int m_maxPatrolPoints;
private Waypoint[] m_waypoints;
private List<Waypoint> m_availableTargets;
private List<Waypoint> m_targets;
private Waypoint m_tempWaypoint;
private int m_currentTargetIndex;
private NavMeshAgent m_navMeshAgent;
public void Start()
{
m_waypoints = m_waypointParent.GetComponentsInChildren<Waypoint>();
m_targets = new List<Waypoint>();
m_navMeshAgent = GetComponent<NavMeshAgent>();
RandomActivity();
}
private void RandomActivity()
{
if (m_waypoints.Length == 0)
{
Debug.Log("Enemy will idle forever (no waypoints found)");
StartCoroutine(Idle(Random.Range(m_minIdle, m_maxIdle)));
return;
}
if(Random.Range(0f, 1f) <= m_patrolChance)
{
//Available waypoints
m_availableTargets = new List<Waypoint>(m_waypoints);
//Remove currentpoint
if(m_targets.Count > 0)
m_availableTargets.Remove(m_targets[m_targets.Count - 1]);
//Reset list
m_targets.Clear();
m_currentTargetIndex = -1;
//Add patrol points
for (int i = 0; i < Random.Range(m_minPatrolPoints, m_maxPatrolPoints + 1); i++)
{
m_tempWaypoint = m_availableTargets[Random.Range(0, m_availableTargets.Count)];
m_targets.Add(m_tempWaypoint);
m_availableTargets.Remove(m_tempWaypoint);
}
NextWaypoint(null);
}
else
StartCoroutine(Idle(Random.Range(m_minIdle, m_maxIdle)));
}
public void NextWaypoint(Waypoint p_waypoint)
{
//Collided with current waypoint target?
if ((m_currentTargetIndex == -1) || (p_waypoint == m_targets[m_currentTargetIndex]))
{
m_currentTargetIndex++;
if (m_currentTargetIndex == m_targets.Count)
RandomActivity();
else
{
Debug.Log("Target: " + (m_currentTargetIndex + 1) + "/" + m_targets.Count + " (" + m_targets[m_currentTargetIndex].transform.position + ")");
m_navMeshAgent.SetDestination(m_targets[m_currentTargetIndex].transform.position);
}
}
}
private IEnumerator Idle(float p_time)
{
Debug.Log("Idling for " + p_time + "s");
yield return new WaitForSeconds(p_time);
RandomActivity();
}
}
Note that for this to work, I have created a tag called Enemy so as to easily differentiate between enemies and any other tiggers in your game.
WayPoint.cs
[RequireComponent(typeof(BoxCollider))]
public class Waypoint : MonoBehaviour
{
public void OnTriggerEnter(Collider p_collider)
{
if (p_collider.tag == "Enemy")
p_collider.GetComponent<AgentController>().NextWaypoint(this);
}
}
I know this is an old post but hope this helps someone.

Categories