Unity WaitForSecondsRealtime doesn't working when time.timescale = 0 - c#

I have an animation that I want to play after my character dies. I'm trying to activate the Game Over screen after this animation is complete, but it doesn't work after yield return new WaitForSecondsRealtime(3f);
Here is my code:
private void OnCollisionEnter2D(Collision2D collision)
{
if (collision.gameObject.tag == "DeathArea")
{
StartCoroutine(Dead());
}
}
IEnumerator Dead()
{
animDie.SetActive(true);
animDeath.SetTrigger("Die");
Time.timeScale = 0;
yield return new WaitForSecondsRealtime(3f);
animDie.SetActive(false);
isDead = true;
deathScreen.SetActive(true);
managerGame.Medal();
}
Thank you!

You could add an event/function to be called at the end of your animation.
Unity lets you do that through the Animation window really easily.
Add an event:
Choose your function from an attached script:

The coroutine is firing off, and Time.timescale is surely breaking things. I'd use it very sparingly if I were you, as this setting also persists between scenes.

I finally found the solution to the problem.
Using the Time.timeScale = 0; code is not a problem. The real problem is that I destroyed my character because it's a death animation. Since my character died, the rest of the code became inoperable.
So instead of killing it, I made it invisible using localScale.
But the solution above is much more useful. Thank you for your answers.

Please change WaitForSecondsRealtime(3f) to WaitForSeconds(3f)
result
yield return new WaitForSeconds(3f);

Related

VR speedrun timer

I am working on a VR speedrun game and I need a timer. The timer doesn't need to be showed in the screen for the player, just on the map I made. It needs to start when the player (VR) passes a specific point and end when it reaches a different point. If anyone has an idea of how to make this work I would really appreciate it.
On the start line you could have an empty gameobject with a trigger collider on it, and in the OnTriggerEnter event you could start a Coroutine that keeps track of the time, and on the finish line you'd have another trigger collider that sets a flag and stops the timer.
Something along the lines of this should work:
using UnityEngine;
using System;
public class Player : MonoBehaviour {
private bool _isTimerStarted = false;
private float _timeElapsed = 0;
private void OnTriggerEnter(Collider other) {
if (other.gameObject.name.Equals("Start Line")) {
_isTimerStarted = true;
StartCoroutine(StartTimer());
} else if (other.gameObject.name.Equals("Finish Line") {
_isTimerStarted = false;
}
}
IEnumerator StartTimer() {
while (_isTimerStarted) {
_elapsedTime += Time.deltaTime;
yield return null;
}
yield break;
}
}
For this to work just make sure your player has a RigidBody attached or else no collision will be detected :)
If you want a "timer" as in "show the elapsed time since some event" you might take a look at Stopwatch
var sw = Stopwatch.StartNew();
...
Console.WriteLine(sw.Elapsed.ToString());
The stopwatch is primarily intended for performance measurements. But it is easy to use, so if it fits your use case you might as well make use of it, even if the resolution and accuracy is much greater than you need.

OnTriggerStay2D performance/alternatives

I got a simple "OnTouch" script on my enemies, which knocks back the player if they come in contact. The player then gets invincible for a short time. Something like this:
void OnTriggerEnter2D(Collider2D collider) {
if (collider.gameObject.CompareTag("Player")) {
if (Time.time > isInvincible) {
isInvincible = Time.time + invincibleTimer;
if (enemy.IsFacingRight) {
player.SetVelocity(knockback * Vector2.right);
} else {
player.SetVelocity(knockback * Vector2.left);
}
}
}
}
(SetVelocity is just a method i use to set.. velocity)
The problem with this is when the player gets invincible after been pushed away. While invincible you can then run on top of an enemy and stay there, even after the invincible timer runs out. Which i guess makes sense since it only triggers on enter.
Using the same code but inside a "OnTriggerStay2D", works as expected. You get pushed away, go invincible, run on top of an enemy, invincible runs out and you then get pushed away out of the enemy.
But having multiple enemies running around with OnTriggerStay colliding with different objects feels like it would be bad performance wise? Is there any more efficient way to do this? Or is TriggerStay the way to go?
The way I found is manually tracking the collisions like this:
List<Collider2D> hitColliders = new List<Collider2D>();
void OnTriggerEnter2D(Collider2D collision) {
if (hitColliders.Contains(collision)) { return; }
hitColliders.Add(collision);
}
void OnTriggerExit2D(Collider2D collision) {
hitColliders.Remove(collision);
}
// Perform operations to the colliders.
void Update() {
foreach (var col in hitColliders) { DoStuff(col); }
}
Although you may not run into performance problems even with the solution you have now, if you do in the future you can try using Physics.IgnoreLayerCollision:
At the start of your invincibleTimer call:
IgnoreLayerCollision(playerLayer, enemyLayer, true);
And at the end of your timer call:
IgnoreLayerCollision(playerLayer, enemyLayer, false);
And acording to the docs:
IgnoreLayerCollision will reset the trigger state of affected
colliders, so you might receive OnTriggerExit and OnTriggerEnter
messages in response to calling this.
This means that when you call IgnoreLayerCollision(false), OnTriggerEnter will be called again, even if you are already on top of an enemy. This is exactly the behaviour you are after.

Detect when VideoPlayer has finished playing

I have a MovieController class that manages videos in my project. I'm using the new video player component introduced in Unity 5.6.
I would like to call a method when a movie has finished playing. So far, this method is only a Debug.Log, as you can see:
using UnityEngine;
using UnityEngine.Video;
public class MovieController : MonoBehaviour
{
private VideoPlayer m_VideoPlayer;
void Awake ()
{
m_VideoPlayer = GetComponent<VideoPlayer>();
m_VideoPlayer.loopPointReached += OnMovieFinished; // loopPointReached is the event for the end of the video
}
void OnMovieFinished(VideoPlayer player)
{
Debug.Log("Event for movie end called");
player.Stop();
}
}
My issue is that the OnMovieFinished is not called at the end of the video, and I just end up with a black screen and nothing in the console.
I don't trust or use the events from the VideoPlayer API because I've ran into problems with them too. What you see is a bug that hasn't been fixed for months.
If you want to detect if video has finished playing, use a coroutine to to check videoPlayer.isPlaying every frame in a while loop. When the while loop exists, it means that the video has finished playing.
while (videoPlayer.isPlaying)
{
yield return null;
}
//Done Playing
I can't test this right now to check if the pause function affects videoPlayer.isPlaying but you can test that by yourself.
Another way to do this is to check if frame is the-same as frameCount. When this is true, you know that the video has finished playing.
if(videoPlayer.frame == videoPlayer.frameCount)
{
//Video has finshed playing!
}
If none of these solved your problem then file for a bug report.
Funnily enough, this still doesn't work completely as expected in Unity 2018.4.25 (current version I'm working with). Playing videos on start-up will often result in isPlaying being false (jumping out of the video without playing it, whilst and testing for
(videoPlayer.frame == videoPlayer.frameCount)
doesn't seem to work reliably on mobile devices (android) as the video will stop playing (isPlaying == false) before the video has played all its frames (usually a couple short, which I assume is frame skipping).
I found
if (( videoPlayer.frame) > 0 && (videoPlayer.isPlaying == false))
captures the use case problems I was running into on mobile, however there may be more interesting use cases and combinations to deal with. I put the test above into an update() rather than a co-routine.
HTH.
using UnityEngine.Video;
private VideoPlayer vid;
private float beginning;
void Awake()
{
vid = GetComponent<VideoPlayer>();
beginning = Time.time;
vid.Play();
StartCoroutine(waitmethod());
}
IEnumerator waitmethod()
{
while (Time.time - beginning < vid.length)
{
yield return null;
}
runyournext();
}
You can use
videoPlayer.loopPointReached += OnMovieFinished;
//the action on finish
void OnMovieFinished(UnityEngine.Video.VideoPlayer vp)
{
vp.playbackSpeed = vp.playbackSpeed / 10.0F;
}
you can see more here https://docs.unity3d.com/ScriptReference/Video.VideoPlayer.html

Sound stops working when function's added

When I add EndGame();
The sound stops playing when the game ends.
If I remove the function, the sound plays fine.
I tried adding the gover.Play to EndGame, but that didn't work either.
This bug doesn't really make sense to me as to why it's happening.
Happens in both Editor and on live device.
Thank you.
Here is my code:
Update() {
if (Input.GetMouseButtonDown(0))
{
}
else
{
AudioSource[] aSources = GetComponents<AudioSource> ();
perfecthit = aSources [0]; // Works
gover = aSources [1]; // Works without EndGame()
miss = aSources [2]; // Works
gover.Play ();
EndGame ();// Adding this makes gover.Play stop working
}
}
private void EndGame()
{
Social.ReportScore (scoreCount, "--", (bool success) => {
//SUCCESS or FAIL
});
PlayerPrefs.SetInt("byte", PlayerPrefs.GetInt("byte") + scoreCount / 2);
if (PlayerPrefs.GetInt ("score") < scoreCount)
PlayerPrefs.SetInt ("score", scoreCount);
gameOver = true;
endPanel.SetActive (true);
theStack[stackIndex].AddComponent<Rigidbody>();
stack.SetActive (false);
demoObj.SetActive (true);
}
I am assuming that it stops playing due to the AudioSource no longer being available.
Inside the EndGame() you are setting stack.SetActive (false);. If this AudioSource is attached to the object, then it will be no longer available. Especially considering you are re-obtaining your AudioSources At least 30 times a second!
With that I would also like to advice you to move your audio logic outside of the Update and maybe look into using the Start, or awake instead

Disable GameObject but continue to run Coroutine

I have a BonusController script as a component of a Bonus gameObject. This Bonus must be destroyed on collision but has to "act" for a few seconds. I have started a coroutine for this, but the script stops its execution (I think it's because I set the gameObject inactive). Here is the code of BonusController:
void OnTriggerEnter2D(Collider2D col)
{
StartCoroutine(speedUp(1));
gameObject.SetActive(false);
}
IEnumerator speedUp(float seconds)
{
Debug.Log("before");
yield return new WaitForSeconds(seconds);
Debug.Log("after"); // <--- This is never called
}
How can I remove the object and don't stop the coroutine script execution?
Can't you just disable the mesh renderer and collider? This way the gameobject will still exists, but the user won't be able to see it.
You cannot pull the ground on which you are standing. :)
Just disable the SpriteRenderer as you are using 2D methods. And keep the object alive and enable.
{
StartCoroutine(speedUp(1));
//gameObject.SetActive (false);
GetComponent<SpriteRenderer> ().enabled = false;
GetComponent<Collider2D> ().enabled = false;
// Above line will deactivate the first collider it will find.
}
IEnumerator speedUp(float seconds)
{
Debug.Log("before");
yield return new WaitForSeconds(seconds);
Debug.Log("after"); // <--- This is never called
}
In order to destroy the object after some time, call destroy inside the coroutine after the yield.
yield return WaitForSeconds(second);
Destroy(this);
Now it is properly destroyed and will free up memory opposed to still being there, invisible, but taking up resources.

Categories

Resources