Showing a Text layer for a short amount of time - c#

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

Related

Fire bullets at an interval continuously while the fire button is held down

In C# Unity3D, I'm trying to get my bullets to fire at an interval of bulletTime. Single shots work perfectly fine at the moment, but when I hold down the fire button, just 1 bullet is created and shot and then nothing else happens.
// Left mouse button HELD down.
else if (fireAgain && Input.GetMouseButton(0))
{
StartCoroutine("LoopNShoot");
fireAgain = false;
}
else if (timerH < bulletTime)
{
timerH += Time.deltaTime;
if(timerH >= bulletTime)
{
fireAgain = true;
timerH = 0;
}
}
IEnumerator LoopNShoot()
{
pivot.transform.Rotate(triggerAngle,0,0);
GameObject bullet = ObjectPooler.SharedInstance.GetPooledObject();
if (bullet != null) {
bullet.transform.position = SSpawn.transform.position;
bullet.transform.rotation = SSpawn.transform.rotation;
bullet.SetActive(true);
}
yield return new WaitForSeconds(.1f);
pivot.transform.rotation = Quaternion.Slerp(transform.rotation, originalRotationValue, Time.deltaTime * rotationResetSpeed);
}
Im thinking I need to place all my if statements and timer inside the coroutine? But that doesnt seem to help either...
Please ignore the pivot and rotation, it works fine for some animation, the only thing taht doesnt work is shooting bullets continuosly at a set interval while the fire button is Held down.
You should remove these else and just do.
private void Update()
{
if (fireAgain && Input.GetMouseButton(0))
{
StartCoroutine("LoopNShoot");
fireAgain = false;
timerH = 0;
}
if (timerH < bulletTime)
{
timerH += Time.deltaTime;
if(timerH >= bulletTime)
{
fireAgain = true;
}
}
}
You could also re-arange this to make clearer how it works:
private void Update()
{
if(!fireAgain)
{
timerH += Time.deltaTime;
if (timerH >= bulletTime)
{
fireAgain = true;
}
}
else
{
if(Input.GetMouseButton(0))
{
StartCoroutine("LoopNShoot");
fireAgain = false;
timerH = 0;
}
}
}
I actually thought you anyway already chose the Invoke method I showed you here

Cool down function for skill button in mobile game

I want to include a cool down function to my skill button in my mobile game that I am currently developing. So I wanted to allow the player to only be able to use the skill button once every few seconds.
Left button is my default skill button and right one is a duplicate. The default skill button will be placed over the duplicate so when I run the game, upon clicking on the default skill button, the duplicate will overlap the default skill button.
However, in my case the duplicate is not able to overlap the default skill button so it does not show the cool down timer.
I am wondering if I need to include a set of codes to allow the default skill button to turn inactive upon clicking or do I just have to sort the layers?
My current codes are as such:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class Abilities : MonoBehaviour
{
public Image abilityImage1;
public float cooldown = 5;
bool isCooldown = false;
public Button ability1;
void Start()
{
abilityImage1.fillAmount = 0;
ability1.onClick.AddListener(AbilityUsed);
}
private void AbilityUsed()
{
if (isCooldown)
return;
isCooldown = true;
abilityImage1.fillAmount = 1;
StartCoroutine(LerpCooldownValue());
}
private IEnumerator LerpCooldownValue()
{
float currentTime = 0;
while (currentTime < cooldown)
{
abilityImage1.fillAmount = Mathf.Lerp(1, 0, currentTime /
cooldown);
currentTime += Time.deltaTime;
yield return null;
}
abilityImage1.fillAmount = 0;
isCooldown = false;
}
}
The first picture is my original skill button and on the bottom is a duplicate which I made to adjust the color to create an overlay effect for the cool down.
Thanks!
Use Lerp instead of changing fill amount in Update() method
void Start()
{
abilityImage1.fillAmount = 0;
ability1.onClick.AddListener(AbilityUsed);
}
private void AbilityUsed()
{
if (isCooldown)
return;
isCooldown = true;
abilityImage1.fillAmount = 1;
StartCoroutine(LerpCooldownValue());
}
private IEnumerator LerpCooldownValue()
{
float currentTime = 0;
while (currentTime < cooldown)
{
abilityImage1.fillAmount = Mathf.Lerp(1, 0, currentTime / cooldown);
currentTime += Time.deltaTime;
yield return null;
}
abilityImage1.fillAmount = 0;
isCooldown = false;
}

Unity Cool Down

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;
}

Problems with my invincibility frames feature

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.

Stop co-routine with a toggle button

I have a co-routine that is triggered when the bool of a toggle button changes, when the bool is changed again that co-routine should be stopped and another one should start. This is my code:
public class thermoPowerControlPanel : MonoBehaviour {
private ThermoElectric thermo;
public bool toggleBool1;
public int temperature;
private int tempUp = 10;
private int tempDown = 1;
public thermoPowerControlPanel (){
temperature = 100;
}
public void turbine1State (bool toggleBool1) {
if (toggleBool1 == false) {
Debug.Log (toggleBool1);
Invoke("ReduceTemperatureEverySecond", 1f);
}
if (toggleBool1 == true) {
Debug.Log (toggleBool1);
Invoke("IncreaseTemperatureEverySecond", 1f);
}
}
private void ReduceTemperatureEverySecond()
{
if (toggleBool1 == true)
{
Debug.Log("I was told to stop reducing the temperature.");
return;
}
temperature = temperature - tempDown;
Debug.Log (temperature);
Invoke("ReduceTemperatureEverySecond", 1f);
}
private void IncreaseTemperatureEverySecond()
{
if (toggleBool1 == false)
{
Debug.Log("I was told to stop increasing the temperature.");
return;
}
temperature = temperature + tempUp;
Debug.Log (temperature);
Invoke("ReduceTemperatureEverySecond", 1f);
}
}
When the function turbine1State(bool t1) receives the first bool (false), the routine decreaseTemperatureEverySecond() starts but it stops immediately after, sending the Debug.Log message, it should keep reducing the temperature until the bool (activated by the toggle button) turned true.
.
Can you help?
It is this easy!
public Toggle tog; // DONT FORGET TO SET IN EDITOR
in Start ...
InvokeRepeating( "Temp", 1f, 1f );
... and then ...
private void Temp()
{
if (tog.isOn)
temperature = temperature + 1;
else
temperature = temperature - 1;
// also, ensure it is never outside of 0-100
temperature = Mathf.Clamp(temperature, 0,100);
}
If you ever need to "totally stop" that action (both up and down), just do this
CancelInvoke("Temp");
So easy!
NOTE purely FYI, the other pattern I explained is this:
bool some flag;
Invoke("Temp", 1f);
private void Temp()
{
if (some flag is tripped) stop doing this
.. do something ..
Invoke( ..myself again after a second .. )
}
In real life, it is usually better to "keep Invoking yourself" rather than use InvokeRepeating.
In this simple example, just use InvokeRepeating, and then CancelInvoke.
You can stop coroutine only by it name.
Just try some like StartCoroutine("increaseTemperature"); and then StopCoroutine("increaseTemperature");.
http://docs.unity3d.com/Manual/Coroutines.html
There are a number of ways to call StartCoroutine. You can "stop" a coroutines IF you start it with the "string" method, like this StartCoroutine("FunctionNameAsStringHere"); If you start it like this StartCoroutine(FunctionNameAsStringHere()); you cannot stop it by name.
(You can also access the actual enumerator to stop coroutines, but that is far beyond the scope of a beginner using coroutines.)

Categories