How to call coroutine from another script? UNITY - c#

This is the script where the touch action, the main game is on it
var addingGoldPerSec = new GameObject();
var buttonInstance = addingGoldPerSec.AddComponent<ItemButton>();
StartCoroutine(buttonInstance.addGoldLoop());
And this is the one where I have the coroutine
public double goldPerSec
{
get
{
if (!PlayerPrefs.HasKey("_goldPerSec"))
{
return 0;
}
string tmpStartGoldPerSec = PlayerPrefs.GetString("_goldPerSec");
return double.Parse(tmpStartGoldPerSec);
}
set
{
PlayerPrefs.SetString("_goldPerSec", value.ToString());
}
}
public void updateUI()
{
priceDisplayer.text = LargeNumber.ToString(currentCostPerSec).ToString();
coinDisplayer.text = "PER SEC\n" + LargeNumber.ToString(goldPerSec).ToString();
levelDisplayer.text = "LEVEL\n" + level + "/150".ToString();
}
public IEnumerator addGoldLoop()
{
while (DataController.Instance.gold <= 1e36)
{
DataController.Instance.gold += goldPerSec;
if (Gameplay.Instance.booster == 1)
{
yield return new WaitForSeconds(0.25f);
}
else if (Gameplay.Instance.booster == 0)
{
yield return new WaitForSeconds(1.0f);
}
}
}
And this is a third Script where I manage the data stored into PlayerPrefs
public void loadItemButton(ItemButton itemButton)
{
itemButton.level = PlayerPrefs.GetInt("_level",1);
PlayerPrefs.GetString("_costPerSec", itemButton.currentCostPerSec.ToString());
PlayerPrefs.GetString("_goldPerSec",itemButton.goldPerSec.ToString());
}
public void saveItemButton(ItemButton itemButton)
{
PlayerPrefs.SetInt("_level", itemButton.level);
PlayerPrefs.SetString("_costPerSec", itemButton.currentCostPerSec.ToString());
PlayerPrefs.SetString("_goldPerSec", itemButton.goldPerSec.ToString());
}
I have the second script which is the coroutine one attached into several gameobjects where exists an Upgrade button, per upgrade increases the coin by second you earn, the main problem here is that the coroutine just stops after I touch the screen, so I made another code into the main script so by that way the coroutine will keep working even after touched the screen, so I made a script where is the GameObject, but it just throws me NullReferenceException, tried a check with TryCatch and throws me that the problem is coming from the GameObject that I have created on the main script, but if is that way I need to attach to the main object where the main script exists, like more than 10 gameobjects where the coroutine exists, I think Singleton is not the way, it deletes me all the information above there by instantiating on Awake, I never thought about making static so I did as you told me and I need to change almost of my code, every text is attached into each gameobjects, to make a non-static member work with a static-member need to delete Monobehaviour but it just makes the game explode, thanks for helping me.

Create two scripts and attach them, for example, at Main Camera. The first script contains your timer with all variables:
using UnityEngine;
using System.Collections;
public class TimerToCall : MonoBehaviour {
//Create your variable
...
//Your function timer
public IEnumerator Counter() {
...
}
}
In the second script, you will call the timer:
public class callingTimer : MonoBehaviour {
void Start() {
//TimerToCall script linked to Main Camera, so
StartCoroutine(Camera.main.GetComponent<TimerToCall>().Counter());
}
}
if you want to have for example the second script not linked to any gameObjet you could use static property:
in first script linked:
void Start()
{
StartCoroutine(CallingTimer.ExampleCoroutine());
}
in second script not linked, you use the static property:
using System.Collections;
using UnityEngine;
public class CallingTimer
{
public static IEnumerator ExampleCoroutine()
{
//Print the time of when the function is first called.
Debug.Log("Started Coroutine at timestamp : " + Time.time);
//yield on a new YieldInstruction that waits for 5 seconds.
yield return new WaitForSeconds(5);
//After we have waited 5 seconds print the time again.
Debug.Log("Finished Coroutine at timestamp : " + Time.time);
}
}

Related

How to prevent the reload of already loaded gameobjects in Unity?

I'm currently developing a game in Unity and I ran into a small problem. I'm working on a restart function that gets called automatically when the player dies and loads the first scene again. However for some reason when reloading the scene games objects are duplicated with the version of the gameobject that was active at the time of death being inactive and the version that gets loaded as should be loaded getting set to active and so on every time the player dies adding a new duplicate of the same gameobjects to the hierarchy. I tried to solve this problem in multiple ways. First by trying to check each the gameobjects that get duplicated already have an instance of themselves running by attaching a script that checks every time a change in scene occurs wether or not their already is an instance of the gameobjects present:
public static GameObject Instance;
void Awake()
{
if(Instance){
DestroyImmediate(gameObject);
}else
{
DontDestroyOnLoad(gameObject);
Instance = this;
}
}
This seemed to solve the problem at first but it became to teadious towards the end because the scripts made all of my other scene objects behave badly or not at all so I chose to look for another solution.
Secondly I tried to Destroy each individual gameobject before I start loading the first scene. This also seemed to work at first but now my object pooler just recreates new intances of the gameobjects that it adds too the hierarchy esentially displacing the same problem to other gameobjects.
Finally in order to solve this problem I tried to make my objectpooler run only once when the scene that requires it to be loaded gets called but this didn't seem to work either. Does anyone have any idea how I could solve this problem. This is part of the script responsible for loading the original scene upon player death:
void Restart()
{
GameObject[] allObjects = UnityEngine.Object.FindObjectsOfType<GameObject>();
foreach (GameObject gos in allObjects)
{
if (gos.activeInHierarchy)
{
if (gos != GameObject.Find("GameManager") && gos != GameObject.Find("ScreenBound"))
{
gos.SetActive(false);
}
}
}
MySceneManager.LoadScene(0, this);
}
How could I change this in order to be able to reload the original scene without having any previously loaded GameObject get duplicated and behave according to how it should in the scene in which it got loaded originally?
The class responsible for loading and deloading scenes:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
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 my ObjectPooler:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ObjectPooler : MonoBehaviour
{
[System.Serializable]
public class Pool
{
public string tag;
public GameObject prefab;
public int size;
}
#region Singleton
public static ObjectPooler Instance;
private void Awake()
{
if (Instance)
{
Destroy(this.gameObject);
return;
}
Instance = this;
DontDestroyOnLoad(this.gameObject);
}
#endregion
public List<Pool> pools;
public Dictionary<string, Queue<GameObject>> poolDictionary;
private Dictionary<string, Pool> prefabPools;
void Start()
{
poolDictionary = new Dictionary<string, Queue<GameObject>>();
foreach (Pool pool in pools)
{
Queue<GameObject> objectPool = new Queue<GameObject>();
for (int i = 0; i < pool.size; i++)
{
GameObject obj = Instantiate(pool.prefab);
DontDestroyOnLoad(obj);
obj.SetActive(false);
objectPool.Enqueue(obj);
}
poolDictionary.Add(pool.tag, objectPool);
}
}
private List<GameObject> currentlySpawnedObjects = new List<GameObject>();
public void Release(GameObject obj)
{
currentlySpawnedObjects.Remove(obj);
obj.SetActive(false);
obj.transform.SetParent(transform);
poolDictionary[obj.tag].Enqueue(obj);
DontDestroyOnLoad(obj);
}
public void ReleaseAll()
{
foreach (var child in currentlySpawnedObjects)
{
Release(child);
}
}
public GameObject SpawnFromPool(string tag, Vector3 position, Quaternion rotation)
{
if (!poolDictionary.ContainsKey(tag))
{
Debug.LogWarning("Pool with tag" + tag + " doesn't exist.");
return null;
}
GameObject objectToSpawn = poolDictionary[tag].Dequeue();
objectToSpawn.SetActive(true);
objectToSpawn.transform.position = position;
objectToSpawn.transform.rotation = rotation;
IPooledObject pooledObj = objectToSpawn.GetComponent<IPooledObject>();
if (pooledObj != null)
{
pooledObj.OnObjectSpawn();
}
poolDictionary[tag].Enqueue(objectToSpawn);
return objectToSpawn;
currentlySpawnedObjects.Add(objectToSpawn);
return objectToSpawn;
}
}
Depends of your needs you can try next ways:
Use singleton pattern if you need save single instance of objects. This case relevant for saving managers (GameplayManager, SceneController, AssetBundleManager, etc.) in other cases will be better to use other ways. To read more about implementation you can see this article.
Destroy all old objects when loaded new scene. To do this you can use SceneManager.LoadScene method with LoadSceneMode.Single as parameter. It will keep DontDestoryOnLoad objects but will remove all others.
I'm not sure but the first possible issue to me already seems to be that it is running in Coroutine on an object in the scene you are going to unload.
It is cool that this is doable but have in mind that the Coroutine will stop working as soon as the caller object/component is destroyed or disabled.
To avoid that I would move your script to an object in the DontDestroyOnLoadScene using a Singleton pattern.
The next issue might be you going by SceneIndex ... both scenes, the one you want to unload and the one you want to load have index 0!
So maybe you get a conflict between the scene additively loading and the one you want to unload.
This also might happen again when you called
var newScene = SceneManager.GetSceneByIndex(lastLoadedScene);
To avoid this I would rather go by scene reference for the unloading
public class MySceneManager : MonoBehaviour
{
private static MySceneManager instance;
// Lazy initialization
// With this you wouldn't even need this object in the scene
public static MySceneManager Instance
{
if(instance) return instance;
instance = new GameObject ("MySceneManager").AddComponent<MySceneManager>();
DontDestroyOnLoad(instance);
}
// Usual instant initialization having this object in the scene
private void Awake ()
{
if(instance && instance != this)
{
Destroy(gameObject);
return;
}
instance = this;
DontDestroyOnLoad(this);
}
public void LoadScene(int index)
{
StartCoroutine(loadNextScene(index));
}
private IEnumerator loadNextScene(int index)
{
// I didn't completely go through your ObjectPooler but I guess you need to do this
ObjectPooler.Instance.ReleaseAll();
// Instead of the index get the actual current scene instance
var currentScene = SceneManager.GetActiveScene();
var _async = SceneManager.LoadSceneAsync(index, LoadSceneMode.Additive);
_async.allowSceneActivation = false;
yield return new WaitWhile(() => _async.progress < 0.9f);
_async.allowSceneActivation = true;
yield return new WaitUntil(() => _async.isDone);
// You have to do this before otherwise you might again
// get by index the previous scene
var unloadAsync = SceneManager.UnloadSceneAsync(currentScene);
yield return new WaitUntil(()=>unloadAsync.isDone);
var newScene = SceneManager.GetSceneByBuildIndex(index);
SceneManager.SetActiveScene(newScene);
}
}
Alternatively since anyway you do nothing special while loading/unloading the scenes:
why using Additive scene loading at all if you could also simply call
ObjectPooler.Instance.ReleaseAll();
SceneManager.LoadSceneAsync(index);
without making it additive so the current scene is simply removed automatically as soon as the new scene is fully loaded.
Note: Types on Smartphone so no warranty but I hope the idea gets clear

Getting print to only print once when countdown Deltatime reaches certain number (Unity)

I have a 10-second countdown script that runs on DeltaTime.
In my Update function, I'm trying to make it print, only once, "Hello" whenever it reaches second 8.
The problem is that deltaTime is repeatedly printing "Hello" while it hangs on the 8th second, rather than only printing once, and I don't know how to stop that behavior.
I kept trying to introduce triggers in the if-block that set to 0 as soon as the block is entered but it still keeps continuously printing "Hello" so long as the timer is on second 8.
Countdown Timer
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class CountdownTimer : MonoBehaviour
{
float currentTime = 0f;
float startingTime = 10f;
public int n = 0;
public int switcher = 0;
// Start is called before the first frame update
void Start()
{
currentTime = startingTime;
}
// Update is called once per frame
void Update()
{
currentTime -= 1 * Time.deltaTime; //does it each frame
n = Convert.ToInt32(currentTime);
if (n == 8)
{
switcher = 1;
}
}
}
Update Method in different class
if (CountdownTimer.switcher == 1)
{
CountdownTimer.switcher = 0;
print("hey");
}
Any ideas on how to make print("hey") only happen once? It's important because later I would replace the print code with an important method and I need to make sure the method happens only once.
This is where you want to implement a system of subscriber with event/listener.
Add an event to the countdown, if countdown is meant to be unique, you can even make it static. Also, if the update is no longer needed after the setting of switcher to 1 then you can convert that to coroutine
public class CountdownTimer : MonoBehaviour
{
float currentTime = 0f;
float startingTime = 10f;
public static event Action RaiseReady;
// Start is called before the first frame update
void Start()
{
currentTime = startingTime;
StartCoroutine(UpdateCoroutine());
}
// Update is called once per frame
IEnumerator UpdateCoroutine()
{
while(true)
{
currentTime -= 1 * Time.deltaTime; //does it each frame
int n = Convert.ToInt32(currentTime);
if (n == 8)
{
RaiseReady?.Invoke();
RaiseReady = null; // clean the event
yield break; // Kills the coroutine
}
yield return null;
}
}
}
Any component that needs to know:
public class MyClass : MonoBehaviour
{
void Start()
{
CountdownTimer.RaiseReady += CountdownTimer_RaiseReady;
}
private void CountdownTimer_RaiseReady()
{
Debug.Log("Done");
// Remove listener though the other class is already clearing it
CountdownTimer.RaiseReady -= CountdownTimer_RaiseReady;
}
}
The solution #Everts provides is pretty good, but as you are using unity I would recommend a couple of tweaks to play nicer with the editor. Instead of a generic event I recommend using a UnityEvent from the UnityEngine.Events namespace. I would also advise against statics due to how unity goes about serializing them across scenes. There are some weird edge cases you can get into if you aren't familiar with how unity handles their serialization. If you just need to send a message to another object in the same scene I would actually recommend a game manager. You can safely do a GameObject.Find() in onvalidate() and link your variables to avoid a performance hit at runtime doing the find. If that data needs to carry across to a different scene for this message then use a ScriptableObject instead. It would look something like below.
Put this component on the scene's "Game Manager" GameObject
public class CountingPassthrough : MonoBehaviour
{
public CountdownTimer countdownTimer;
}
put this component on the scene's "Timer" GameObject
public class CountdownTimer : MonoBehaviour
{
public float startingTime = 10f;
public UnityEvent timedOut = new UnityEvent();
private void OnValidate()
{
if(FindObjectOfType<CountingPassthrough>().gameObject.scene == gameObject.scene && FindObjectOfType<CountingPassthrough>() != new UnityEngine.SceneManagement.Scene())
FindObjectOfType<CountingPassthrough>().countdownTimer = this;
}
private void Start()
{
StartCoroutine(TimerCoroutine());
}
// Coroutine is called once per frame
private IEnumerator TimerCoroutine()
{
float currentTime = 0f;
while (currentTime != 0)
{
currentTime = Mathf.Max(0, currentTime - Time.deltaTime);
yield return null;//wait for next frame
}
timedOut.Invoke();
}
}
Put this component on the GameObject you want to use the timer
public class user : MonoBehaviour
{
[SerializeField, HideInInspector]
private CountingPassthrough timerObject;
private void OnValidate()
{
if(FindObjectOfType<CountingPassthrough>().gameObject.scene == gameObject.scene && FindObjectOfType<CountingPassthrough>() != new UnityEngine.SceneManagement.Scene())
timerObject = FindObjectOfType<CountingPassthrough>();
}
private void OnEnable()
{
timerObject.countdownTimer.timedOut.AddListener(DoSomething);
}
private void OnDisable()
{
timerObject.countdownTimer.timedOut.RemoveListener(DoSomething);
}
private void DoSomething()
{
//do stuff here...
}
}
This workflow is friendly to prefabs too, because you can wrap the find() in onvalidate() with if(FindObjectOfType<CountingPassthrough>().gameObject.scene == gameObject.scene) to prevent grabbing the wrong asset from other loaded scenes. And again, if you need this to carry data across scenes then have CountingPassthrough inherit from ScriptableObject instead of MonoBehaviour, create the new ScriptableObject to your project folder somewhere, and ignore that extra if check to constrain scene matching. Then just make sure you use a function to find it that includes assets if you use the cross-scene ScriptableObject approach.
EDIT:Forgot nested prefabs edgecase in unity 2018+ versions. You need to add this to account for it: && FindObjectOfType<CountingPassthrough>() != new UnityEngine.SceneManagement.Scene() I've updated the code snippet above. Sorry about that.
Since Updtade() is called once per frame, then switcher is set to 1 once per frame during the 8th second (and there is a lot of frames in 1 sec).
An answer could be something like this to prevent it from printing again :
if (CountdownTimer.switcher == 1)
{
if (!AlreadyDisplayed)
{
print("hey");
AlreadyDisplayed = true;
}
}
Where AlreadyDisplayed is a Boolean set to false when declared.
This should do what you want to achieve. :)

Activate and deactivate an object not work

i try to make a fire place were i need to collect wood and use it on fire place but nothing happen, i have used the debug.log to see if the variable are correct receive the changes and its work perfect but the if i have fireonplace variable = 1 then activate the fireplace object the wood its inserted on the variable but the trigger for activate the fire not happen.
This is my code
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
public class enablefire : MonoBehaviour {
public GameObject Enable_Disable;
public static int woodonfire = 0;
public void Enable ()
{
Enable_Disable.SetActive (true);
}
public void Disable()
{
Enable_Disable.SetActive (false);
}
// Use this for initialization
void Start ()
{
Enable ();
//Enable_Disable.SetActive (false);
}
void Update()
{
if (woodonfire >= 1)
{
Enable_Disable.SetActive (true);
}
if (woodonfire == 0)
{
Enable_Disable.SetActive (false);
//Enable_Disable.SetActive (false);
}
}
}
change the name of the game object, as a rule the name has to begin with lowercase, try names like enableDisable;
also you already created a method to enable/disable the GameObject why don't you just change this code...
void Update()
{
if (woodonfire >= 1)
{
Enable();
} else if (woodonfire == 0)
{
Disable();
}
}
You have to move the script onto another object.
Since an inactive gameobject has all its components inactive, the script on it will be inactive too and you won't be able to active the object again. It may be a better option to make the particle system in another object which contains the script. ( You will basicly activate and de-activate your child object (fire) ).

How do I pause GameObject for a couple of seconds and unpause after a couple of seconds in unity3d

I need the gameobject to pause on its own in my scene for 7f or 8f and un-pause at 2f on its own. The script I have is letting my pause by key. Here is my script :
{
sing UnityEngine;
using System.Collections;
public class star : MonoBehaviour {
GameObject[] pauseObjects;
void Start () {
pauseObjects = GameObject.FindGameObjectsWithTag("Player");
}
void pauseGameobject()
{
if()
{
start coroutine("wait");
}
}
public ienumenator wait()
{
time.timescale = 0;
yield return new waitforsceonds(7);
time.timesale = 1;
}
void pauseGameobject()
{
if()
{
start coroutine("wait");
}
}
public ienumenator wait()
{
time.timescale = 0;
yield return new waitforsceonds(7);
time.timesale = 1;
}
}
You can use coroutines to insert delays in the update() loop. Coroutines use generators, which "yield", rather than functions/methods which "return". What this allows for is code that operates asynchronously while still being written in a linear fashion.
The built in coroutine you're most likely looking for is WaitForSeconds. To start a coroutine you simply call StarCoroutine() and pass in any method of type IEnumerator. This method will yield periodically. In the following example, WaitForSeconds(5) will yield after 5 seconds. Fractions of a second can also be used, represented by floats, for example 2.5 would be two and a half seconds.
using UnityEngine;
using System.Collections;
public class WaitForSecondsExample : MonoBehaviour {
void Start() {
StartCoroutine(Example());
}
IEnumerator Example() {
Debug.Log(Time.time); // time before wait
yield return new WaitForSeconds(5);
Debug.Log(Time.time); // time after wait
}
}
It's not too clear what you mean by pause on it's one, however I'm going to answer broadly, to try and help you.
If you whant to pause a single game object externally you can deactivate it and activate it accordingly with this code: gameObject.SetActive(false);
Instead if you want to pause the game object internally you can make a bool and in the update test wether or not it's true:
using UnityEngine;
using System.Collections;
bool update = false
public class ActiveObjects : MonoBehaviour
{
void Start ()
{
//Do stuff
}
void Update ()
{
if(update){
//Do stuff
}
//decide wether or not to pause the game object
}
}
If you want to pause the game you can set the Time.timeScale to 0, or just pause all game objects.
Here you can find how to make a timer, all you need to do is count down a variable using timeLeft -= Time.deltaTime;.
Hope I helped you,
Alex
Edit:
Ok, here is the script, keep in mind I have no way to test it ;)
using UnityEngine;
using System.Collections;
public class star : MonoBehaviour {
GameObject[] pauseObjects;
public float timer = 7;
float t = 0;
bool pause = false;
void Start () {
pauseObjects = GameObject.FindGameObjectsWithTag("Player");
t = timer;
}
void Update() {
if(pause){
if(t<0){
t=timer;
pause = false;
time.timescale = 1;
}else{
t -= Time.deltaTime;
time.timescale = 0;
}
}
}

Background music get louder each time a scene is loaded

I have a GameObject on level01 with an Audio Source and the script below.
When the game starts the script runs and music starts playing.
The problem I have is that each time I load a new level the sound gets louder. I don't understand why this is happening. Can someone explain why and give a solution or point me in the right direction?
using UnityEngine;
using System.Collections;
public class MusicManagerScript : MonoBehaviour
{
public AudioClip[] songs;
int currentSong = 0;
// Use this for initialization
void Start() {
DontDestroyOnLoad(gameObject);
}
// Update is called once per frame
void Update() {
if (audio.isPlaying == false) {
currentSong = currentSong % songs.Length;
audio.clip = songs[currentSong];
audio.Play();
currentSong++;
}
}
}
EDIT: I see that your answer was that your camera was simply closer to the 3D audio source, but I've put my answer here anyway as it is a common solution to a common problem.
You're instantiating your music manager every time you enter a scene with your music manager in it, but you're never destroying a music manager, which duplicates the sound. What you need is a singleton - a way of telling your code never to allow multiple instances. Try this:
public class MusicManagerScript : MonoBehaviour
{
private static MusicManagerScript instance = null;
public AudioClip[] songs;
int currentSong = 0;
void Awake()
{
if (instance != null)
{
Destroy(this);
return;
}
instance = this;
}
// Use this for initialization
void Start()
{
DontDestroyOnLoad(gameObject);
}
// Update is called once per frame
void Update()
{
if (audio.isPlaying == false)
{
currentSong = currentSong % songs.Length;
audio.clip = songs[currentSong];
audio.Play();
currentSong++;
}
}
void OnDestroy()
{
//If you destroy the singleton elsewhere, reset the instance to null,
//but don't reset it every time you destroy any instance of MusicManagerScript
//because then the singleton pattern won't work (because the Singleton code in
//Awake destroys it too)
if (instance == this)
{
instance = null;
}
}
}
Because instance is static, every music manager script will have access to it. If it's been set already, they destroy themselves when they're created.

Categories