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;
}
Related
I have a scene with multiple triggers and a Player with collider. My idea is to have one script to deal with all interactions between triggers and Player collider. I thought that using the same script to different GameObjects would be enough but this thing doesn't work properly. While working with one trigger I need to change one GameObject attached to this trigger but the other GameObjects with this script are changing as well.
One more detail about script is that I have two types of triggers: one is for GameObjects that need some input from user, some doesn't need it. I check it by checking two fields firstChoiceText and secondChoiceText. And if they are empty, the interaction with GameObject doesn't need any choice from the player. Otherwise, I show a plane with two option and change the text of this options using firstChoiceTextfield and secondChoiceTextfield.
So the main idea of script is that when the Player enter the trigger the script activates E key of keyboard. And if we press the E button and if this interaction wasn't done yet, we go further. We check the type of trigger and if we need to make a choice, we activate so called choiceMode of PlayerScript. PlayerScript is just a script for movements of the player, and we need choiceMode just to use W and S keys to select the choice. We make our choice and return to usual movement of the player. If we don't need to make a choice we just do some action and mark the trigger as isDone.
Everything works great with one trigger but when I use several triggers and GameObjects with InteractionScript, all this triggers are mixed up. While working with one trigger I accidentally change the other script. And I don't quite understand how to separate this scripts from each other.
Can anybody give me some piece of advice?
using UnityEngine;
using UnityEngine.UI;
public class InteractionScript : MonoBehaviour
{
[SerializeField]
private GameObject buttonE;
[SerializeField]
private GameObject choiceMenu;
[SerializeField]
private PlayerScript ps;
private int choiceNum = 0;
[SerializeField]
private Transform choicePoint;
[SerializeField]
private Text firstChoiceTextfield;
[SerializeField]
private Text secondChoiceTextfield;
[SerializeField]
private string firstChoiceText;
[SerializeField]
private string secondChoiceText;
[SerializeField]
private bool enteredMe;
[SerializeField]
private bool isDone;
void OnTriggerEnter2D() {
if (!isDone) {
enteredMe = true;
buttonE.SetActive(true);
Debug.Log("++");
}
}
void OnTriggerExit2D() {
if (!isDone) {
buttonE.SetActive(false);
enteredMe = false;
}
}
void Update() {
if (!isDone) {
if (Input.GetKeyDown(KeyCode.E) && buttonE.activeSelf && enteredMe) {
if (firstChoiceText != "" && secondChoiceText != "") {
choiceMenu.SetActive(true);
buttonE.SetActive(false);
ps.choiceMode = true;
Debug.Log("with choice");
} else {
Debug.Log("without choice");
//Action
buttonE.SetActive(false);
isDone = true;
}
}
}
if (ps.choiceMode) {
firstChoiceTextfield.text = firstChoiceText;
secondChoiceTextfield.text = secondChoiceText;
if (Input.GetKeyDown(KeyCode.W)) {
choiceNum += 1;
}
if (Input.GetKeyDown(KeyCode.S)) {
choiceNum -= 1;
}
if (choiceNum > 1) {
choiceNum = 0;
} else if (choiceNum < 0) {
choiceNum = 1;
}
if (choiceNum == 0) {
choicePoint.localPosition = new Vector3(0f, 24f, 0f);
if (Input.GetKeyDown(KeyCode.Return)) {
Debug.Log("choice1");
choiceMenu.SetActive(false);
isDone = true;
enteredMe = false;
ps.choiceMode = false;
}
} else if (choiceNum == 1) {
choicePoint.localPosition = new Vector3(0f, -21.5f, 0f);
if (Input.GetKeyDown(KeyCode.Return)) {
Debug.Log("choice2");
choiceMenu.SetActive(false);
isDone = true;
enteredMe = false;
ps.choiceMode = false;
}
}
}
}
}
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
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 have 26 images (GameObjects) where each image has a button.
If a button is clicked, it plays a sound and animation.
However, I have a problem. I have tried to prevent this, but if I perform a multi touch / stress test, some buttons cannot be clicked anymore. If I click slowly it works fine, but if I perform multiple clicks on a button then it will fail/can't be clicked anymore. Any idea why?
My source code :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using DG.Tweening;
[RequireComponent(typeof(AudioSource))]
public class z_abcLetter_Main : MonoBehaviour {
public GameObject InputData;
public List<GameObject> List_uGUI;
private List<Sprite> List_Sprite;
private List<AudioClip> List_AudioClip;
private int currentIndex;
private GameObject currentGameObject;
private GameObject parentGameObject;
// Use this for initialization
void Start () {
List_Sprite = InputData.GetComponent<z_abcLetter_InputData>().Daftar_element;
List_AudioClip = InputData.GetComponent<z_abcLetter_InputData>().Daftar_Suara;
int total_element = List_Sprite.Count;
for (int i = 0; i < total_element; i++)
{
List_uGUI[i].GetComponent<UnityEngine.UI.Image>().sprite = List_Sprite[i];
List_uGUI[i].GetComponent<UnityEngine.UI.Image>().preserveAspect = true;
}
}
public void onButtonClick()
{
currentIndex = int.Parse(EventSystem.current.currentSelectedGameObject.transform.parent.name.ToString());
currentGameObject = EventSystem.current.currentSelectedGameObject;
parentGameObject = currentGameObject.transform.parent.gameObject;
StartCoroutine(PlaySoundWithAnimation());
}
IEnumerator PlaySoundWithAnimation()
{
currentGameObject.GetComponent<UnityEngine.UI.Button>().enabled = false;
currentGameObject.GetComponent<UnityEngine.UI.Button>().interactable = false;
AudioSource audio = GetComponent<AudioSource>();
audio.clip = List_AudioClip[currentIndex];
audio.Play();
parentGameObject.transform.DOPunchScale(new Vector3(2, 2, 0), 1, 1, 1);
yield return new WaitForSeconds(1.5f);
currentGameObject.GetComponent<UnityEngine.UI.Button>().enabled = true;
currentGameObject.GetComponent<UnityEngine.UI.Button>().interactable = true;
}
// Update is called once per frame
void Update () {
}
}
Your code seems to behave as expected. But for fast multiple clicks, you need to reset your coroutine.
To do that, you need a reference to PlaySoundWithAnimation.
private IEnumerator coroutine;
void Start()
{
coroutine = PlaySoundWithAnimation();
}
public void onButtonClick()
{
StopCoroutine(coroutine);
// you also need to stop audio here
//...
StartCoroutine(coroutine);
}
see: https://docs.unity3d.com/ScriptReference/MonoBehaviour.StopCoroutine.html
The reason some buttons are not responding is because the coroutines are stacked and they're all waiting for yield return new WaitForSeconds(1.5f);
see: https://answers.unity.com/questions/309613/calling-startcoroutine-multiple-times-seems-to-sta.html
Note:
If you don't want to stop the clip, consider audio.clip.length before enabling the buttons.
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