0
\$\begingroup\$

My app will feature tokens to fund some in app functions. If someone clicks a menu button to run a paid function and has insufficient tokens, they should be prompted with a new token purchase menu to buy more tokens. After purchasing sufficient tokens, the requested function that prompted the token purchase should then run automatically.

I am having trouble figuring out the best way to make the paid function automatically run after the tokens are purchased. The best way I could think of is as follows.

In my main menus I am instantiating buttons to run paid functions essentially as follows:

 GameObject menuOption = Instantiate(Resources.Load<GameObject>("Menu Text Toggle Option")) as GameObject;

        //BUILD IN THE CLICK FUNCTION
        menuOption.GetComponentInChildren<Button>().onClick.AddListener(delegate {
          
            if (userTokens >= costOfActivity) {
                userTokens -= costOfActivity;
                Debug.Log("PURCHASED FUNCTION OCCURS HERE");
                }

            else {
                Debug.Log("OPEN TOKEN PURCHASE MENU");
                BuildAndOpenTokenPurchaseMenu();
                IAPButton[] iapButtonList = tokenPurchaseMenu.GetComponentsInChildren<IAPButton>();
              
                  for (int i = 0; i < iapButtonList.Length; i++) {

                    //ADD PURCHASED FUNCTION TO RUN ON COMPLETION
                    iapButtonList[i].onPurchaseComplete.AddListener(delegate {
                        userTokens -= costOfActivity;
                        Debug.Log("PURCHASED FUNCTION OCCURS");
                        GameObject.Destroy(tokenPurchaseMenu);
                    });
                }
            }
        });

So in other words, when you click the action button in the paid function menu, it runs the desired function if you have sufficient tokens. However, if you don't have enough tokens, it generates a token purchase menu (eg. from a prefab) and opens this.

The IAP buttons in this freshly generated token purchase menu would just have actions to buy tokens.But in this script I add the function that needs to complete after that purchase is done as a second listener based on whatever button triggered it to generate and open.

The only way this works though is if I destroy the menu and rebuild it each time. Because otherwise if the token menu survives and is just "hidden" when not needed, I would just be adding more and more listeners to it for cumulative extra functions if it's triggered multiple times.

Destroying the token purchase menu and then re-instantiating solves this, but it's wasteful. I think this method would work. But I wonder if there's a better way to do it.

Does this make sense and how would you approach it?

eg. Is there some way I could use the same approach but just remove the extra added listener at the end rather than nuking the whole token purchase menu each time and rebuilding it from scratch?

Thoughts? Thanks.


So if I'm not clear, my code for general function purchase menu is:

  1. Instantiate regular menu button in regular purchase function menu.
  2. Build into that menu button the function to generate and open a token purchase menu if there are inadequate funds.
  3. Have the token purchase menu generate from its own prefab with only basic token purchase functions built in to run on purchase completion.
  4. In step #2, also then add a second listener to the token purchase IAP menu buttons in #3 to run whatever function prompted the creation of the token purchase menu.
  5. Destroy the token purchase menu when done (as it now has extra listeners added to its IAP buttons that I don't want to stick around).
\$\endgroup\$
1
\$\begingroup\$

I'd assume you considered removing your listeners once they've done their work?

IAPButton[] iapButtonList = tokenPurchaseMenu.GetComponentsInChildren<IAPButton>();

// Prepare a function to add as a listener on each purchase button.
UnityAction<Product> purchaseWhenDone = null;
purchaseWhenDone = (Product product) => {
    // Deduct the cost and execute the action, whatever it is.
    CompleteAction();

    // Clean up the listeners we added to all these buttons.
    foreach(var button in iapButtonList)
        button.onPurchaseComplete.RemoveListener(purchaseWhenDone);
};

// Add listeners to each button.
foreach(var button in iapButtonList)
    button.onPurchaseComplete.AddListener(purchaseWhenDone);

// Also remove these listeners if the player cancels out of the menu.
// TODO: Also respond to Escape / Back if those are used in your app.
var cancel = tokenPurchaseMenu.transform.Find("Cancel Button").GetComponent<Button>();

cancel.onClick.addEventListener(() => {
    foreach(var button in iapButtonList)
        button.onPurchaseComplete.RemoveListener(purchaseWhenDone);
});
\$\endgroup\$
3
  • \$\begingroup\$ Thanks that's brilliant DMGregory and just what I was wondering how to do. I didn't know how to give the added listener a name or identity so I could selectively remove it when done as you showed. I will try that. \$\endgroup\$
    – mike
    Nov 19 '21 at 2:55
  • \$\begingroup\$ Note that you'll also need to remove the cancel button listener the same way. I accidentally skipped that part. \$\endgroup\$
    – DMGregory
    Nov 19 '21 at 5:02
  • \$\begingroup\$ Though on reflection, I think my preference here would be to make a PurchaseMenu script that exposes a "post-purchase" field, a method that executes the post-purchase action, if any, and then clears that field, and a cancel/close method that also clears that field. Wire up a listener to all the purchase buttons to call the post-purchase method, and leave them that way. No need to rewire them on the fly. Instead, the script that shows the purchase menu can just set the post-purchase action when relevant, and the menu itself will clear it when it's been acted upon or cancelled. Less garbage! \$\endgroup\$
    – DMGregory
    Nov 19 '21 at 23:55

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .