0

I am trying to make it so that a button in Unity that can only be pressed and activated every few seconds. I have an array with famous quotes and when I press my button it does display these quotes, but if you press the button continuously then it will cycle through all of the quotes without the ability to be thoughtful and read through them. Now, I have tried something like below, but it did nothing (as in the button still was able to be pressed and no effect was apparent):

Bool pressed;

void Function () {

   if (pressed) {
    //code to call array
     }
  }

I thought that a simple combination of bool and an if loop would be sufficient, but this does not work. Am I close to this concept? What would need to be changed to get this operating?

1 Answers1

5

There are a few things you can do to achieve this. Your current code is never toggling the bool pressed. You can just catch the unwanted button presses with a localized bool like you are doing now, but there might be a better approach.

I would consider changing the intractability of the button to also show the end-user that they can not press the button for a short amount of time.

using UnityEngine.UI;

// button that will be pressed
[SerializeField] private Button btn = null;

// time until the button is re-enabled after being pressed
private float timeToWait = 5f;

// reference to the coroutine that toggles our button
private Coroutine buttonDisabled = null;

private void Start()
{
    // you can either add the OnClick in the inspector, or you can programmatically add it here
    btn.onClick.AddListener(ClickButton);
}

public void ClickButton()
{
    // if we have an ongoing coroutine do not start another
    if(buttonDisabled != null)
        return;

    // start our coroutine to re-enable the button
    buttonDisabled = StartCoroutine(DisableButtonForSeconds(timeToWait));

    // put any other code needed here such as displaying a new quote
}

private IEnumerator DisableButtonForSeconds(float seconds)
{
    // disable the button 
    btn.interactable = false;

    // wait for our amount of time to re-enable
    yield return new WaitForSeconds(seconds);
    
    // re-enable the button
    btn.interactable = true;

    // reset our reference to be called again
    buttonDisabled = null;
}

Assign the button in the editor to btn and set whatever time you want to wait between button presses for timeToWait. You will also need to assign the button's OnClick to ClickButton, either in code or in the inspector. With this solution the function will not be able to be called as the button itself will be grayed out and not be able to be clicked. After the timeToWait duration is over, the button will reactivate. Place whatever other code you need in the ClickButton method that drives your quote cycling.

Edit: Here is similar code but using an Invoke instead of Coroutine as requested

using UnityEngine.UI;

// button that will be pressed
[SerializeField] private Button btn = null;

// time until the button is re-enabled after being pressed
private float timeToWait = 5f;

private void Start()
{
    // you can either add the OnClick in the inspector, or you can programmatically add it here
    btn.onClick.AddListener(ClickButton);
}

public void ClickButton()
{
    // do not start the function if we are already in the process
    if (IsInvoking("ReEnableButton"))
        return;

    // disable our button interactability
    btn.interactable = false;

    // call our function ReenableButton in timeToWait seconds
    Invoke("ReEnableButton", timeToWait);
}

private void ReEnableButton()
{
    // re-enable the button
    btn.interactable = true;
}

Edit 2: There are a few ways you can go about having one button disable all four buttons. In general practice, you do not want these buttons knowing about each other, so it would be a good idea to make a button manager that handles the OnClick now and sends the event to all Buttons it knows about.

using UnityEngine.UI;

public class ButtonManager : MonoBehaviour
{
    // assign these button references in the inspector by dragging them into the list
    [SerializeField] private List<YourButtonScript> YourButtons = new List<YourButtonScript>();
    
    private void Start()
    {
        // assign the onClick of each button here INSTEAD of the Start() in the button class   
        // you can still have an onClick in the Button itself, but do NOT have it do the disabling
        
        foreach(YourButtonScript btn in YourButtons)
        {
            // assign our onClick to callback to disable all buttons 
            btn.SetDisableOnclick(DisableAllButtons);
        }
    }
    
    public void DisableAllButtons()
    {
        foreach(YourButtonScript btn in YourButtons)
            btn.DisableButton();
    }
}

public class YourButtonScript : MonoBehaviour
{
    using UnityEngine.UI;
        
    // button that will be pressed
    [SerializeField] private Button btn = null;
    
    // time until the button is re-enabled after being pressed
    private float timeToWait = 5f;
    
    // reference to the coroutine that toggles our button
    private Coroutine buttonDisabled = null;
    
    public delegate void DisableButtonCallback();
    
    public void SetDisableOnclick(DisableButtonCallback callback)
    {
        // add the callback to the manager
        btn.onClick.AddListener(delegate{callback();});
    }
    
    private void Start()
    {
        // do NOT call the ClickButton anymore from here as the manager handles it
        // if you have other button specific data put it in HandleSpecificButtonQuoteData() now
        btn.onClick.AddListener(HandleSpecificButtonQuoteData);
    }
    
    private void HandleSpecificButtonQuoteData()
    {
        // put whatever code here that is specific to JUST this button
        // if it handles the quotes or what not, display it here
    }
    
    public void DisableButton()
    {
        // if we have an ongoing coroutine do not start another
        if(buttonDisabled != null)
            return;
    
        // start our coroutine to re-enable the button
        buttonDisabled = StartCoroutine(DisableButtonForSeconds(timeToWait));
    }
    
    private IEnumerator DisableButtonForSeconds(float seconds)
    {
        // disable the button 
        btn.interactable = false;
    
        // wait for our amount of time to re-enable
        yield return new WaitForSeconds(seconds);
        
        // re-enable the button
        btn.interactable = true;
    
        // reset our reference to be called again
        buttonDisabled = null;
    }   
}

Let me know if you have issues with this.

TEEBQNE
  • 6,104
  • 3
  • 20
  • 37
  • 2
    Clean - the only thing I'd suggest is renaming the method from `DisableButton` to `EnableButtonAfterDelay` since it better describes what the coroutine is doing. – Charleh May 07 '21 at 17:42
  • 1
    @Charleh Thanks! Noted and updated. Definitely brings more clarity. – TEEBQNE May 07 '21 at 17:44
  • 2
    I would actually move the `btn.interactable = false;` into the routine and rather call it `DisableButtonForSeconds(float seconds)` tbh :D – derHugo May 07 '21 at 18:28
  • 1
    @derHugo I rather like that. Updated! – TEEBQNE May 07 '21 at 18:41
  • 2
    Thank you everyone for the assistance! – Isaac Franks May 07 '21 at 21:17
  • @IsaacFranks this is far too complicated for a beginner. Just use the `Invoke` function fort a simnple timer in Unity. it's that easy. – Fattie May 08 '21 at 13:35
  • **This answer has extremely poor pattern.** In a call like `DisableButtonForSeconds` the first thing you have to do is **cancel the previous timer**. (If your response is "oh, the only thing that can call DisableButtonForSeconds is the button" that is just not how you program computers. Functions have to actually work, this one doesn't. – Fattie May 08 '21 at 13:37
  • You can't call it "DisableButtonForSeconds". As it stands it would be called something longer: "DisableButtonForSeconds If We Happen To Magically Know That The Only Thing That Will Ever Call This Routine Is That Button And We Never Want To Write Usable Code Or Use Very Basic Patterns" – Fattie May 08 '21 at 13:40
  • @Fattie, so do you mean that I just need to call my quotes function in the 'Invoke' function and that will allow the button to only be pressed every so often? Is that all I need? – Isaac Franks May 10 '21 at 17:03
  • @IsaacFranks No, not exactly. You would still need to disable the button in some way. Using an invoke is just an alternative to using a Coroutine in this situation. I will update the answer to assure that the Coroutine can only be called once in a setting. I can add an example for Invoke if you'd like. The Coroutine should work though. – TEEBQNE May 10 '21 at 17:12
  • I would also consider checking out the [duplicate thread](https://stackoverflow.com/questions/30056471/how-to-make-the-script-wait-sleep-in-a-simple-way-in-unity), which has a very in-depth answer showing various examples. – TEEBQNE May 10 '21 at 17:18
  • I would appreciate an example for the Invoke as well. I have checked out the duplicate, but the reason it is not a duplicate is it has nothing to do with a button. – Isaac Franks May 10 '21 at 18:39
  • @IsaacFranks I added an `Invoke` example. I personally would use `Coroutine` here as there is other functionality outside of just calling the function after some amount of time. I do not see how one is more complex than the other but whichever you understand more go with that. The duplicate does have nothing to do with a button but it does answer your question. It shows an example of how to use Invoke properly. You would need to apply the example to your situation. I also want to add there is **nothing** wrong with the coroutine example. Fattie is being nitpicky. – TEEBQNE May 10 '21 at 21:01
  • 1
    It worked perfectly! The quotes display, the button grays out for however long I like and then it becomes interactable again. I was already trying to pull this off with coroutines instead of the invoke so I went with your original answer, if it makes you feel better. Thanks again for the help! – Isaac Franks May 15 '21 at 16:43
  • @TEEBQNE, just one quick question; I have modified the code to add in the ability to use four buttons, but it only calls the array of the first button every time, despite being accessed in the inspector and throughout the code. Can you explain how I would do this? How do I paste that code into one of these comments so you can see it as its pretty long? – Isaac Franks May 15 '21 at 19:26
  • @IsaacFranks Comments are not really a place to post code. I would open a new question. You can message me when it's up. If I am understanding you correctly though you would want to add a new parameter to the function where you pass in the specific button you want to affect or make the function an extension of buttons themselves. – TEEBQNE May 15 '21 at 19:55
  • Because of the duplicate I can't post anymore questions for the time being and need to resolve that apparently. How would I go about passing that parameter and making it an extension? Do you mean the ClickButton function? – Isaac Franks May 15 '21 at 21:22
  • @IsaacFranks It would be a bit hard to explain in a comment. – TEEBQNE May 15 '21 at 21:27
  • @TEEBQNE how do you send a message? – Isaac Franks May 16 '21 at 16:32
  • @TEEBQNE can you just provide me with a brief explanation? My current solution is having to make 4 separate scripts on 4 separate pages to make the buttons work. Can you help? – Isaac Franks May 25 '21 at 21:21
  • @IsaacFranks Are you just asking how you would use this script on 4 different buttons? Attaching it on each object, then dragging in the corresponding button should allow each button to utilize the script. Adding a script to multiple buttons is fine unless you mean you made 4 different scripts, 1 for each object. The other alternative is to make a manager to manage all buttons you are using. – TEEBQNE May 25 '21 at 21:26
  • @TEEBQNE I can set it up so that there are 4 scripts and 4 buttons on the same page, but clicking one button will not make the other buttons inactive as well. I want to be able to have all four buttons go inactive at the same time. How would I go about doing that? – Isaac Franks May 28 '21 at 17:06
  • Oh sorry I misunderstood. Are you able to ask a new question? I have an answer that would work but I'd like to share more info than a comment can hold. – TEEBQNE May 28 '21 at 17:12
  • I do not have anymore questions left. I understand far more about coding now so I'd love to delete the old questions and get more questions back, but I don't know how to resolve this. Is there any way you could share that answer with me through the answer above? – Isaac Franks May 29 '21 at 16:32
  • @IsaacFranks Forgot I can still edit my post even though the question is closed. I will add an example! – TEEBQNE May 29 '21 at 17:06
  • I commented out the second 'using UnityEngine.UI' as it was producing errors. Before I can test it I have 2 errors. On line 43 (public delegate void DisableButtonCallback;), it says the semicolon is a syntax error...why would that be? Then, on line 48 (btn.onClick.AddListener(callback) ) callback is listed as an error, stating 'cannot convert from YourButtonScript.DisableButtonCallback to UnityEngine.Events.UnityAction'. I am not sure why there is a syntax error in the first one and I do not understand the second, can you explain please? Really appreciate the sample by the way! – Isaac Franks May 29 '21 at 20:51
  • I can get to it when home. Hard to reformat an answer on mobile. I know how to fix it, sorry typed up the code untested and was in a hurry. – TEEBQNE May 29 '21 at 21:03
  • @IsaacFranks Oh, so these are two different scripts. Everything might work fine. One is a manager for buttons and the other is the original button script changed slightly. Make a new empty `GameObject` that has the manager script on it then drag and drop all of the buttons into the inspector list field. – TEEBQNE May 30 '21 at 15:31
  • Woops, rookie mistake! Yes, separated it into two scripts, but the same two errors remain on line 43 and 48, being the semicolon at the end of 'public delegate void DIsableButtonCallback;' and then actual 'callback' as the parameter in the AddListener. What would be causing these errors? It seems really close to working. – Isaac Franks May 30 '21 at 15:55
  • Edit: I added "()" to line 43 and that resolved that error, so only line 48 remains now. – Isaac Franks May 30 '21 at 16:02
  • You might need to wrap the callback in a delegate{}, `btn.onClick.AddListener(delegate{callback();});` And yes you are right, I forgot to add a `()` to the delegate declaration. – TEEBQNE May 30 '21 at 16:07
  • Okay, both errors resolved, thanks. So now all I would do is use the Button Manager to do the disabling and have 4 YourButtonScripts to handle individual quotes, right? – Isaac Franks May 30 '21 at 16:59
  • @IsaacFranks Yep that is correct. If you assign the button references on the manager in the inspector, then the disabling will be handled by the manager. It is still calling the same function, just done from the manager instead. In the `Start` of each button script, it is adding the onClick listener for each quote. I also do not know your whole setup, so am I not 100% sure if this works as you want. – TEEBQNE May 30 '21 at 17:17
  • @TEEBQNE I set up the YourButtonScript as 4 individual scripts renamed based on the quotes content and tested it out. They work and there is no problem with getting all four buttons to go inactive. The only odd thing is I never set up and activated the Button Manager script so I am not sure how they are all disabling. Is that a problem or does it not matter as the concept still works? – Isaac Franks May 30 '21 at 19:47
  • If it works then it's fine. Not sure how it's setup – TEEBQNE May 30 '21 at 20:56
  • My setup is just that I have four of the YourButtonScripts attached in the inspector, each with separate quotes, but all attached to the same UI text and "box" (transparent colored background for the text to display on). Just to make sure it doesn't stop working on me.... in your ButtonManager script, when you have a list of YourButtonScripts, what is the essentially? A list of only scripts call YourButtonScript? Currently, the ButtonManager script (also inspector attached) does not allow any button to be attached to it as it is looking specifically for YourButtonScript. – Isaac Franks May 31 '21 at 17:22
  • Yes so it needs the scripts. You drag in the component that has the script and that script has the button reference. If everything works as is then don't change anything you don't need the manager then. It seems I misunderstood that you had 4 individual buttons and 4 scripts not 1 button and 1 script. – TEEBQNE May 31 '21 at 18:35
  • 1
    Well, I don't know how the four buttons are communicating with each other, but when I press one all four do time out for however many seconds I have set it does work and print out the specific array of quotes. Yes, I always had 4 buttons, but modified it to be 4 scripts as was unaware how to convert what was above. Thank you very much for all of your excellent help with this problem! – Isaac Franks Jun 01 '21 at 17:52