Unity 2D: How to start timer when player loses all of lives - c#

I want to start my timer countdown when my player's current health reaches 0. so far my timer is connected to my game over panel that slides down when the player loses all of his hearts (lives). When I play the application (game) the timer starts automatically and stops when my the game over panel slides down. Which is not what I want. What I want to do is make the timer start when the player loses all hearts (lives) and the game over panel slides down.
This is my player's health script:
public int curHealth;
public int maxHealth = 3;
Vector3 startPosition;
public PlayerHealth playerhealthRef;
float counter;
public Animator anima; // drag the panel in here again
private UI_ManagerScripts UIM;
private PandaScript ps;
DateTime currentDate;
DateTime oldDate;
void Start ()
{
curHealth = maxHealth;
startPosition = transform.position;
ps = GameObject.FindGameObjectWithTag("PandaScript").GetComponent <PandaScript> ();
currentDate = System.DateTime.Now;
}
void Update ()
{
if (curHealth > maxHealth) {
curHealth = maxHealth;
}
if (curHealth <= 0) {
Die ();
}
}
void Awake()
{
UIM = GameObject.Find ("UIManager").GetComponent<UI_ManagerScripts> ();
}
void Die(){
if (PlayerPrefs.HasKey ("Highscore")) {
if (PlayerPrefs.GetInt ("Highscore") < ps.Score) {
PlayerPrefs.SetInt ("Highscore", ps.Score);
}
} else
{
PlayerPrefs.SetInt ("Highscore", ps.Score);
}
UIM.EnableBoolAnimator(anima);
PlayerPrefs.SetInt("RemainingLives", curHealth);
print("Remanining Lives: " + curHealth);
PlayerPrefs.Save();
}
void OnApplicationQuit()
{
PlayerPrefs.SetString("sysString", System.DateTime.Now.ToBinary().ToString());
print("Saving this date to prefs: " + System.DateTime.Now);
}
void LoadSaveData()
{
PlayerPrefs.GetInt ("RemainingLives",0);
OnApplicationQuit ();
}
public void Damage(int dmg)
{
curHealth -= dmg;
Reset();
}
void Reset ()
{
transform.position = startPosition;
GotoMouse.target = startPosition;
}
}
And this is my timer script:
public Text timer;
int minutes = 1;
int seconds = 0;
float miliseconds = 0;
public int curHealth;
public int maxHealth = 3;
[Range(1, 59)]
public int defaultStartMinutes = 1;
public bool allowTimerRestart = false;
public bool useElapsedTime = true;
private int savedSeconds;
private bool resetTimer = false;
private DateTime centuryBegin = new DateTime(2001, 1, 1);
void Start ()
{
curHealth = maxHealth;
}
void Awake ()
{
minutes = defaultStartMinutes;
if (PlayerPrefs.HasKey("TimeOnExit"))
{
miliseconds = PlayerPrefs.GetFloat("TimeOnExit");
savedSeconds = (int)miliseconds;
if (useElapsedTime && PlayerPrefs.HasKey("CurrentTime"))
{
int elapsedTicks = (int)(DateTime.Now.Ticks / 10000000);
int ct = PlayerPrefs.GetInt("CurrentTime", elapsedTicks);
PlayerPrefs.DeleteKey("CurrentTime");
elapsedTicks -= ct;
if (elapsedTicks < miliseconds)
{
miliseconds -= elapsedTicks;
}
else
{
miliseconds = 0;
}
}
minutes = (int)miliseconds / 60;
miliseconds -= (minutes * 60);
seconds = (int)miliseconds;
miliseconds -= seconds;
PlayerPrefs.DeleteKey("TimeOnExit");
}
savedSeconds = 0;
}
public void Update()
{
// count down in seconds
miliseconds += Time.deltaTime;
if (resetTimer)
{
ResetTime();
}
if (miliseconds >= 1.0f)
{
miliseconds -= 1.0f;
if ((seconds > 0) || (minutes > 0))
{
seconds--;
if (seconds < 0)
{
seconds = 59;
minutes--;
}
}
else
{
resetTimer = allowTimerRestart;
}
}
if (seconds != savedSeconds)
{
// Show current time
timer.text = string.Format("{0}:{1:D2}", minutes, seconds);
savedSeconds = seconds;
}
}
void ResetTime()
{
minutes = defaultStartMinutes;
seconds = 0;
savedSeconds = 0;
miliseconds = 1.0f - Time.deltaTime;
resetTimer = false;
}
private void OnApplicationQuit()
{
int numSeconds = ((minutes * 60) + seconds);
if (numSeconds > 0)
{
miliseconds += numSeconds;
PlayerPrefs.SetFloat("TimeOnExit", miliseconds);
if (useElapsedTime)
{
int elapsedTicks = (int)(DateTime.Now.Ticks / 10000000);
PlayerPrefs.SetInt("CurrentTime", elapsedTicks);
}
}
}
}

Use this free and very useful asset: https://www.assetstore.unity3d.com/en/#!/content/17276
Then write something like this:
Observable.EveryUpdate()
.Where(_ => currentHealth <= 0)
.Subscribe(_ =>
{
//Do something...
//Ex. Die();
});
All stuff in .Subscribe will do when condition in where return true. Use this in Start() not Update().
Ohhh... and if you want some simple timer, write for example:
Observable.Timer(TimeSpan.FromMilliseconds(1000))
.Subscribe(_ =>
{
});

You have your code in the Awake() and Update() methods but I don't see anything that is telling it to wait for when the player loses. You can either set a flag or enable the timer script when you want the timer to begin.
It also seems you're using PlayerPrefs for some things they aren't necessarily needed for. Unless you want to be keeping track of the remaining lives after the game is exited and restarted (which doesn't even make much sense), you'd be better off with an instance, or even class, variable.

Related

Heal reset Unity C#

I implemented healing into my Unity project and have a timer for when the player can heal and I want the timer to reset every time the player takes damage but I can't seem to get it to work.. here is my code:
private void HealthRegen()
{
timer += Time.deltaTime;
if (timer > timeToHeal)
{
StartCoroutine(HealthRegenCo());
}
if (damaged == true)
{
timer = 0f;
}
}
private void SetHealth(float value)
{
currentHealth = Mathf.Clamp(value, 0f, 100f);
healthbar.value = currentHealth;
}
private IEnumerator HealthRegenCo()
{
while (enabled)
{
yield return new WaitForSeconds(0.1f);
SetHealth(currentHealth + healAmount);
Save();
}
}
I see what happened here, let me descriptively explain what your code does.
I assume that HealthReign() is called in Update(), and the timer is increasing. When the timer goes above timeToHeal, the coroutine is started. If damage is set to true, timer is set to 0. When the timer goes above timeToHeal, the code inside else if block doesn't get called at all due to it being an else if. Code inside else if is only read when the condition in the if statement is false.
Fix, don't use else if, just use another if
private void HealthRegen()
{
timer += Time.deltaTime;
if (timer > timeToHeal)
{
StartCoroutine(HealthRegenCo());
}
if (damaged == true)
{
timer = 0f;
}
}
Time.deltaTime <-- this is working by Update() , so you can count time by your coroutine. like this.
private void HealthRegen()
{
StartCoroutine(HealthRegenCo());
}
private void SetHealth(float value)
{
currentHealth = Mathf.Clamp(value, 0f, 100f);
healthbar.value = currentHealth;
}
private IEnumerator HealthRegenCo()
{
while (enabled)
{
yield return new WaitForSeconds(0.1f);
timer += 0.1f;
SetHealth(currentHealth + healAmount);
Save();
if(timer > timeToHeal)
{
yield break;
}
}
}
//------------------------ I saw your question yestday , so I think like this is simply , and you can use "HealthOpen" to open or close health.
float time = 0;
public bool HealthOpen = false;
private void Update()
{
if(HealthOpen)
{
time += Time.detlaTime;
if(time > 0.1f)
{
time = 0;
setHeal(currentheal + heal);
}
if(currentheal >= 100)
{
HealthOpen = false;
}
}
}

Why I'm getting MissingReferenceException exception when starting the coroutine over and over again?

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GenerateWalls : MonoBehaviour
{
public GameObject gameObjectToRaise;
public float duration;
public Vector3 raiseAmount;
public bool go = false;
public Color[] colors = new Color[4];
public bool randomColors = false;
private GameObject objtoraise;
private GameObject[] walls;
private bool scaleOver = false;
private void Start()
{
Init();
ColorWalls();
// The z Axis must be minimum 1 or any value above 0 could be also 0.1f
// but it's better to keep it minimum as 1 by default.
if (raiseAmount.z < 1)
{
raiseAmount.z = 1f;
}
if (go)
{
StartCoroutine(ScaleOverSeconds(objtoraise, new Vector3(raiseAmount.x, raiseAmount.y,
raiseAmount.z), duration));
}
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.R))
{
//if (scaleOver)
//{
if (objtoraise != null)
{
if (raiseAmount.z < 1)
{
raiseAmount.z = 1f;
}
Destroy(objtoraise);
Init();
ColorWalls();
StartCoroutine(ScaleOverSeconds(objtoraise, new Vector3(raiseAmount.x, raiseAmount.y,
raiseAmount.z), duration));
scaleOver = false;
//}
}
}
}
private void Init()
{
objtoraise = Instantiate(gameObjectToRaise);
objtoraise.name = "Walls";
walls = GameObject.FindGameObjectsWithTag("Wall");
}
public IEnumerator ScaleOverSeconds(GameObject objectToScale, Vector3 scaleTo, float seconds)
{
float elapsedTime = 0;
Vector3 startingScale = objectToScale.transform.localScale;
while (elapsedTime < seconds)
{
objectToScale.transform.localScale = Vector3.Lerp(startingScale, scaleTo, (elapsedTime / seconds));
elapsedTime += Time.deltaTime;
yield return new WaitForEndOfFrame();
}
objectToScale.transform.localScale = scaleTo;
scaleOver = true;
}
private void ColorWalls()
{
for (int i = 0; i < walls.Length; i++)
{
if (randomColors)
{
walls[i].transform.GetChild(0).GetComponent<Renderer>().material.color
= GetRandomColour32();
}
else
{
walls[i].transform.GetChild(0).GetComponent<Renderer>().material.color = colors[i];
}
}
}
private Color32 GetRandomColour32()
{
//using Color32
return new Color32(
(byte)UnityEngine.Random.Range(0, 255), //Red
(byte)UnityEngine.Random.Range(0, 255), //Green
(byte)UnityEngine.Random.Range(0, 255), //Blue
255 //Alpha (transparency)
);
}
}
Inside the Update() when I press the R key it's destroying the Instantiated object and then Instantiate is again and start the coroutine again. The problem is when I press on the R key many times in a row after two times I'm getting MissingReferenceException exception in the editor :
MissingReferenceException: The object of type 'GameObject' has been destroyed but you are still trying to access it.
Your script should either check if it is null or you should not destroy the object.
GenerateWalls+d__12.MoveNext () (at Assets/Scripts/GenerateWalls.cs:81)
Line 81 is :
objectToScale.transform.localScale = Vector3.Lerp(startingScale, scaleTo, (elapsedTime / seconds));
The goal is to be able to generate the walls each time over again when pressing R it should stop the current coroutine and start over.
Maybe the problem is that it's in the middle of the coroutine and because the old coroutine didn't stop yet then the object is missing in the middle because I destroy it?
How then I should do it to be able to press R over and over again and it will start the coroutine over and over? Not to start multiple coroutines but to start each time the coroutine over again.
The solution is to add : StopCoroutine In the Update()
This is not the solution. I thought stopping the coroutine will stop also the while loop inside but it didn't. It seems that checking for null inside the while loop solved the problem :
public IEnumerator ScaleOverSeconds(GameObject objectToScale, Vector3 scaleTo, float seconds)
{
if (objectToScale != null)
{
float elapsedTime = 0;
Vector3 startingScale = objectToScale.transform.localScale;
while (elapsedTime < seconds)
{
if (objectToScale == null)
{
yield return null;
}
else
{
objectToScale.transform.localScale = Vector3.Lerp(startingScale, scaleTo, (elapsedTime / seconds));
elapsedTime += Time.deltaTime;
yield return new WaitForEndOfFrame();
}
}
objectToScale.transform.localScale = scaleTo;
scaleOver = true;
}
}

Where should I reset the timeElapsed?

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class DetectCollision : MonoBehaviour
{
public Transform player;
public Transform target;
public bool turnOnOffPlayerAnimator = false;
float timeElapsed = 0;
float lerpDuration = 3;
float startValue = 1;
float endValue = 0;
float valueToLerp = 0;
private Animator playerAnimator;
private bool entered = false;
private bool resetElapsed = false;
// Start is called before the first frame update
void Start()
{
playerAnimator = player.GetComponent<Animator>();
if (turnOnOffPlayerAnimator)
playerAnimator.enabled = false;
}
// Update is called once per frame
void Update()
{
if (IsFacing(target))
{
if (entered)
{
if (timeElapsed < lerpDuration)
{
valueToLerp = Mathf.Lerp(startValue, endValue, timeElapsed / lerpDuration);
playerAnimator.SetFloat("Forward", valueToLerp);
timeElapsed += Time.deltaTime;
}
playerAnimator.SetFloat("Forward", valueToLerp);
}
}
else
{
if (valueToLerp < 0.9f)
{
if(resetElapsed == false)
{
timeElapsed = 0;
resetElapsed = true;
}
if (timeElapsed < lerpDuration)
{
valueToLerp = Mathf.Lerp(endValue, startValue, timeElapsed / lerpDuration);
playerAnimator.SetFloat("Forward", valueToLerp);
timeElapsed += Time.deltaTime;
}
playerAnimator.SetFloat("Forward", valueToLerp);
}
}
if(turnOnOffPlayerAnimator)
{
playerAnimator.enabled = false;
}
else
{
playerAnimator.enabled = true;
}
}
private void OnTriggerEnter(Collider other)
{
entered = true;
Debug.Log("Entered !");
}
private void OnTriggerExit(Collider other)
{
entered = false;
Debug.Log("Exited !");
}
private bool IsFacing(Transform target)
{
Vector3 forward = player.TransformDirection(Vector3.forward);
Vector3 toTarget = target.position - player.position;
return Vector3.Dot(forward, toTarget) > 0;
}
}
I reset it once back to value 0 in this part :
if(resetElapsed == false)
{
timeElapsed = 0;
resetElapsed = true;
}
but it also working only once.
I need somehow to reset the value to 0 each time the player is switching direction facing or not facing the target but not sure where and how to do it in the Update.
if not resetting it to 0 when the player will not facing the target or will face it after not facing again the value of timeElapsed is bigger then the lerpDuration value.
You could store the result of your IsFacing() method in a field and check in the next update run if it's has still the same value.
Simple example:
private bool prevFacing = false;
void Update() {
var currFacing = IsFacing(target);
if(currFacing != prevFacing) {
// here you switched from facing to not facing or vise verca.
}
prevFacing = currFacing;
}

Stopwatch in c# (unity)

i'm making a door (similiar to the Doom 64's ones) and i have this code:
public class aperturaPorta : MonoBehaviour
{
public Transform playerCheck;
public Vector3 halfSize = new Vector3 (3f, 3f, 0.5f);
public LayerMask playerLayer;
bool playerEntering = false;
public BoxCollider collider;
public MeshRenderer renderer;
bool aprendo = false;
bool chiudendo = false;
public float openSpeed;
int counter = 1;
public int tick = 99;
// Update is called once per frame
void Update()
{
playerEntering = Physics.CheckBox(playerCheck.position, halfSize, Quaternion.identity, playerLayer, QueryTriggerInteraction.UseGlobal);
if (playerEntering && Input.GetButtonDown("e")) {
aprendo = true;
chiudendo = false;
}
if (counter == 100) {
chiudendo = true;
}
if (aprendo) {
transform.position += new Vector3 (0f, openSpeed, 0f);
counter += 1;
if (counter > tick) {
aprendo = false;
chiudendo = true;
}
}
if (chiudendo) {
transform.position -= new Vector3 (0f, openSpeed, 0f);
counter -= 1;
if (counter < 1) {
chiudendo = false;
}
}
}
}
This work but the door start closing when it finishes openening but it's too fast so i want to implement a two or three seconds stopwatch so that when it finishes the door start closing, how can i do it? thank you
ps: excuse me but i'm a newbie in unity
If I understand correctly you want a simple delay when the door is open before it closes? Keeping the same code structure you can add an other counter for that.
public float openSpeed;
int counter = 1;
public int tick = 99;
public int delayTicks = 100;
private int delayCounter = 0;
// Update is called once per frame
void Update()
{
playerEntering = Physics.CheckBox(playerCheck.position, halfSize, Quaternion.identity, playerLayer, QueryTriggerInteraction.UseGlobal);
if (playerEntering && Input.GetKeyDown(KeyCode.P))
{
aprendo = true;
chiudendo = false;
}
if (counter == 100)
{
chiudendo = true;
}
if (aprendo)
{
transform.position += new Vector3(0f, openSpeed, 0f);
counter += 1;
if (counter > tick)
{
delayCounter = delayTicks;
aprendo = false;
}
}
if (delayCounter > 0)
{
delayCounter--;
if (delayCounter <= 0)
{
chiudendo = true;
}
}
else if (chiudendo)
{
transform.position -= new Vector3(0f, openSpeed, 0f);
counter -= 1;
if (counter < 1)
{
chiudendo = false;
}
}
}

Create a Timer like Time.time but with possibility to reset it to 0

can i make a timer or reset somehow Time.time ? i tried making this
float Timer(float time)
{
if (Time.time > nextTime)
{
return 0.01f;
nextTime = Time.time + 0.01f;
}
else
{
return 0;
}
}
but it seems it not work properly, time don't count same as Time.time
You can create timer like this
public float timeLeft=5f;
void FixedUpdate()
{
timeLeft -= Time.deltaTime;
if(timeLeft < 0)
{
DoSomething();
timeLeft=5f; //If you want to reset timer
}
}
public class Timer : MonoBehaviour
{
private static float _lastTime = Time.realtimeSinceStartup;
[SerializeField, Tooltip("Time in second between timer ding dong."), Range(0f, 3600f)]
private float _interval = 5f;
public UnityEvent OnTickTack;
private void Update()
{
if (Time.realtimeSinceStartup - _lastTime >= _interval)
{
_lastTime = Time.realtimeSinceStartup;
OnTickTack.Invoke();
}
}
}
Or you could always use coroutine "yield return new WaitForSeconds(5f);"

Categories

Resources