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;
}
}
}
Related
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;
}
}
In my code, I am calling a Slow motion function when the player enter to a Trigger, but I want this function to stop after 1 second, I tried the code below but the slow motion didn't work. Do you have any idea?
Player Script:
public Rigidbody Ball;
public float Speed = 50f;
public TimeManager timeManager;
bool SlowOn;
bool ClickDone = false;
// Use this for initialization
void Start () {
StartCoroutine(SlowOff());
}
// Update is called once per frame
void FixedUpdate () {
if (!ClickDone){
if (Input.GetMouseButton (0)) {
ClickDone = true;
Ball.velocity = transform.forward * Speed;
}
}
}
private void OnTriggerEnter (Collider other) {
if(other.gameObject.CompareTag ("SlowMotionArea")) {
if (SlowOn) {
timeManager.DoSlowMotion();
}
}
}
IEnumerator SlowOff () {
yield return new WaitForSeconds(2.0f);
SlowOn = false;
}
TimeManager Script:
public float SlowDownFactor = 0.05f;
public float SlowdownLength = 2f;
public void DoSlowMotion () {
Time.timeScale = SlowDownFactor;
Time.fixedDeltaTime = 0.02f * Time.timeScale ;
}
There are a few problems with your code
You never set SlowOn to true so your line
timeManager.DoSlowMotion();
is never executed. Somewhere in your code you should call
SlowOn = true;
I'm guessing but it seems that you wanted to use that bool to prevent multiple calls of timeManager.DoSlowMotion()? In this case it should rather be something like
if(!SlowOn)
{
timeManager.DoSlowMotion();
SlowOn = true;
}
Why do you call StartCoroutine(SlowOff()); in your Start() method?
I guess you should rather remove that line and place it after
timeManager.DoSlowMotion();
StartCoroutine(SlowOff());
so the Coroutine is started everytime a SlowMotion starts.
It is not enough for the SlowMotion to stop to just set your SlowOn = false.
In your TimeManager you should rather store the original TimeScale and reset them in a second method:
public float SlowDownFactor = 0.05f;
public float SlowdownLength = 2f;
private float originalTimeScale;
private float originalFixedDeltaTime;
public void DoSlowMotion () {
// before changing store current values
originalTimeScale = Time.timeScale;
originalFixedDeltaTime = Time.fixedDeltaTime;
Time.timeScale = SlowDownFactor;
Time.fixedDeltaTime = 0.02f * Time.timeScale ;
}
public void ResetTimeScales()
{
Time.timeScale = originalTimeScale;
Time.fixedDeltaTime = originalFixedDeltaTime;
}
and than you also have to call that ResetTimeScales method from the player
IEnumerator SlowOff () {
yield return new WaitForSeconds(2.0f);
timeManager.ResetTimeScales();
SlowOn = false;
}
I guess you know that
yield return new WaitForSeconds(2.0f);
will also be affected by the changed Timescale so your SlowMotion currently would be longer than the expected 2 seconds namely 2 / SlowDownFactor = 40!
You could avoid this by using the Time.unscaledDeltaTime as a countdown like
private IEnumerator SlowOff()
{
// since your title actually claims you want to reset after 1 second
float timer = 1;
while (timer > 0)
{
timer -= Time.unscaledDeltaTime;
yield return null;
}
timeManager.ResetTimeScales();
SlowDown = false;
}
I am a bit of a noob to programming and I am trying to make a GameObject , deactivate and reactivate over a set amount of seconds.For example I want my star to slowly blink before it goes away, to create a cool looking effect. If there is a better way of using this method done with out using SetActive(false) and what not , please feel free to give me your method - This is my code , Sorry if its messy i gotta get better at this but i will in due time
Thanks guys
//Timers
public float ScoreTimer;
public float[] TimeMarkStamp;
//Scoring
public int totalCollStars;
[Space]
public int maxStars = 5;
public GameObject[] StarImages;
// Use this for initialization
void Start()
{
}
// Update is called once per frame
void Update()
{
ScoreTimer += Time.deltaTime;
if (ScoreTimer <= TimeMarkStamp[0])
{
Debug.Log("It works");
StarImages[0].SetActive(false);
}
else if (ScoreTimer <= TimeMarkStamp[1])
{
Debug.Log("It workds" + TimeMarkStamp[1]);
StarImages[1].SetActive(false);
}
else if (ScoreTimer <= TimeMarkStamp[2])
{
Debug.Log("It works" + TimeMarkStamp[2]);
StarImages[2].SetActive(false);
}
else if (ScoreTimer <= TimeMarkStamp[3])
{
//This is not working
InvokeRepeating("flickerEffect", 3f, 1f);
}
}
void flickerEffect()
{
bool flickCheck = false;
if (flickCheck == false)
{
StarImages[3].SetActive(true);
flickCheck = true;
}
else if (flickCheck == true)
{
StarImages[3].SetActive(false);
flickCheck = false;
}
}
}
If there is a better way of using this method done with out using
SetActive(false) and what not
Yes, there is a better way, other than using the SetActive function. You should change the alpha color of the GameObject from 0 to 1 back and forth. After that you can then disable the GameObject with SetActive. This saves how much garbage would have been generated when repeatedly calling the SetActive function.
If this is a 3D GameObject, change the Rendering Mode from Opaque(default) to Fade or Transparent.
A simple function that can do this:
void blink(GameObject obj, float blinkSpeed, float duration)
{
StartCoroutine(_blinkCOR(obj, blinkSpeed, duration));
}
IEnumerator _blinkCOR(GameObject obj, float blinkSpeed, float duration)
{
obj.SetActive(true);
Color defualtColor = obj.GetComponent<MeshRenderer>().material.color;
float counter = 0;
float innerCounter = 0;
bool visible = false;
while (counter < duration)
{
counter += Time.deltaTime;
innerCounter += Time.deltaTime;
//Toggle and reset if innerCounter > blinkSpeed
if (innerCounter > blinkSpeed)
{
visible = !visible;
innerCounter = 0f;
}
if (visible)
{
//Show
show(obj);
}
else
{
//Hide
hide(obj);
}
//Wait for a frame
yield return null;
}
//Done Blinking, Restore default color then Disable the GameObject
obj.GetComponent<MeshRenderer>().material.color = defualtColor;
obj.SetActive(false);
}
void show(GameObject obj)
{
Color currentColor = obj.GetComponent<MeshRenderer>().material.color;
currentColor.a = 1;
obj.GetComponent<MeshRenderer>().material.color = currentColor;
}
void hide(GameObject obj)
{
Color currentColor = obj.GetComponent<MeshRenderer>().material.color;
currentColor.a = 0;
obj.GetComponent<MeshRenderer>().material.color = currentColor;
}
Usage:
void Start()
{
blink(gameObject, 0.2f, 5f);
}
If this is a SpriteRender, you have to replace all the obj.GetComponent<MeshRenderer>().material.color code with obj.GetComponent<SpriteRenderer>().color.
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.
So I'm making a top-down tank shooter game and I want to make a better reloading system than it was before. So I came to the idea that I need some king of progress bar. I knew how to make it so I started doing it. The problem is that it doesn't work properly. As I show in the .gif above, the progress bar don't go down when you shoot second time. Because I'm new to unity, I still don't know everything very good. So I came here, maybe someone could help.
EDIT:
I just found another problem and maybe an answer why I have this problem. The second time my script tries to reload, my "needTimer" bool is false, thus the progress bar is not going down when it's false. The new question would be why it becomes false instead of true?
My reloading script:
using UnityEngine;
using UnityEngine.UI;
using System.Collections;
public class Reload : MonoBehaviour {
public float ammo;
public Image progress;
public bool alreadyReloading;
public bool needTimer;
void Start () {
alreadyReloading = false;
}
IEnumerator needtimertime(){
yield return new WaitForSeconds (6.5f);
needTimer = false;
}
IEnumerator uztaisyt(){
Debug.Log ("REEELOUUUDING!");
yield return new WaitForSeconds(6.5f);
ammo += 1;
alreadyReloading = false;
}
void Update () {
if (needTimer == true) {
timer ("");
}
if (ammo < 5) {
if(alreadyReloading == false){
needTimer = true;
StartCoroutine(uztaisyt());
alreadyReloading = true;
}
}
if (progress.fillAmount <= 0) {
progress.fillAmount = 1.0f;
}
}
void timer(string tipas){
progress.fillAmount -= Time.deltaTime / 6.5f;
StartCoroutine (needtimertime ());
}
}
When you start the uztaisyt() as a coroutine after shooting the first time (in the animation), needTimer is set to true and in the next Update() call, the needtimertime() coroutine will start. Since both the uztaisyt() and needtimertime() have identical 6.5 second waits, they will not both return on the same frame update because needtimertime() will always be started in the next frame after uztaisyt(). And, since there is no guarantee of the time interval between Update() calls, (see Time and Frame Managment), this interval may be more than expected and needtimertime() could return false in the frame right after uztaisyt() is called after firing the second time.
To ensure that the needtimertime() is always started (if not already running) immediately following a call for uztaisyt() (and called within the same frame update), you could try the following update to Reload script, (basically changes to the Update() method and when/how _isTimerRunning is set).
public class Reload : MonoBehaviour {
public float ammo;
public Image progress;
private bool _alreadyReloading;
private bool _isTimerRunning;
void Start () {
_alreadyReloading = false;
_isTimerRunning = false;
}
IEnumerator needtimertime(){
yield return new WaitForSeconds (6.5f);
_needTimer = false;
}
IEnumerator uztaisyt(){
Debug.Log ("REEELOUUUDING!");
yield return new WaitForSeconds(6.5f);
ammo += 1;
_alreadyReloading = false;
}
void Update () {
if (ammo < 5) {
if(_alreadyReloading == false){
StartCoroutine(uztaisyt());
_alreadyReloading = true;
//this will check for and start the progress bar timer in the same udate call
//so both coroutines finish on the same frame update
if(!_isTimerRunning){
_isTimerRunning = true;
timer ("");
}
}
}
if (progress.fillAmount <= 0) {
progress.fillAmount = 1.0f;
}
}
void timer(string tipas){
progress.fillAmount -= Time.deltaTime / 6.5f;
StartCoroutine (needtimertime ());
}
}
I found my problem and fixed it.
The problem was that needTimer was becoming false. So I found where and removed it.
my new code:
using UnityEngine;
using UnityEngine.UI;
using System.Collections;
public class Reload : MonoBehaviour {
public float ammo;
public Image progress;
public bool alreadyReloading;
public bool needTimer;
void Start () {
alreadyReloading = false;
needTimer = false;
}
IEnumerator uztaisyt(){
Debug.Log ("REEELOUUUDING!");
yield return new WaitForSeconds(6.5f);
ammo += 1;
alreadyReloading = false;
}
void Update () {
if (ammo < 5.0f) {
if(alreadyReloading == false){
progress.fillAmount = 1.0f;
needTimer = true;
StartCoroutine(uztaisyt());
alreadyReloading = true;
}
if (needTimer == true) {
timer ("");
}
}
}
void timer(string tipas){
progress.fillAmount -= Time.deltaTime / 6.5f;
}
}