Search Unity

Resolved putting out an item after buying from store

Discussion in 'Game Foundation' started by lpchow, Jul 16, 2020.

  1. lpchow

    lpchow

    Joined:
    Nov 30, 2014
    Posts:
    19
    hi there, im not fluent in programming so please bear with me. i got to know Game Foundation last week and thought it will be super helpful for my game and likely easier to grasp. indeed it is! i hope to be able to ship my game with this package. *finger cross*

    i have set up the store (following the tutorial) but i am stuck on how to know which item was purchase from the store. my gameplay is very similar to neko atsume / cat collector genre. there are two scenarios:

    1. after player purchased the item in store, a pop up will appear asking if they want to set the item into scene. if player click yes, they will be brought to main scene to choose which slot to input their item. i do not know what code or id to pass to my Game Manager so that it knows which item to put into the slot in the main scene.
    upload_2020-7-16_23-22-50.png upload_2020-7-16_23-32-29.png
    i manage to instantiate a dummy 3d game object to place into the green slot. now i just need to know what item was bought so that i can place the right game object at the green slot.

    2. if player click no, they can choose to set purchased items at a later time. in my store, after item has been purchased once, the "buy" button will turn to a "set" item button. in this scenario, i also do not know what code or id to pass to my Game Manager.
    upload_2020-7-16_23-22-23.png

    im thinking, maybe i will need to add some asset details for the inventory item to point to the actual item game object path. but i am also not sure how to access the asset details.

    really appreciate any inputs! thank you.
     
    erika_d likes this.
  2. erika_d

    erika_d

    Joined:
    Jan 20, 2016
    Posts:
    413
    Hi @ahblock,

    Thanks for using Game Foundation in your game, I'm glad you're enjoying it so far!

    1. The transaction system will automatically add any purchased items and currency to the player's inventory and wallet (so you could just show the whole inventory of purchased items and let them choose which to add to the slot) but since you want to show them specifically the latest item they purchased, you can get the key of the purchased reward from the onTransactionSucceeded callback. (I'm not sure whether you're using the store prefab as demo'd in the UniteNow tutorial, or writing your own so I've included instructions in both cases)

    If using Game Foundation Store Prefab:
    a) In a script that you have loaded in your store scene add a method to be called when a transaction is successful, the method signature should look something like:
    Code (CSharp):
    1. public void OnTransactionSucceeded(BaseTransaction transaction)
    b) In the Store View component (found in the Inspector when the store prefab is selected in the hierarchy) add this new method you created as a listener to the On Transaction Succeeded callback. It will look something like:
    Screen Shot 2020-07-16 at 1.41.01 PM.png

    c) Back in the OnTransactionSucceeded method in your script, you can access the key like
    Code (CSharp):
    1. string purchasedItemKey = null;
    2.  
    3. if (transaction?.rewards != null )
    4. {
    5.       if (transaction.rewards.ItemExchangeCount > 0)
    6.       {
    7.            purchasedItemKey = transaction.rewards.GetItemExchange(0).item.key;
    8.            // Call the GameManager code using purchasedItemKey
    9.         }
    10. }
    (this assumes that the item is the first item that you're interested in is the first item in the reward list cause it sounded to me like your transactions are for only one item, not a combination. If it's for a combination you'll probably want to use GetItemExchanges and pass in a list instead.)​

    If using your own Store code and initiating transactions yourself:
    a) Check out the ExecuteTransaction method in the PurchaseButton component (GameFoundation/UI/Runtime/PurchaseButton.cs) to see how we're initiating a transaction and determining whether it was successful so that we can invoke OnPurchaseSuccess (this is ultimately the callback that's triggering the listener described above)

    b) A quick sum up of that method is that it waits for deferred.isDone to be true, then once it's true it checks whether the rewards have been fulfilled (deferred.isFulfilled) and if they have been it considers the transaction complete and successful at this point you could pass the key to the game manager using this code and the transaction you triggered the purchase with:
    Code (CSharp):
    1. string purchasedItemKey = null;
    2.  
    3. if (transaction?.rewards != null )
    4. {
    5.       if (transaction.rewards.ItemExchangeCount > 0)
    6.       {
    7.            purchasedItemKey = transaction.rewards.GetItemExchange(0).item.key;
    8.            // Call the GameManager code using purchasedItemKey
    9.         }
    10. }

    You can pass this key to the GameManager, and then in the GameManager you could look up the item in the Inventory based on that key. (Or you could even pass the item instead of item.key, which would be an InventoryItemDefinition, and then be able to skip the looking it up in the inventory step).

    2. In this scenario you would get the reward item key off of the transaction in the same way as above (transaction.rewards.GetItemExchange(0).item.key)

    You could definitely use the asset detail to point to an image icon, or a prefab you've created for the item if that's what you mean by actual item game object path, and then use that to display the item in your game. Some things to note for this:
    - The items you want to reference in the assets detail need to be in a resources folder, and then in the asset detail you provide the path from inside the Resources folder i.e. if my prefab is in Resources/Icons/myIcon the path I would add in the asset detail would be Icons/myIcon.
    - Here's sample code for getting a sprite image from an item's asset detail: var sprite = item.GetDetail<AssetsDetail>()?.GetAsset<Sprite>("my_asset_key");


    I hope this all helps answer your questions! Let us know if you have any other questions. Also, you can learn more about working with Game Foundation, by checking out our samples (if you haven't already), which can be downloaded in package manager from the game foundation package!
     

    Attached Files:

  3. lpchow

    lpchow

    Joined:
    Nov 30, 2014
    Posts:
    19
    hi @erika_d , thanks for the help! i manage to get the first scenario solved using the code you provided + referencing the prefab in the inventory assets detail using your sprite example. i am using the Game Foundation Store Prefab + i have also check out the samples prior.


    for the second scenario, i was going to do the same set up as the first scenario, however i realise there is no transaction occurring, so i am stuck again >_<.

    player can visit the store anytime to "set" any items that they have purchased before (not necessarily last purchased item). whenever player first purchase an item, i will hide the buy button in the store (so that they cannot buy more than one of the same item) and show a "set item" button. the reasons why i set it up like this, were:
    - i dont know how to limit player to only buy once per item
    - originally, i wanted to display all purchased items in a separated inventory ui, but i couldn't figure out how to do it from the samples or from forum. lol... (i think the current implementation is simpler on the UX front but will be good to know how to set up an inventory of purchased items)
    upload_2020-7-17_16-42-49.png

    ok here come my questions:
    1. is there a way to know what inventory item is the "Set Item!" button on? this button did not have the Game Foundation Purchase Button script. so that when player press the "Set Item!" button, i know which is the item he wants to set in the main scene.
    2. how do i display all purchased items in a separated inventory ui? which samples can i reference to understand more about how to set this up? there was another thread on this question, but i could not figure out from the replies.

    really appreciate your replies, thank you!
     
    erika_d likes this.
  4. erika_d

    erika_d

    Joined:
    Jan 20, 2016
    Posts:
    413
    Hi @ahblock,

    Glad that helped answer your first question!

    For the second question/new questions:

    1) I would suggest tying the button you've created either to the inventory item you want it to set or to a transaction (even though you're not actually initiating a transaction with that button press you could still use it to get the item in the reward). I would look into doing it a couple different ways. Either in the script attached to your button add a public variable for InventoryItemDefinition and then attach an inventoryItemDefinition to that button in the editor inspector or when the button is initialized (how to do that attaching kinda depends on how you've got the scene and your game set up) or use something like our TransactionItem prefab to display the item as a whole, and then alternate between showing a purchaseButton prefab and a setItemButton prefab (of your own creation). Then the transactionItem component could pass the correct transaction to either the purchaseButton or the setItemButton (and they could each have the code for passing the item in the transaction's payout/reward to the gameManager).

    2) You could probably go about doing this a couple different ways, but my first thought is to give each InventoryItemDefinition that can be purchased from the store a "Purchasable" or some such tag. Then at runtime, the InventoryManager will hold all items the player has, including the purchased ones, and so you could call FindItemsByTag("Purchasable") to get a list of items the player has that can be purchased in the store. (It wouldn't be a guarantee that they did purchase it in the store though, if your players can attain an item by purchasing and another way. Right now if you needed a guarantee you'd probably need to keep track of the list yourself.) And then once you have the list you can display them in your own UI.

    (Also as a side note you mention not knowing how to limit player to only buy one of each item. Your way of hiding the button is a good way if you're just talking about not being sure how to prevent player from trying to purchase an item that they can't purchase again. But if you're talking about how to actually make it so that the platform stores prevent the duplicate purchase of an IAP, check out consumable vs non consumable IAPs. Both Google and Apple app stores have a concept of this although they address it differently. Here's a link from apple to get you started: https://developer.apple.com/in-app-purchase/)

    I hope those suggestions are clear and helpful. Let me know if not!
     
    lpchow likes this.
  5. lpchow

    lpchow

    Joined:
    Nov 30, 2014
    Posts:
    19
    Hi @erika_d, appreciate the replies!

    i think my command of programming is not sufficient to implement your suggestions for 1. i spent a day figuring out the syntax or logic but have trouble with both suggestions. i will like to focus on this section first:
    is it possible to provide code snippet or direct me to a sample example that is similar?

    i added a script to my "set" button and also added a public variable for InventoryItemDefinition. how do i attach an inventoryItemDefinition to that button in the editor inspector? what info do i need to provide as you mention the attaching depends on my scene set up?

    my "set" button is in a StoreItem prefab (which is a duplicate from the Vertical Transaction Item prefab that comes with the samples). the Grid Store (also using the prefab from samples) use this prefab as the Transaction Item Prefab
    upload_2020-7-20_21-9-2.png
    upload_2020-7-20_21-8-40.png

    i also have syntax issues with trying to retrieve the "item_3dasset" from the inventoryItem on line20.

    this is how my current script look like:
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.GameFoundation.DefaultLayers;
    5. using UnityEngine.GameFoundation;
    6.  
    7. public class SetButton : MonoBehaviour
    8. {
    9.     SettingItems settingItems;
    10.     public InventoryItemDefinition inventoryItemDef;
    11.  
    12.     private void Start()
    13.     {
    14.         settingItems = FindObjectOfType<SettingItems>();
    15.     }
    16.  
    17.     public void ItemToSet()
    18.     {
    19.         var inventoryItem = InventoryManager.FindItemsByDefinition(inventoryItemDef);
    20.         var item = inventoryItem.GetDetail<AssetsDetail>()?.GetAsset<GameObject>("item_3dasset"); //error:'InventoryItem[]' does not contain a definition for 'GetDetail' and no accessible extension method 'GetDetail' accepting a first argument of type 'InventoryItem[]' could be found
    21.         //how to retrieve the "item_3dasset" from the inventoryItem variable?
    22.  
    23.         //to pass game object of the inventory item so that the settingItem script can set the item in scene
    24.         settingItems.PurchasedItemIs(item);
    25.     }
    26.  
    27. }
    look forward to your reply, thank you!
     
    erika_d likes this.
  6. erika_d

    erika_d

    Joined:
    Jan 20, 2016
    Posts:
    413
    Hey @ahblock,

    Since you're using versions of the store and item prefabs I think the easiest way to hook up the items is the second suggestion in answer 1. So in the script you have attached to your StoreItem (which I assume is a duplicate of our TransactionItemView script) you can choose whether to display the PurchaseButton or SetItem button (it looks like from your video you're already doing this part) and pass the transactionKey to the SetItem button just like we do with the purchaseButton.

    For example:
    In TransactionItemView.cs in the UpdateContentAtRuntime() method we have code like this:

    Code (CSharp):
    1. if (m_PurchaseButton != null)
    2. {
    3.     m_PurchaseButton.Init(m_TransactionKey, m_PriceIconSpriteName, m_NoPriceString);
    4. }
    5. else
    6. {
    7.     Debug.LogWarning($"{nameof(TransactionItemView)} - Purchase Button is not defined.");
    8. }
    I would suggest changing it to look like
    Code (CSharp):
    1. if (itemNotOwned) //It will be up to you to figure out how you want to track itemNotOwned (the most basic way which seems like it would work for your app is checking if it already exists in the InventoryManager)
    2. {
    3.     if (m_PurchaseButton != null)
    4.     {
    5.         m_PurchaseButton.Init(m_TransactionKey, m_PriceIconSpriteName, m_NoPriceString);
    6.     }
    7.     else
    8.     {
    9.         Debug.LogWarning($"{nameof(TransactionItemView)} - Purchase Button is not defined.");
    10.     }
    11. }
    12. else
    13. {
    14.     if (m_SetItemButton != null)
    15.     {
    16.         m_SetItemButton.Init(m_TransactionKey);
    17.     }
    18.     else
    19.     {
    20.         Debug.LogWarning($"{nameof(TransactionItemView)} - Set Item Button is not defined.");
    21.     }
    22. }
    Then in your SetButton script you can add an init method that takes a string, looks up the transaction by it's key, gets the item reward and sets your inventoryItemDef variable.

    Code (CSharp):
    1. public void Init(string transactionKey)
    2. {
    3.     var transaction = GameFoundation.catalogs.transactionCatalog.FindItem(m_TransactionKey);
    4.     if (transaction?.rewards != null )
    5.     {
    6.         if (transaction.rewards.ItemExchangeCount > 0)
    7.         {
    8.             inventoryItemDef = transaction.rewards.GetItemExchange(0).item;
    9.         }
    10.     }
    11. }

    For your second question, InventoryManager.FindItemsByDefinition(inventoryItemDef) returns an array of InventoryItems that match that definition. And you only need one to look up the asset detail on. So you want something like (changed pieces in bold):

    var inventoryItems = InventoryManager.FindItemsByDefinition(inventoryItemDef);
    if (inventoryItems.Count > 0)
    {

    var item = inventoryItems[0].GetDetail<AssetsDetail>()?.GetAsset<GameObject>("item_3dasset");
    }


    Good luck!
     
    Last edited: Jul 20, 2020
    mingz-unity likes this.
  7. lpchow

    lpchow

    Joined:
    Nov 30, 2014
    Posts:
    19
    hi @erika_d,

    i ran into the following error for this line of code

    m_SetItemButton.Init(m_TransactionKey);

    Library\PackageCache\com.unity.game.foundation@0.6.0-preview.2\UI\Runtime\TransactionItemView.cs(421,33): error CS1061: 'Button' does not contain a definition for 'Init' and no accessible extension method 'Init' accepting a first argument of type 'Button' could be found (are you missing a using directive or an assembly reference?)

    i think it is because i declare it as a Button? --> public Button m_SetItemButton;
    when i declare it as --> public SetButton m_SetItemButton;

    i got this error instead:

    Library\PackageCache\com.unity.game.foundation@0.6.0-preview.2\UI\Runtime\TransactionItemView.cs(95,16): error CS0246: The type or namespace name 'SetButton' could not be found (are you missing a using directive or an assembly reference?)

    i am not too sure what code to add at the namespace. the TransactionItemView script i am using is the exact same one provided in the Game Foundation package.
     
  8. lpchow

    lpchow

    Joined:
    Nov 30, 2014
    Posts:
    19
    OHHH, i manage to get it work in another way! kind of clunky but better than nothing. whenever player press the SetButton, it will trigger the PassTransactionKey().
    upload_2020-7-21_20-9-50.png

    PassTransactionKey() will call a function in TransactionItemView.cs to return the transactionKey and pass it to GFInit (which is the main script putting out the right prefab based off the transactionKey for the purchase button).

    code for PassTransactionKey():

    Code (CSharp):
    1. public void PassTransactionKey()
    2.     {
    3.         var transactionKey = transactionItemView.GetTransactionKey();
    4.         gfInit.ItemToSet(transactionKey);
    5.     }

    code added in the TransactionItemView.cs:

    Code (CSharp):
    1. public string GetTransactionKey()
    2.         {
    3.             return m_TransactionKey;
    4.         }

    code for ItemToSet() in GFInit is exactly the same as how to process for Item that was purchase, except added this line of code to convert transactionKey to transaction:

    Code (CSharp):
    1. var transaction = GameFoundation.catalogs.transactionCatalog.FindItem(transactionKey);

    however, i encounter a bug. the additional code i added into the TransactionItemView.cs (which is stored Packages folder) disappear when i reopen unity. if anyone encounter this issue before or know how to fix? thanks! especially @erika_d, thank you for all those help!!! i wouldnt be able to get this far without your assistance.

    ps: additional info on the bug. after i close my unity, i check the script in a notepad and the additional code is still there. when i reopen my unity, the script revert back to the default version. i also tried adding comments at various places in the script, but all gone whenever i reopen unity.
     
    Last edited: Jul 21, 2020
    erika_d likes this.
  9. erika_d

    erika_d

    Joined:
    Jan 20, 2016
    Posts:
    413
    Great job finding a workaround for the errors! :) For this new issue I would suggest copying the TransactionItemView.cs into a new file in your own project, instead of editing the file in the packages folder (I'm actually surprised to hear it lets you edit that file at all). Then you can associate your transactionItem prefabs with your new script (the copy of TransactionItemView). (This should also fix your issue of "namespace name 'SetButton' could not be found", although since you already found a way to work around that, that might not be necessary, just a fun fact ;) )

    You likely may also need/want to copy TransactionItemViewEditor into your own project, so that you don't lose the custom way the inspector for that script is displayed. (Change [CustomEditor(typeof(TransactionItemView))] to whatever you name your copy)
     
    lpchow likes this.
  10. lpchow

    lpchow

    Joined:
    Nov 30, 2014
    Posts:
    19
    @erika_d, thanks for the suggestions! i had to also duplicate PurchaseButton.cs and StoreView.cs for the duplicated TransactionItemView.cs to work. previously was throwing out these errors:
    upload_2020-7-23_15-5-21.png

    --> good to know! i guess the scripts that are/will be referencing each other have to be kept in the same directory aka under the Assets folder. lol wasnt aware of it previously. so if next time, i need to edit scripts that are in the Packages folder, i should move/duplicate them to the Assets folder.

    thanks! i will mark this thread as solved. :D

    one small thing if you can point me to the right direction (not related to this thread topic).

    i notice a bug where by the purchase button did not fade out (aka not interactable) for the second store when the currency in the wallet is not sufficient. i have two grid stores (toys and plants). when player click the store button, the store ui will pop up displaying the toy store first (plant store is hidden). player can then switch between toy and plant store from the ui buttons.
    upload_2020-7-23_15-19-23.png
    if i were to spend all my currency in the plant store first, i notice the purchase buttons on the toy store is not faded. no transaction is made when i click on the button. just a visual bug. if i click on the purchase buttons prefabs in the scene view while the game is still running, the purchase buttons will fade out like how it should be. refer to the first video (ran out of gold coins but purchase buttons is clickable at the toy store). second video show that the purchase buttons are working correctly if i were to spend all my currency in toy store first.



    so i suspect that maybe i need to have both stores set to active when game first start. it didn't solve the bug. instead, the reverse happen whereby if i spend all my currency in the toy store first, the plant store's purchase buttons doesn't fade out correctly.

    if you have any idea what may be causing this bug?
     
    erika_d likes this.
  11. erika_d

    erika_d

    Joined:
    Jan 20, 2016
    Posts:
    413
    Yay! Glad the PurchaseButtons are working now! Good to know that duplicating the TransactionItemView class made you also duplicate the other two, I'll keep that in mind as we improve our prefabs.

    To summarize the purchase button issue:
    - If the toy store is set to active on open (but not the plant store), then you change to plant store and spend all your money in the plant store, the toy store buttons will still be enabled.
    - If the toy store is set to active on open (but not the plant store), and you spend all your money in the toy store, then go to the plant store, the plant store buttons will be properly disabled.
    - If both the toy and plant stores are set to active on open, and you spend all your money in either one, then the buttons in the other will still be enabled.

    When you navigate from the toy store to the plant store, I assume you're leaving the toy store as active?

    My guess would be that it's not redrawing the toy store when you navigate back to it, so the buttons are staying the same as they were when you left it. But when you set the store as active (from inactive) it has to redraw it, so the buttons are correct in the plant store. If I'm right, then setting the toy store to inactive when the user clicks on the plant store button (and vice versa) will solve the issue. Whether this is expected behavior on the prefabs part though, I'm not sure, possibly we should add a call to UpdateButtonStatus in an Update() method, I'll have to look into it. Thanks for the report!
     
    lpchow likes this.
  12. lpchow

    lpchow

    Joined:
    Nov 30, 2014
    Posts:
    19
    yes

    no. if both stores are set to active on game first initialise, unity seems to take it that plant store is the first to initialise (cuz plant store is below toy store in the hierarchy), so these will happen:
    - if spend all money in toy store, the plant store buttons will still be enabled.
    - if spend all money in plant store, the toy store buttons will be properly disabled.

    no, i set toy store to inactive when player click to plant store. i set plant store to inactive when player click to toy store. similar to your suggestion i think.
    upload_2020-7-25_20-27-19.png
    upload_2020-7-25_20-27-33.png

    my guess is that -> if i spend all my money in the second-to-initialise store, the first-to-initialise store 's purchase buttons wont be updated correctly. anyway im still in the prototyping phase, so this is not very crucial for me yet. i could do further testing when i have some down time. i will leave this as it is first.

    np! thanks once again!
     
    erika_d likes this.
  13. erika_d

    erika_d

    Joined:
    Jan 20, 2016
    Posts:
    413
    Ok thanks for the added clarification! I'll see if I can duplicate this in my test project and get to the bottom of it!
     
    lpchow likes this.
  14. erika_d

    erika_d

    Joined:
    Jan 20, 2016
    Posts:
    413
    lpchow likes this.