public IEnumerator Screen1()// coroutine
{
LaptopCamera.Play ("Laptop");
p_material_LaptopScreen.mainTexture =p_text_BlueScreen[0];
p_transform_CorporatePV.localScale = new Vector3 (0.5f, 0.5f, 0.5f);
yield return new WaitForSeconds (2.5f);
p_gameobject_Training_Laptop[0].SetActive (true);
p_gameobject_Training_Laptop[1].SetActive (true);
p_material_LaptopTraining.mainTexture = p_text_Tarining [0];
yield return new WaitForSeconds (2.5f);
p_audioSource_Galderma.Stop ();
p_audioSource_Galderma.clip = p_audioclip_link;
p_audioSource_Galderma.Play ();
yield return new WaitUntil(() => p_audioSource_Galderma.isPlaying == false);
P_GameObject_UI.SetActive (true);
p_gameoject_CheckList_Button.SetActive (true);
p_int_break_scene1 = 2;
}//p_int_break_scene1
public void PlayPause()//play and pause button
{
if(p_int_play_pause == 0)
{
p_int_play_pause = 1;
p_image_PlayPause.sprite = p_sprite_Pause;
Time.timeScale = 0;
AudioSource[] go = FindObjectsOfType(typeof(AudioSource)) as AudioSource[];
foreach (AudioSource g in go)
{
if (g.isPlaying)
{
g.Pause();
pausedSources.Add(g);
}
}
}//p_int_play_pause
else if (p_int_play_pause == 1)
{
p_int_play_pause = 0;
p_image_PlayPause.sprite = p_sprite_Play;
if (p_int_check_list_opened == 0)
{
Time.timeScale = 1;
foreach (AudioSource source in pausedSources)
{
source.Play ();
}
pausedSources.Clear ();
}
}//p_int_play_pause
I am calling the coroutine screen1.I have play pause button to play and pause the game. I want the coroutine to wait still my audio plays fully.
yield return new WaitUntil(() => p_audioSource_Galderma.isPlaying == false);
When i pause the game that will pause the audio source
g.Pause();
then the p_audioSource_Galderma.isPlaying == false); and the coroutine complete with out playing the audio source.
Is there a way to coroutine to wait still my audio plays out completely even if i pause the game will not effect this.
I do not want to use yield return new WaitForSeconds since that is frame dependent.Is there a another way to coroutine wait still audio plays completely even if i pause the game.
yield return new WaitForSeconds (p_audioSource_Galderma.clip.length);
will not work for since there is more delay needed to play the animation and show the text.Is there a way to delay still audio plays fully
Since you are using Time.timeScale = 0 to pause, you can use:
yield return new WaitUntil(
() => !p_audioSource_Galderma.isPlaying
&& Time.timeScale != 0
);
You may need to change how you unpause, changing Time.timeScale after you play the AudioSources:
foreach (AudioSource source in pausedSources)
{
source.Play ();
}
Time.timeScale = 1;
Sidenote, you don't need to write out something == false, you can just do !something and it is often easier to read.
If you change yield return new WaitUntil(() => p_audioSource_Galderma.isPlaying == false); to yield return new WaitForSeconds(p_audioSource_Galderma.clip.length); you will achieve the same effect.
I even prefer it this way, since audio is independent from frames rendering.
Related
I tried to make a mob spawner which does followings:
1) spawns pre-defined amount of mobs with time interval of choice
2) checks if spawned gameobject destroyed if so spawns new ones till it reaches maximum amount again
Code works but i still think there can be improvements i want it to be mmo like slot spawner with pre defined maximum mob amount and intervals between every spawn
Issues im having:
1) at start works properly by 5 sec intervals between spawns but sometimes after you delete gameobject next spawn in line spawns instantly or very quickly
private void Start()
{
spawnedCount = gameObject.transform.childCount;
if (spawnedCount != 6)
isSpawning = false;
}
private void Update()
{
spawnedCount = gameObject.transform.childCount;
if (!isSpawning && spawnedCount < maxSpawnCount + 1) // check if should start spawn status and if coroutine currently working already isSpawning = false > yes you can can if you want to spawn or not isSpawning = True > no it is already spawning you cant check anymore and send requests
{
isSpawning = true; // set spawning status to true
StartCoroutine(DelayedSpawn(delayInterval));
}
}
IEnumerator DelayedSpawn(float delay)
{
yield return new WaitForSecondsRealtime(delay);
if (spawnedCount <= maxSpawnCount)
{
GetRandomZombie(); // gets random zombie prefab to instantiate
spawnedObj = Instantiate(PrefabToSpawn, new Vector3(gameObject.transform.position.x + Random.Range(-5f, 5), 0, gameObject.transform.position.z + Random.Range(-5f, 5)), transform.rotation);
spawnedObj.transform.parent = gameObject.transform;
}
if (spawnedCount <= maxSpawnCount )
StartCoroutine(DelayedSpawn(delayInterval));
else if (spawnedCount == 6)
{
isSpawning = false;
yield break;
}
}
I would do this like this. It seems cleaner.
Also I would suggest GetRandomZombie() method to return a prefab. That would be cleaner as well.
A reminder: The game object with script that has the coroutine method is disabled/deleted somehow, unity gives an error and coroutine stops. You can solve this like this:
private void OnDisable() //To prevent the error
{
StopAllCoroutines();
}
private void OnEnable() //To start the coroutine again whenever the spawner object is enabled again.
{
StartCoroutine(DelayedSpawn());
}
private IEnumerator DelayedSpawn()
{
while (true)
{
yield return new WaitForSecondsRealtime(delayInterval);
if (gameObject.transform.childCount <= maxSpawnCount)
{
GetRandomZombie(); // gets random zombie prefab to instantiate
spawnedObj = Instantiate(PrefabToSpawn, new Vector3(gameObject.transform.position.x + Random.Range(-5f, 5), 0, gameObject.transform.position.z + Random.Range(-5f, 5)), transform.rotation);
spawnedObj.transform.parent = gameObject.transform;
}
}
}
In my script i did that when the player is on the top of the platform move it up.
It's working fine. But now i want to make that once it got up play the clip "Down".
using UnityEngine;
using System.Collections;
using System.Reflection;
public class DetectPlayer : MonoBehaviour {
GameObject target;
public void ClearLog()
{
var assembly = Assembly.GetAssembly(typeof(UnityEditor.ActiveEditorTracker));
var type = assembly.GetType("UnityEditorInternal.LogEntries");
var method = type.GetMethod("Clear");
method.Invoke(new object(), null);
}
void OnCollisionEnter(Collision collision)
{
if (collision.gameObject.name == "Platform")
{
Debug.Log("Touching Platform");
}
}
void OnTriggerEnter(Collider other)
{
if (other.gameObject.name == "OnTop Detector")
{
Debug.Log("On Top of Platform");
GameObject findGo = GameObject.Find("ThirdPersonController");
GameObject findGo1 = GameObject.Find("Elevator");
findGo.transform.parent = findGo1.transform;
target = GameObject.Find("Elevator");
target.GetComponent<Animation>().Play("Up");
}
}
}
After the line
target.GetComponent<Animation>().Play("Up");
I want when it finish playing it play the down:
target.GetComponent<Animation>().Play("Down");
While both answers should work, another method of doing this with coroutine and the IsPlaying function. You use the coroutine solution if you also want to perform other task after the animation.
For the Animation system:
The old Unity animation playback system. This should not be used in your new Project unless you are still using old Unity version.
IEnumerator playAndWaitForAnim(GameObject target, string clipName)
{
Animation anim = target.GetComponent<Animation>();
anim.Play(clipName);
//Wait until Animation is done Playing
while (anim.IsPlaying(clipName))
{
yield return null;
}
//Done playing. Do something below!
Debug.Log("Done Playing");
}
For the Animator system
This is the new Unity animation playback system. This should be used in your new Project instead of the Animation API. In terms of performance, it's better to use the Animator.StringToHash and compare the current state by hash number instead of the IsName function which compares string since the hash is faster.
Let's say that you have state names called Jump, Move and Look. You get their hashes as below then use the function for playing and waiting for them them below:
const string animBaseLayer = "Base Layer";
int jumpAnimHash = Animator.StringToHash(animBaseLayer + ".Jump");
int moveAnimHash = Animator.StringToHash(animBaseLayer + ".Move");
int lookAnimHash = Animator.StringToHash(animBaseLayer + ".Look");
public IEnumerator PlayAndWaitForAnim(Animator targetAnim, string stateName)
{
//Get hash of animation
int animHash = 0;
if (stateName == "Jump")
animHash = jumpAnimHash;
else if (stateName == "Move")
animHash = moveAnimHash;
else if (stateName == "Look")
animHash = lookAnimHash;
//targetAnim.Play(stateName);
targetAnim.CrossFadeInFixedTime(stateName, 0.6f);
//Wait until we enter the current state
while (targetAnim.GetCurrentAnimatorStateInfo(0).fullPathHash != animHash)
{
yield return null;
}
float counter = 0;
float waitTime = targetAnim.GetCurrentAnimatorStateInfo(0).length;
//Now, Wait until the current state is done playing
while (counter < (waitTime))
{
counter += Time.deltaTime;
yield return null;
}
//Done playing. Do something below!
Debug.Log("Done Playing");
}
For a solution specifically for your particular problem with the collision callback function (OnTriggerEnter), there are two possible ways to do that:
1.Start a coroutine function to play the animation after trigger detection:
void OnTriggerEnter(Collider other)
{
if (other.gameObject.name == "OnTop Detector")
{
Debug.Log("On Top of Platform");
GameObject findGo = GameObject.Find("ThirdPersonController");
GameObject findGo1 = GameObject.Find("Elevator");
findGo.transform.parent = findGo1.transform;
target = GameObject.Find("Elevator");
StartCoroutine(playAnim(target));
}
}
IEnumerator playAnim(GameObject target)
{
Animation anim = target.GetComponent<Animation>();
anim.Play("Up");
//Wait until Up is done Playing the play down
while (anim.IsPlaying("Up"))
{
yield return null;
}
//Now Play Down
anim.Play("Down");
}
OR
2.Make the OnTriggerEnter function a coroutine(IEnumerator) instead of void function:
IEnumerator OnTriggerEnter(Collider other)
{
if (other.gameObject.name == "OnTop Detector")
{
Debug.Log("On Top of Platform");
GameObject findGo = GameObject.Find("ThirdPersonController");
GameObject findGo1 = GameObject.Find("Elevator");
findGo.transform.parent = findGo1.transform;
target = GameObject.Find("Elevator");
Animation anim = target.GetComponent<Animation>();
anim.Play("Up");
//Wait until Up is done Playing the play down
while (anim.IsPlaying("Up"))
{
yield return null;
}
//Now Play Down
anim.Play("Down");
}
}
One way to do this without needing to check manually at all is to use queuing
target.GetComponent<Animation>().PlayQueued("Down", QueueMode.CompleteOthers);
This code will wait for any other animation currently playing on the object to finish before playing the queued animation.
The Unity API page regarding this topic
in my project im trying to count the diferent objects and simulate a little animation, for example i have stars in my game, and i want to count the number of stars in the final of the game from 0 trough the number of stars the user got, so i did this:
public void youWin()
{
audio.Stop ();
StartCoroutine (activatePanel ());
}
IEnumerator activatePanel()
{
yield return new WaitForSeconds (3f);
pausePanel2.SetActive (true);
for (int i = 0; i <= stars; i++) {
yield return new WaitForSeconds (0.2f);
starText2.text = i + "";
}
}
my code worked well for 0.3f on the for loop wait for seconds, but it is too slow, i want it for 0.2f, but something strange happen sometimes it get like a bug and the first number seems to go back, it doesn't count right, someone know what is happening?
It very likely that the activatePanel function is being called from another place while it is already running or the script that contains this code is attached to multiple GameObjects and the activatePanel is again, being called by another function. You can use flag to stop this from happening.
If the coroutine function is already running, use yield break; to break out of it.
bool isRunning = false;
IEnumerator activatePanel()
{
//Exit if already running
if (isRunning)
{
yield break;
}
//Not running, now set isRunning to true then run
isRunning = true;
yield return new WaitForSeconds(3f);
pausePanel2.SetActive(true);
WaitForSeconds waitTime = new WaitForSeconds(0.2f);
for (int i = 0; i <= stars; i++)
{
yield return waitTime;
starText2.text = i.ToString();
}
//Done running, set isRunning to false
isRunning = false;
}
Well i solved it with all of you guys help, actually you all where right, i thaught i was calling the youWin function just 1 time, but i forgot this is unity and i called the youWin inside a trigerEnter function, that means that the object keep enter the triger function and called the youWin function, thank you all here is what i mean with that
Solved it with the bool entered
public class Victory : MonoBehaviour {
Manager gameManager;
// Use this for initialization
public AudioClip clip;
private AudioSource audio;
public Animator girl;
private bool entered;
void OnTriggerEnter(Collider c)
{
if (c.gameObject.tag == "Player" && !entered) {
gameManager.win = true;
audio.clip = clip;
audio.Play ();
gameManager.Ball.GetComponent<MoveBall> ().enabled = true;
girl.SetBool ("win",true);
entered = true;
gameManager.youWin ();
}
}
void Start () {
gameManager = GameObject.Find ("GameController").GetComponent<Manager> ();
audio = GetComponent<AudioSource> ();
entered = false;
}
// Update is called once per frame
void Update () {
}
}
in my project I want a sound to play and then for an objects active state to be set to false, at the moment both are happening at the same time so the sound doesn't play. If I keep the active state as true then I will hear the sound play.
How can I make sure that the audio finishes before the set active state is switched to false, any help or advice is appreciated.
Here's a selection of my code;
void OnTriggerEnter(Collider other)
{
if (other.tag == "Pick Up")
{
if (!isPlayed) {
source.Play ();
isPlayed = true;
}
}
if (other.gameObject.CompareTag ("Pick Up"))
{
other.gameObject.SetActive (true);
count = count + 1;
SetCountText ();
}
}
You could hide the object using its renderer, as well as turning off any colliders, play the sound, then destroy/set it to inactive:
renderer.enabled = false;
gameObject.collider.enabled = false;
audio.PlayOneShot(someAudioClip);
Destroy(gameObject, someAudioClip.length); //wait until the audio has finished playing before destroying the gameobject
// Or set it to inactive
Use coroutine as Joe Said. Start coroutine each time the collided object is enabled.
void OnTriggerEnter(Collider other)
{
if (other.tag == "Pick Up")
{
if (!isPlayed) {
source.Play ();
isPlayed = true;
}
}
if (other.gameObject.CompareTag ("Pick Up"))
{
other.gameObject.SetActive (true);
count = count + 1;
SetCountText ();
StartCoroutine(waitForSound(other)); //Start Coroutine
}
}
IEnumerator waitForSound(Collider other)
{
//Wait Until Sound has finished playing
while (source.isPlaying)
{
yield return null;
}
//Auidio has finished playing, disable GameObject
other.gameObject.SetActive(false);
}
I have a problem. Now I'm developing game in Unity when the condition is score 2. I want to wait for ten seconds before do the code change scene (Application.LoadLevel) (I'm using C# for develop)
But this code when score = 2 It will change to "scenea_5"
It can't wait for ten seconds
void OnTriggerEnter( Collider collider ){
if (collider.name == front_hztleft) {
audio.Play ();
}
if (collider.name == left_hztleft) {
audio.Play ();
score ++;
Debug.Log (string.Format (scoreSyntax, score));
endtime = DateTime.Now.ToString("HH:mm:ss");
InsertResult();
}
if (score == 2) {
StartCoroutine(Waiting());
Application.LoadLevel("scene_a5");
}
}
IEnumerator Waiting()
{
yield return new WaitForSeconds (10);
}
It can run and compile not without error.
Put the scene loading inside the Coroutine.
yield return new WaitForSeconds(10);
Application.LoadLevel("scene_a5");