So I am making a 2D app in Unity similar to any "dodge the falling objects". I have added a pause function, but I want to add a countdown timer after you exit the pause menu. What I have right now is something like this:
public void Pressed () {
if (Time.timeScale == 0) {
pauseMenu.SetActive(false);
countdown.text = "3";
yield WaitForSeconds(1);
// repeat for 2 and 1
Time.timeScale = currentTimeScale;
} else {
Time.timeScale = 0;
pauseMenu.SetActive(true);
}
}
Any suggestions on what the proper way to code this particular countdown is, or how to just fix mine in general would be very helpful.
Thanks!
-Brandon
There are already many implementations of count down timers. For example, see this and this.
The general idea is to use a int field to save the remaining time. Or use yield WaitForSeconds(1) inside a coroutine to save the state.
using UnityEngine;
using System.Collections;
public class WaitForSecondsExample : MonoBehaviour {
void Start() {
StartCoroutine(CountDown());
}
IEnumerator CountDown() {
countdown.text = "3";
yield return new WaitForSeconds(1);
countdown.text = "2";
yield return new WaitForSeconds(1);
countdown.text = "1";
yield return new WaitForSeconds(1);
//time up, now resume the app
}
}
UPDATE
As for how to pause the app, see this and this.
Time.realtimeSinceStartup returns the time since startup, not affected by Time.timeScale.
public static class CoroutineUtilities
{
public static IEnumerator WaitForRealTime(float delay){
while(true){
float pauseEndTime = Time.realtimeSinceStartup + delay;
while (Time.realtimeSinceStartup < pauseEndTime){
yield return 0;
}
break;
}
}
}
Usabilty:
yield return StartCoroutine(CoroutineUtilities.WaitForRealTime(1));
Then we use yield return StartCoroutine(CoroutineUtilities.WaitForRealTime(1)) instead of yield return new WaitForSeconds(1) in the code above:
using UnityEngine;
using System.Collections;
public class WaitForSecondsExample : MonoBehaviour {
void Start() {
StartCoroutine(CountDown());
}
IEnumerator CountDown() {
countdown.text = "3";
yield return StartCoroutine(CoroutineUtilities.WaitForRealTime(1));
countdown.text = "2";
yield return StartCoroutine(CoroutineUtilities.WaitForRealTime(1));
countdown.text = "1";
yield return StartCoroutine(CoroutineUtilities.WaitForRealTime(1));
//time up, now resume the app
}
}
Related
I added following function to my script and it causes crash in unity.
public void AddCurrentFrameToVideo()
{
_addFrameFunctionHasBeenCalled = true;
using (var encoder = new MediaEncoder(encodedFilePath, videoAttr, audioAttr))
using (var audioBuffer = new NativeArray<float>(sampleFramesPerVideoFrame, Allocator.Temp))
{
IEnumerator SetFrame()
{
yield return new WaitForSeconds(0.3f);
encoder.AddFrame(tex);
encoder.AddSamples(audioBuffer);
if (recordingButtonHasBeenPressed)
{
yield return StartCoroutine(SetFrame());
}
else
{
yield return null;
yield break;
}
}
IEnumerator mycoroutine;
mycoroutine = SetFrame();
if (recordingButtonHasBeenPressed)
{
StartCoroutine(mycoroutine);
}
else
{
StopCoroutine(mycoroutine);
}
}
}
I call this function in an if statement in Update function. see:
void Update()
{
_currentsframe = Time.frameCount;
if (recordingButtonHasBeenPressed)
{
if (!videoBasicFileHasBeenCreated)
{
CreateVideoBasicFile();
}
if (!_addFrameFunctionHasBeenCalled)
{
AddCurrentFrameToVideo();
}
}
}
Also I controlled recordingButtonHasBeenPressed variable in another script by a button OnClick(). see:
public void RecordVideo_OnClick()
{
if (videoIsRecording)
{
videoIsRecording = false;
videoRecordButton.image.sprite = videoButtonIsNotRecordingSprite;
_myRecorderSc.recordingButtonHasBeenPressed = false;
_myRecorderSc.videoBasicFileHasBeenCreated = false;
}
else
{
videoRecordButton.image.sprite = videoButtonIsRecordingSprite;
_myRecorderSc.recordingButtonHasBeenPressed = true;
_myRecorderSc.videoBasicFileHasBeenCreated = false;
videoIsRecording = true;
}
}
I don't know why it crashes unity. I don't think it's an infinity loop.
Also I tested a DO-While loop instead of using Croutine. see:
using (var encoder = new MediaEncoder(encodedFilePath, videoAttr, audioAttr))
using (var audioBuffer = new NativeArray<float>(sampleFramesPerVideoFrame, Allocator.Temp))
{
do
{
encoder.AddFrame(tex);
encoder.AddSamples(audioBuffer);
} while (recordingButtonHasBeenPressed);
}
it causes unity crash too.
What should I do? What's wrong with it?
This
IEnumerator SetFrame()
{
yield return new WaitForSeconds(0.3f);
encoder.AddFrame(tex);
encoder.AddSamples(audioBuffer);
if (recordingButtonHasBeenPressed)
{
yield return StartCoroutine(SetFrame());
}
}
is a recursive call where you yield return the same routine again (which internally yield returns the same routine again etc) so it waits until all nested subroutines are finished => So at some point you will get a StackOverflow!
This is definitely a closed never ending while loop
using (var audioBuffer = new NativeArray<float>(sampleFramesPerVideoFrame, Allocator.Temp))
{
do
{
encoder.AddFrame(tex);
encoder.AddSamples(audioBuffer);
} while (recordingButtonHasBeenPressed);
}
within the loop the value of recordingButtonHasBeenPressed will never be changed and Unity/your app immediately freezes forever!
What you would want to do instead would be a Coroutine like
IEnumerator SetFrame()
{
// initially wait once
yield return new WaitForSeconds(0.3f);
// simply continue to execute the routine until the record shall be stopped
while(recordingButtonHasBeenPressed)
{
encoder.AddFrame(tex);
encoder.AddSamples(audioBuffer);
// yield the next frames for 0.3 seconds before checking
// recordingButtonHasBeenPressed again
yield return new WaitForSeconds(0.3f);
}
}
You then wouldn't even need to actively stop it. All you need to do is start it and then in order to interrupt it simply set recordingButtonHasBeenPressed to false.
Do it event driven
Now in general instead of using Update and multiple controller flag bools you immediately seem to reset again once a method gets called here I would rather make the entire code event driven and called once when the button is called. This would prevent having concurrent routines running by accident and make the whole thing way better to read and maintain.
I don't know your full code but it might look something like
public void RecordVideo_OnClick()
{
// invert the toggle flag
videoIsRecording = !videoIsRecording;
// depending on the new flag value chose the sprite
videoRecordButton.image.sprite = videoIsRecording ? videoButtonIsRecordingSprite : videoButtonIsNotRecordingSprite;
if (!videoIsRecording)
{
_myRecorderSc.StopRecording();
}
else
{
_myRecorderSc.StartRecoring();
}
}
and then in the recorder script you would only need
public void StartRecording()
{
if(!recording)
{
StartCoroutine(RecorderRoutine);
}
}
public void StopRecording()
{
recording = false;
}
// flag to interrupt running record
private bool recording;
private IEnumerator RecorderRoutine()
{
// Just in case prevent concurrent routines
if(recording) yield break;
recording = true;
// initialize your file
CreateVideoBasicFile();
// initially wait once
yield return new WaitForSeconds(0.3f);
using (var encoder = new MediaEncoder(encodedFilePath, videoAttr, audioAttr))
using (var audioBuffer = new NativeArray<float>(sampleFramesPerVideoFrame, Allocator.Temp))
{
// simply continue to execute the routine until the record shall be stopped
while(recording)
{
encoder.AddFrame(tex);
encoder.AddSamples(audioBuffer);
// yield the next frames for 0.3 seconds before checking
// recordingButtonHasBeenPressed again
yield return new WaitForSeconds(0.3f);
}
}
recording = false;
}
I'm making my first game and I have one last thing left to do. I have a shop where you can buy a lot of stuff. One of them obstacles don't spawn, but it was OP. So I decided to make a button that would do this effect for 10 seconds and have it cool down 20s before the next use. I tried the known for me methods but in this case they didn't work. I just want the boolean to change to true and after 10 seconds to false and to be cool down 20s before it can be pressed again. Any ideas?
Thanks for any help :)
EDIT:
So this is my code for on click button event:
public static bool isActive;
public Animator anim;
void use()
{
isActive = true;
anim.SetBool("isAngry", true);
}
void stop()
{
isActive = false;
anim.SetBool("isAngry", false);
}
public void clickButton()
{
StartCoroutine("start");
}
IEnumerator start ()
{
while (true)
{
use();
yield return new WaitForSeconds(10f);
stop();
}
}
and this is my code for obstacles:
void Start()
{
screenBounds = Camera.main.ScreenToWorldPoint(new Vector3(Screen.width, Screen.height, Camera.main.transform.position.z));
StartCoroutine(obstaclesWave());
}
private void spawnObstacles()
{
GameObject a = Instantiate(obstaclesPrefab) as GameObject;
a.transform.position = new Vector2(Random.Range(-screenBounds.x, screenBounds.x), screenBounds.y);
}
IEnumerator obstaclesWave()
{
while (true)
{
yield return new WaitForSeconds(respawnTime);
if(usePszczoła.isActive == false)
spawnObstacles();
yield return new WaitForSeconds(respawnTime);
}
}
}
Maybe you can use Coroutine. Simple code here:
private bool isCooldown = false;
public void ActivateSkill()
{
if (!isCooldown)
{
// Activate Skill
StartCoroutine(_ActivateSkillCoroutine());
}
else
{
Debug.Log("The skill is in cooldown!");
}
}
private IEnumerator _ActivateSkillCoroutine()
{
// Sets that this skill is on cooldown.
isCooldown = true;
// activate your skill, and wait for the end of it's effect.
yield return _SkillEffect();
// then wait for it's cooldown.
yield return new WaitForSeconds(20f);
// after 20 seconds, sets that the skill is ready to use.
isCooldown = false;
}
private IEnumerator _SkillEffect()
{
// 10 seconds for your skill. Try implement it yourself!
yield return new WaitForSeconds(10f);
}
This code is mere example, so you will be able to find your own way to do this.
More references : UnityEngine.Coroutine
Edit: As I see in your code, It seems like your code is, when the button is clicked, it stops spawning and waits for 10 seconds, and enables spawning, and immediatly stops spawning for 10 seconds, which loops forever. Try this:
private bool isCooldown = false
IEnumerator start ()
{
if (isCooldown) yield break;
isCooldown = true;
use();
yield return new WaitForSeconds(10f);
stop();
yield return new WaitForSeconds(20f);
isCooldown = false;
}
I've been working on a game as a project and I've gotten round to introducing invincibility frames when the player takes a heart of damage. In this case I want it so that the player model flashes roughly once every 0.1 seconds and to have the invincibility last for 2 seconds.
I've written this code and I can't figure out why it isn't working. By the way using this code when the player takes damage they cannot take damage afterwards so something is really messed up (it isn't just the visual invincibility being an issue).
(Thank you)
private void loseHealth()
{
if (invinTimerCounter == 0)
{
curHealth -= 1;
invinTimerCounter = invinTimer;
invincibilityBlink();
}
}
private void invincibilityBlink()
{
for (int i = 0; i < 10; i++)
{
Invoke("spriteDisable", 1);
Invoke("spriteEnable", 1);
}
}
private void spriteEnable()
{
this.spriteRenderer.enabled = true;
}
private void spriteDisable()
{
this.spriteRenderer.enabled = false;
}
private void Update()
{
if (invinTimerCounter < 0)
{
invinTimerCounter -= Time.deltaTime;
}
}
In addition to jmalenfant's comment, I'd like to rewrite your invincibilityBlink() method to use a coroutine:
private IEnumerator invincibilityBlink()
{
for (int i = 0; i < 10; i++)
{
spriteDisable();
yield return new WaitForSeconds(0.1f);
spriteEnable();
yield return new WaitForSeconds(0.1f);
}
invincible = false;
}
Then, here:
if (!invincible)
{
curHealth -= 1;
invincible = true;
StartCoroutine(invincibilityBlink());
}
Oh, and we'll change that messy float to a boolean and let the coroutine handle it too, so if you decide to change the invincibility time, you only need to change things in one place.
I'm trying out some code for an idea I had recently. But right now I'm stuck on trying to find a good way to add a delay. I've been trying to use coroutine, and I get a delay, but the method called after that gets called way too many times (I only want it to be called once)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using System.IO;
using System;
using Random = UnityEngine.Random;
public class YourHealth : MonoBehaviour
{
bool yourTurn = true;
public Button button1;
public Button button2;
public Text YH;
public Text EH;
private int yourHealth = 100;
private int enemyHealth = 100;
// Use this for initialization
void Start()
{
button1.onClick.AddListener(Heal20);
button2.onClick.AddListener(Damage40);
YH.text = Convert.ToString(yourHealth);
EH.text = Convert.ToString(enemyHealth);
}
public void Heal20()
{
yourHealth += 20;
yourTurn = false;
}
public void Damage40()
{
enemyHealth -= 40;
yourHealth -= 5;
yourTurn = false;
}
public void Update()
{
YH.text = Convert.ToString(yourHealth);
EH.text = Convert.ToString(enemyHealth);
if (yourTurn == false)
{
button1.interactable = false;
StartCoroutine(Wait(2));
}
else
{
button1.interactable = true;
}
}
public void EnemyTurn()
{
int roll = Random.Range(1 , 7);
switch (roll)
{
case 1:
yourHealth -= 10;
break;
case 2:
enemyHealth -= 10;
break;
case 3:
yourHealth -= 30;
break;
case 4:
yourHealth += 5;
break;
case 5:
break;
case 6:
enemyHealth += 10;
break;
}
yourTurn = true;
}
IEnumerator Wait(float time)
{
yield return new WaitForSecondsRealtime(time);
EnemyTurn();
}
}
I want EnemyTurn to be called only once after the delay. But instead it runs a bunch of times. I hope I can get a solution to this quickly because it has just brought a halt to my project and I can't figure it out myself.
That's because you're creating a Wait Coroutine on every Update call between the player's end and the enemy's turn. You need a way to only create it once per player turn end.
One way is adding a flag that you set when you create the Wait Coroutine and then unset it when the enemy's turn is done:
// If this is true, we are already waiting
// and don't want more Wait coroutines (yet)
private bool waiting;
void Start()
{
button1.onClick.AddListener(Heal20);
button2.onClick.AddListener(Damage40);
YH.text = Convert.ToString(yourHealth);
EH.text = Convert.ToString(enemyHealth);
waiting = false;
}
// ...
if (yourTurn == false)
{
if (!waiting)
{
// we only want to do this stuff the first frame we start waiting
button1.interactable = false;
waiting = true;
StartCoroutine(Wait(2));
}
}
else
{
button1.interactable = true;
}
// ...
IEnumerator Wait(float time)
{
yield return new WaitForSecondsRealtime(time);
EnemyTurn();
waiting = false;
}
i've got a player and an enemy. When i rightclick the enemy his HP goes down and a hitcounter goes up. I want to make it like when you hit the enemy the text label becomes visible and when you stop attacking it stays visible for a couple more seconds and then hides and sets the hitcounter back to 0.
This is what i have at the moment.
public Text GUIHit;
public int HitCounter = 0;
void OnMouseOver()
{
if (Input.GetMouseButtonDown(1))
{
HitCounter++;
StartCoroutine(ShowHitCounter(HitCounter.ToString(), 2));
}
}
IEnumerator ShowHitCounter(string message, float delay)
{
GUIHit.text = message;
GUIHit.enabled = true;
yield return new WaitForSeconds(delay);
HitCounter = 0;
GUIHit.enabled = false;
}
What happens is that it works for 2 seconds, but even when im still attacking it goes invisible and the hit counter goes back to 0, the coroutine does not get reset back to a starting point.
Lets analyze your code:
void OnMouseOver()
{
if (Input.GetMouseButtonDown(1)) //you get passed that if when you hit first time
{
HitCounter++;
StartCoroutine(ShowHitCounter(HitCounter.ToString(), 2)); //you call your label with delay of 2 sec
}
}
IEnumerator ShowHitCounter(string message, float delay)
{
GUIHit.text = message;
GUIHit.enabled = true;
yield return new WaitForSeconds(delay); // still on your first hit you get to here and wait 2 seconds
HitCounter = 0; //after 2 seconds you reset hitcounter and disable label
GUIHit.enabled = false;
}
To fix it you need to know when you stopped hitting, and then reset hitcounter and disable label.
I would change showhitcounter to below:
IEnumerator ShowHitCounter(string message)
{
GUIHit.text = message;
GUIHit.enabled = true;
}
void ClearLabel()
{
HitCounter = 0;
GUIHit.enabled = false;
}
}
I made clearLabel to have separate method that clears label. Your logic will have to be in different places and call this method.
One place would onmouseleave event.
Other place would be in your onmouseover and added a property
public static DateTime TimeLeft { get; set; }
void OnMouseOver()
{
TimeSpan span = DateTime.Now - TimeLeft;
int ms = (int)span.TotalMilliseconds;
if (ms > 2000)
{
ClearLabel();
}
if (Input.GetMouseButtonDown(1))
{
HitCounter++;
StartCoroutine(ShowHitCounter(HitCounter.ToString(), 2));
}
}
Also you need to initialize TimeLeft somewhere before
Just finished with my solution and realized there is an answer already. Can't discard it. Just putting it as a solution with no memory allocation.
You don't need to start Coroutine each time right mouse is clicked like you did in the code in your question. I say this because of constant memory allocation when StartCoroutine() is called after each mouse click. Timer in the code below is based on frame-rate but can be easily changed to real-time by using DateTime.Now. You can also put the code in a while loop in a Coroutine then call it once from Start function.
public Text GUIHit;
public int HitCounter = 0;
bool firstRun = true;
float waitTimeBeforeDisabling = 2f;
float timer = 0;
void Update()
{
//Check when Button is Pressed
if (Input.GetMouseButtonDown(1))
{
//Reset Timer each time there is a right click
timer = 0;
if (!firstRun)
{
firstRun = true;
GUIHit.enabled = true;
}
HitCounter++;
GUIHit.text = HitCounter.ToString();
}
//Button is not pressed
else
{
//Increement timer if Button is not pressed and timer < waitTimeBeforeDisabling
if (timer < waitTimeBeforeDisabling)
{
timer += Time.deltaTime;
}
//Timer has reached value to Disable Text
else
{
if (firstRun)
{
firstRun = false;
GUIHit.text = HitCounter.ToString();
HitCounter = 0;
GUIHit.enabled = false;
}
}
}
}
Awh, okay then, here's another concept, just for the sake of it :)Did not test it and such so handle with care, but the thing is, starting a coroutine, etc looks too much (and too expensive) for me for something as little as what you want.
private float holdOutTime = 2.0f;
private float lastHitTime = 0.0f;
void OnMouseOver() {
if (Input.GetMouseButtonDown(1)) { IncHitAndShowUI() } //compacted
}
private void Update() {
if (GUIHit.enabled) { TestAndDisableHitUI(); } //compacted
}
#region priv/helper methods
//would force it inline if it was possible in Unity :)
private void IncHitAndShowUI() {
HitCounter++;
lastHitTime = Time.time;
GUIHit.text = HitCounter.ToString();
GUIHit.enabled = true;
}
//same here :)
private void TestAndDisableHitUI() {
if (lastHitTime + holdOutTime >= Time.time) {
GUIHit.enabled = false;
}
}
#endregion