Implementing In-App Purchases with Unity3D
Today it’s a good day for some Unity3D development chatter!
During the development of our game (Mr.Walter), we came across the need to implement an In-App Purchase mechanism for the game’s iOS version. In order to do so, we tried two ways for doing this procedure: the hard way (from scratch) and the easy way (using Prime31’s plug-in).
Before we move on to the proper discussion of both approaches, we first must introduce (very quickly) an important Unity3D feature: native component call. Long story short, you can develop components (from a simple class, to a whole library or even a full feature) on java or objective-c using your favorite IDE/SDK and Unity will let you call it’s methods inside its own c# code, thus enabling a nifty degree of native platform communication/integration. This in turn is instrumental to enabling InAppPurchases on your game, since Apple has its own libraries and “pipeline” for the process and you must implement the whole thing in object-c.
So, your basic architecture for In-App Purchases will have (on a very high level) a object-c class/library that establishes communication with the appstore, requests product information (single or as a list), performs the purchase, cancels the purchase, provides an invoice, etc. and C#/Unity component (or library) that is able to trigger the before mentioned operations (upon user interaction with the game), process operation results and interact with any required interface elements (such as wait cursors, message boxes, shopping carts, store fronts, etc.). On top of this you’ll also need to develop/create your own store front and related mechanisms, Apple’s In-App Purchase library must be considered as a supped up data-provider and nothing more.
Prerequisites:
Like all Apple-related development, this too has some burocracy attached in the shape of prerequistes that you need to fill before you can start the development proper. The basic process is as follows:
1-Setup the “purchasable” product(s) on ITunes Connect
2-Generate the required provisioning profiles (for In-App Purchases)
3-Setup Test-Users for the Appstore Sandbox on ITunes Connect
These three procedures consist on a rather detailed and lengthy check-list and they are bit out of this article’s scope. However you can easily find tutorials/guides for this step online and since we are such nice fellows here at WCFM, we will pony up some links:
In-App Purchases Setup and Testing, iDevGames.com
In-App Purchases for Developers, Apple.com
Troy Bryant’s awesome In-App Tutorial
These should get you going through all the burocratic aspects of the process.
Pro-Tip: When you setup a test-user NEVER EVER try to use it to access the “real” appstore. First you will be asked to fill in the full user information form (address, add payment method, etc.) and then your test-user will never work properly with Apple’s Appstore Sandbox.
Instead, when testing go to your device’s settings, access the iTunes Store/Appstore section and logoff any account that is logged in. Only then you can move to test your app and In-App Purchases. Inside the app, you will be prompted to login on the sandbox whenever In-App is activated, and that’s where you’ll insert your test user’s credentials. Don’t forget to logoff the test user when you are done!
Now that the prerequisites are out of the way, we can start getting some dev done! Like we said on the beginning, there are two ways to do this: the easy way and the hard way. We will start by the hard way.
In-App Purchases Development: the hard way
In a nutshell under this approach, one must start by writing all the required objective-c code to access the appstore and get product lists, product details, handle purchases, returns, invoices, cancelations, etc., etc. This translates into a set of classes that, depending on your needs, may as well be a full library. You must add Apple’s StoreKit library to your Xcode project and do the rest by hand.
When that is done, you will need to “expose” the key methods for Unity to access, using the facade architecture pattern may be a good thing at this point. After the whole objective-c section is done, you may move on to Unity’s C# and implement all the game-related In-App purchase mechanics, followed by the classes/libraries that will access your objective-c In-App Purchase components and call the methods you previously exposed.
For our specific needs on Mr. Walter, we knew beforehand that In-App Purchases would only require the most basic of settings, even so we did the full objective-c implementation, similar to the one described on any tutorial you’ll find on the web. Since this is a rather common need for iOS developers and the solution is pretty well documented over the web, we will spare our readers the sight of huge blocks of objective-c code, adding it wouldn’t bring anything new to this article.
Some example implementations similar to ours can be found here:
Troy Bryant’s awesome In-App Tutorial
Step by step on iOS 6 at Ray Wenderlich.com
Nice tutorial at Nix solutions.com
You can find many more if you Google it up a bit, it’s a quite common procedure.
When we started this component on Mr.Walter, the objective-c was “easy” to implement, the C# part however would require some serious software engineering and it would take time to get done properly.
Overall we are looking at quite an amount of dev/debug hours, especially if it’s your first time doing this. The StoreKit/Apple bit has quite a number of hoops you’ll need to jump before you can get it to work flawlessly, dealing with them will take time and demand careful documentation of its several quirks.
The C# (Unity) would be done from scratch and would follow the usual project pipeline of something that is built from the ground up (iterations, testing, corrections, etc.). We would start by the most basic features, such as “Get All the Products”, “Get Product Detail”, “Purchase Product” and build over them to reach the more complex aspects.
However while we believe it’s important to know how things work, specially byzantine processes such as In-App Purchases, we must keep in mind that it will take some time (and money) to tame the process and become efficient in handling it.
Using the “Hard way” approach is mandatory if you are going for native iOS-dev and it might be an option if you require a high level of control/customization over your In-App Purchase operations. Since we are discussing a Unity implementation the first case is irrelevant, as for the second case ,one must be careful when assessing the requirements because implementing such a mechanism from scratch can be expensive and the end result may even be inferior to the one provided by the “Easy way”.
After we finished the “basic” In-App Purchase mechanism, we had our objective-c component, the interface logic (unity) and a C# wrapper that would access objective-c bit and relay all the data to a “View-Model” class where the data is sent to the UI controls for display. In between there is a ton of verifications and validations that must be enforced to make sure operations can be started, that can be (or are) canceled and that they are indeed finished. Not to mention spot-on error checking and handling, laxitude may cost us dearly on type of mechanisms.
Having covered “Get All the Products”, “Get Product Detail”, “Purchase Product” and we couldn’t help to feel like we were re-inventing the wheel…the hard way.
After some research we found out we were right! Enter the “Easy way”.
In-App Purchases Development: the easy way
Like almost all things Unity, this too has a plug-in! After some thorough research and testing we found out that someone had already invented this particular wheel and boy it rolled great.
On our travels through the web we came across Prime31’s iOS In-App Purchase Unity Plug-in. After some analysis, some email exchanges and some sneak-peaks to its implementation we concluded that it did much more than our own homegrown library (obviously) and much better too. Its features covered all we needed for Mr. Walter and everything we will ever need regarding In-App Purchases on current and future projects.
When you use this plug-in the focus is placed on Unity, Prime31 skillfully abstracts the objective-c bit from the process, leaving you to deal only with a set of C# libraries that handle the communication/event management part of the process. You don’t see or handle any objective-c code what so ever, only the C# you need to develop your project/features.
Keep in mind that although this nice plug-in makes the process much simpler, lighter and faster, one still needs to be a developer (or have C# skills) to get In-App Purchases to work under Unity, it’s not (at least yet) a drag-and-drop procedure.
Prime31’s plug-in can be divided into three main sections:
1-Unity Prefabs: there are two of them and they are mandatory for the plug-in to work. They are StoreKitEventListener and StoreKitManager
2-Operation objects: there three of them (two are attached to the prefabs) and they are comprised of an objective-c wrapper, an event manager and an event listener. They are StoreKitEventListener, StoreKitManager and StoreKitBinding (wrapper)
3-Communications objects: there are two of them and they basically process the Appstore’s responses to the requests that are made by the game. One handles product information, the other handles transaction information. They are StoreKitProduct and StoreKitTransaction
There’s also a test-scene with all the elements properly implemented, so that one can have an example of a standard implementation of the plugin’s features.
All of these items can be found under a StoreKit folder, inside Unity’s default “Plugins” folder:
This is the set of items you can manipulate or customize (editing the code), so these are the ones that matter for implementing our In-App Purchase logic and interaction.
Some more details about the .cs files:
StoreKitProduct.cs: defines a product type (fields), extracts product info from a JSON string that is returned by the wrapper and fills in those fields on the object.
StoreKitTransaction.cs: defines a transaction type (fields), extracts transaction info from a JSON string that is returned by the wrapper and fills in those fields on the object.
StoreKitBinding.cs: is a wrapper that handles all the calls and responses to the native (iOS) store components.
StoreKitManager.cs: defines all the required events for In-App Purchases, all state changes of a given transaction and error handling within the transaction itself.
StoreKitEventListener.cs: defines all the application logic to apply during a given transaction, triggers transaction state changes, handles transaction events.
During our work on Mr. Walter there was no need to change StoreKitProduct.cs and StoreKitTransaction.cs, since we didn’t need to change any types or the way the responses were processed. The same happened with StoreKitBinding.cs, since we didn’t need to change the wrapper to pull any additional data from the native code nor we needed to change any states on StoreKitManager.cs.
We believe that most In-App Purchases scenarios won’t require changes to those objects either, since they offer a great deal of functionality out-of-the-box that covers most needs. However we did have to change StoreKitEventListener in order to implement our specific logic for both purchases and state changes.
So, now that we have covered how Prime31’s plug-in works and what elements it is comprised of, it’s time to demonstrate how one can implement it.
First we must set up our Unity scene. For this example we will be using some elements from Mr. Walter:
As you can see, on our scene we have the two mandatory prefabs from the plug-in. We also added a background, the purchase sign, a wait cursor and a couple of text boxes to display the product’s price on the UI.
Next we need to setup a small pipeline that articulates interface instructions (detect taps for instance) with the plug-in itself. In between we also have to handle “visualizable” state changes, such as wait cursors and message boxes. To that effect, we’ve started by creating a small “sceneManager” script that is attached to the Main Camera and takes care of all the interface stuff:
public class IAPSceneManager : MonoBehaviour { //sub-wrapper to handle all plugin calls IAPWrapper iapWrapper; //interface objs GameObject iapBg; GameObject iapBuy; GameObject mainBg; //Iap Form Controls GameObject IAPFormPrice; GameObject IAPFormPriceShadow; //raycasts to detect taps RaycastHit[] hits; Ray ray; //state changes private bool IAPButtonHit=false; public int stateIAPTransaction {get;set;} void Start () { //fetch everything that's on the UI iapBg = GameObject.Find("MenuIAPBg") as GameObject; iapBuy=GameObject.Find("MenuIAPBuy") as GameObject; IAPFormPrice=GameObject.Find("IAPFormPriceLbl") as GameObject; IAPFormPriceShadow=GameObject.Find("IAPFormPriceLbl_shadow") as GameObject; mainBg = GameObject.Find("MenuBg") as GameObject; doIap(); } //check for screen taps void FixedUpdate () { processTap(); } //handle screen taps private void processTap() { IAPButtonHit=false; for (int i=0; i<Input.touchCount; i++) { if (Input.GetTouch(i).phase.Equals(TouchPhase.Began)) { // BUY ray = Camera.main.ScreenPointToRay(Input.GetTouch(i).position); hits = Physics.RaycastAll(ray); for(int j=0; j<hits.Length ; j++) { if(hits[j].collider.gameObject.name == "MenuIAPBuy") { IAPButtonHit=true; doIapBuy(); return; } } if(!IAPButtonHit) { doBack(); return; } } } } private void doIap() { //When the splash gets loaded, an access is made to the appstore to fetch the product's price if(iapWrapper == null) { iapWrapper = new IAPWrapper(); } iapWrapper.getProductData("IAP"); } private void doIapBuy() { if(iapWrapper == null) { iapWrapper = new IAPWrapper(); } iapWrapper.buy("IAP"); Debug.Log("BUYING"); } private void hideIAP() { setIAP(false); } public void showIAP() { setIAP(true); } private void setIAP(bool enabled) { iapBg.renderer.enabled = enabled; iapBuy.renderer.enabled = enabled; IAPFormPrice.guiText.enabled=enabled; IAPFormPriceShadow.guiText.enabled=enabled; } }
Basically it checks for taps over the “buy product sign”, if a tap is detected there, the plug-in is called to perform a transaction. Also when the screen loads, it accesses the plug-in to fetch the product data from the appstore. On this example we only have one product, but the procedure would be the same for a given n products. Besides this, our code also shows/hides the price tags for the product.
Now that we have interface logic, the next step is to have a sub-wrapper between this class and the plug-in. You may be wondering why should we add yet another layer of complexity, well the answer lies on the Mediator pattern. We decouple the UI code from the plug-in in order to gain flexibility over the whole interaction.
Let’s suppose we do several calls to the plug-in from different screens on our application (it happens on Mr. Walter) and that the nature of those calls varies from screen to screen, that you need to do specific operations before the call of the plug-in proper or even that you perform different operations depending on which screen you are. Having a “mediator” in the middle enables you to manage all those scenarios without clogging up the UI code.
Let’s also suppose you are building your game for multiple platforms (android/iOS) and on top of the before mentioned operations you want your code to be clever enough to know when to call the iOS plug-in or the Android plug-in. Basically with a mediator the UI classes don’t have to concern themselves with such issues, you abstract the destination of the call from the UI and let the middle class handle it. It also simplifies the task of handling profound changes to the “In-App Engine”, since it’s only called on one place and you know exactly where.
For this example we’ve set up a small mediator class class called “IAPWrapper” that is a lightweight version of the one used on Mr. Walter. For the sake of simplicity, we’ve placed some dummy methods on the android sections:
public class IAPWrapper : IDisposable { //get product detail public void getProductData(string screen) { //check if android or ios #if (UNITY_ANDROID) if(obj_Activity == null) init(); obj_Activity.Call("buy"); #elif (UNITY_IPHONE) //current screen Config.CurrentIAPLocation=screen; //show the wait cursor GameObject.Find("IAPFormWaitCursor").GetComponent().showWaitCursor(); //set the product identifier var productIdentifiers=new string[]{"com.wecamefrommars.demo.IAP"}; //get data from appstore StoreKitBinding.requestProductData( productIdentifiers ); #endif } //prepare to do the purchase public void buy(string screen) { #if (UNITY_ANDROID) if(obj_Activity == null) init(); obj_Activity.Call("buy"); #elif (UNITY_IPHONE) //current screen Config.CurrentIAPLocation=screen; //show wait cursor GameObject.Find("IAPFormWaitCursor").GetComponent().showWaitCursor(); //if my current transaction state isn't IN_Progress or Canceled, set the new state and try to buy if(GameObject.FindWithTag("MainCamera").GetComponent().stateIAPTransaction<IN_PROGRESS && GameObject.FindWithTag("MainCamera").GetComponent().stateIAPTransaction>CANCELED) { Debug.Log("Will try to buy"); GameObject.FindWithTag("MainCamera").GetComponent().stateIAPTransaction=IN_PROGRESS; DoBuy(); } #endif } //execute the purchase private void DoBuy() { //build a proper product list List products=new List(); //if the list has items if(GameObject.Find("StoreKitEventListener").GetComponent().AllProducts.Count>0) { products=GameObject.Find("StoreKitEventListener").GetComponent().AllProducts; //since we only have one product, get the first one var SelectedProduct=products[0]; Debug.Log( "preparing to purchase product: " + SelectedProduct.productIdentifier ); StoreKitBinding.purchaseProduct( SelectedProduct.productIdentifier, 1 ); } else { Debug.Log("no product"); GameObject.Find("IAPFormWaitCursor").GetComponent().hideWaitCursor(); } } public void Dispose() { #if UNITY_ANDROID if(obj_Activity != null) obj_Activity.Dispose(); #endif } //As an added bonus, we are importing a objetive-c class that shows the native message boxes from iOS [DllImport ("__Internal")] private static extern void ShowMessageBox(string messageType); public static void ShowBox(string boxType) { #if (UNITY_ANDROID) #elif (UNITY_IPHONE) ShowMessageBox(boxType); #endif } }
As you can see, a mediator can be pretty useful. We got ours to run different operations depending if we are on android or iOS (without bothering the UI code), we got it to access the plug-in for data, to show the wait cursor and even to call code to show iOS’s native message boxes that notify the user of different transaction states/transaction progress.
Finally after our mediator we have the plugin’s event listener (StoreKitEventListener.cs), where we added some event-specific code to fetch useful information, such as product price, do interface changes such as setting text box values, showing/hiding the wait cursor and more important above all, set transaction states. These operations are triggered according to the transaction event that is raised. A full example can be seen below:
using UnityEngine; using System.Collections; using System.Collections.Generic; public class StoreKitEventListener : MonoBehaviour { GameObject IAPFormPrice; GameObject IAPFormPriceShadow; GUIText IapText; GUIText IapShadowText; public List AllProducts {get;set;} //#if UNITY_IPHONE void OnEnable() { // Listens to all the StoreKit events. All event listeners MUST be removed before this object is disposed! StoreKitManager.purchaseSuccessful += purchaseSuccessful; StoreKitManager.purchaseCancelled += purchaseCancelled; StoreKitManager.purchaseFailed += purchaseFailed; StoreKitManager.receiptValidationFailed += receiptValidationFailed; StoreKitManager.receiptValidationRawResponseReceived += receiptValidationRawResponseReceived; StoreKitManager.receiptValidationSuccessful += receiptValidationSuccessful; StoreKitManager.productListReceived += productListReceived; StoreKitManager.productListRequestFailed += productListRequestFailed; StoreKitManager.restoreTransactionsFailed += restoreTransactionsFailed; StoreKitManager.restoreTransactionsFinished += restoreTransactionsFinished; //get UI controls IAPFormPrice=GameObject.Find("IAPFormPriceLbl") as GameObject; IAPFormPriceShadow=GameObject.Find("IAPFormPriceLbl_shadow") as GameObject; IapText=IAPFormPrice.GetComponent(); IapShadowText=IAPFormPriceShadow.GetComponent(); } void OnDisable() { // Remove all the event handlers StoreKitManager.purchaseSuccessful -= purchaseSuccessful; StoreKitManager.purchaseCancelled -= purchaseCancelled; StoreKitManager.purchaseFailed -= purchaseFailed; StoreKitManager.receiptValidationFailed -= receiptValidationFailed; StoreKitManager.receiptValidationRawResponseReceived -= receiptValidationRawResponseReceived; StoreKitManager.receiptValidationSuccessful -= receiptValidationSuccessful; StoreKitManager.productListReceived -= productListReceived; StoreKitManager.productListRequestFailed -= productListRequestFailed; StoreKitManager.restoreTransactionsFailed -= restoreTransactionsFailed; StoreKitManager.restoreTransactionsFinished -= restoreTransactionsFinished; } void productListReceived( List productList) { Debug.Log( "total productsReceived: " + productList.Count ); AllProducts=productList; // Do something more useful with the products than printing them to the console foreach( StoreKitProduct product in productList ) { string prices=product.price + " "+ product.currencySymbol; IAPFormPrice.guiText.text=prices; IAPFormPriceShadow.guiText.text=prices; Debug.Log("Price: " +prices); } GameObject.FindWithTag("MainCamera").GetComponent().showIAP(); GameObject.Find("IAPFormWaitCursor").GetComponent().hideWaitCursor(); if(productList.Count>0) GameObject.FindWithTag("MainCamera").GetComponent().stateIAPTransaction=2; } void productListRequestFailed( string error ) { Debug.Log( "productListRequestFailed: " + error ); } void receiptValidationSuccessful() { Debug.Log( "receipt validation successful" ); } void receiptValidationFailed( string error ) { Debug.Log( "receipt validation failed with error: " + error ); } void receiptValidationRawResponseReceived( string response ) { Debug.Log( "receipt validation raw response: " + response ); } void purchaseFailed( string error ) { HandleTransactionEnd(1,"FAILED"); Debug.Log( "purchase failed with error: " + error ); } void purchaseCancelled( string error ) { HandleTransactionEnd(1,"CANCELED"); Debug.Log( "purchase cancelled with error: " + error ); } void purchaseSuccessful( StoreKitTransaction transaction ) { Config.setIAPState(false); HandleTransactionEnd(4,"SUCCESS"); Debug.Log( "purchased product: " + transaction ); } void restoreTransactionsFailed( string error ) { Debug.Log( "restoreTransactionsFailed: " + error ); } void restoreTransactionsFinished() { Debug.Log( "restoreTransactionsFinished" ); }
//handle transaction end actions private void HandleTransactionEnd(int IAPState, string MessageBox) { GameObject.Find("IAPFormWaitCursor").GetComponent().CheckAndHideWaitCursor(); if(Config.CurrentIAPLocation=="Main") { MainMenuManager m = GameObject.FindWithTag("MainCamera").GetComponent(); m.doBack(); m.doBack(); switch(MessageBox) { case "SUCCESS": m.doBack(); m.doBack(); m.SetIAPSetting(); break; } IAPWrapper.ShowBox(MessageBox); m.stateIAPTransaction=IAPState; } else { IAPSceneManager i=GameObject.FindWithTag("MainCamera").GetComponent(); IAPWrapper.ShowBox(MessageBox); i.stateIAPTransaction=IAPState; if(MessageBox=="SUCCESS") i.LoadNextLevel(); else i.doBack(); } } }
This is pretty much the “stock” StoreKitEventListener.cs class from the plug-in, only with the desired actions on the proper places. On this example we are setting values on the UI, managing wait cursors, setting states and even handling transaction end actions depending on the screen from which In-App is called.
During the whole process you also must handle trivial situations such as Network/Internet connection failure and Appstore response/connection timeouts. While for the first you can use Unity’s methods to check such situations, for the second Prime31’s plug-in already has a ProductListRequestFailed event that should suffice to check if something is wrong with your connection to the appstore.
On most Prime31 StoreKit plug-in implementations, changing/add instructions to this “skeleton” class, combined with the proper architectural approach, should suffice to your needs.
By this point you should have a fully working In-App Purchases pipeline on your Unity app/game.
Further Considerations:
One important aspect to consider during development is:
When to load my In-App Purchase data on the game/app? More specifically, when should I access the appstore and populate my storefront? Is it better do it as soon as the game loads or “on demand” when the user accesses the store?
This is a bit of an open-ended question, since on the end it truly depends on a few factors, such as the amount of items to load, how dynamic is your inventory and how many times per session will the user access the store.
For the sake of discussion, let’s play with some possible scenarios to help us highlight the issue.
Case 1: Freemium Game w/ purchasable consumable goods (coins, game items, powerups), frequent updates
On this scenario, we will most likely have a wide array of consumable goods, for instance coin packs (10 coins, 100 coins, 1000 coins), innumerable game items separated maybe by category (clothes, armor, weapons, furniture, etc.) and also innumerable powerups.
Besides the amount of items, this type of inventory should be dynamic so that you don’t need to update the game when you add or change a product. So it will be fetched from the iTunes store instead of being “hardcoded” into the game.
Finally, it needs to load seamlessly and swiftly as soon as you open it on the game, since users will want to access it several times per gaming session, you’ll want them to go in, purchase and get back to playing quickly so that they can spend it all and come back to buy some more. Also it’s not a clever move to let customers waiting, especially when they are still deciding if they will make that first purchase that as we all know, is the key to hooking players into the habit of buying stuff on your game.
To this we must add the fact that internet connections some time fail and that the appstore is a total slowpoke when responding to app requests.
On this scenario, our approach would be to fill the store on the game’s initial load. Sure it adds up to that “Loading” splash screen, but it only happens once and your users are free to have a nice, seamless experience using the store, you can pull everything from the appstore and feed it to your interface. No unnecessary waits, no fuss.
Of course that inventory updates will only reflect when the user re-starts the game, but this isn’t an issue (unless your products behave like the stock-market!) since you can add code to check for new stuff if it’s really that vital.
Case 2: Freemium Game w/ purchasable DLCs (levels, avatars), infrequent updates.
This example is very similar to the purchases scenario we had on Mr.Walter.
On this scenario we will have a short amount of inventory, mostly composed by downloadable content that may or may not require a full game update to get the assets in. Also these products won’t be released ever so often (unless you decide to publish a new level every week); in most cases they are even marketed in packs of x items, so the storefront doesn’t have to be fully dynamic.
Finally this type of content doesn’t require frequent visits to the store. So users will only go to check for new stuff (once in a while) or if you tell them beforehand that new dlc’s are available.
For this scenario the In-App approach can be different. You won’t have a lot of content and it won’t change for a long time, so can avoid loading the inventory when the game starts, thus speeding up the process a bit. Also, since inventory doesn’t change for weeks or months, you can get away with having the storefront fully “hardcoded” on the game, without having to connect to get product data. Most times new content will mean new assets, that in turn mean a game update where you get the opportunity to change the “hardcoded” storefront, so it doesn’t really matter if it’s dynamic or not.
Basically you can make it work nicely just by mapping the UI elements of the storefront to the product Id’s on the appstore and let the plug-in handle the rest. The main advantage of this approach is a reduction of loading times and having almost an asynchronous storefront that will display inventory without having to connect or load, integrating seamlessly with the rest of the game and working smoothly, which adds points for user experience.
On Walter we went for a mixed approach. Like all things, software architecture requires a good dose of sense; it isn’t just about following path A or path B to the letter, but to find the best solution for the problem.
In our case the requirements fit scenario nº2, so we have a fully hardcoded storefront with the product controls mapped to Appstore product Ids and nothing was dynamic…except for the price. For strategic reasons, dlc prices for the game may change over time, to accommodate this change without having to publish an update every time, the price tags, and only the price tags on the storefront are dynamic.
This of course translates on a small loading time when you open the storefront, but since that is an action that will occur very few times per user and with a small impact on overall usability, we decided that it would be better to make the game start/load faster and have a small loading time for purchases than the other way around. Also it was simpler and faster do implement it this way, being the other option a bit of overkill.
Whenever you are implementing this is type of mechanism try to get a really clear picture of the whole strategy for dlc/consumable sales, because they do have a strong impact over the way the store and sales pipeline gets implemented on the game.
About the plug-in
Prime31’s StoreKit iOS plug-in is a must-have for any Unity game/app that features In-App Purchases. It will make your development easier, your life simpler and your code cleaner. There’s nothing bad to say, it “operates as described”, it’s expansible (up to a point), customizable (up to a point) and it works great!
We can however leave some remarks for the “organization/company” behind the plug-in:
First a “product detail page” would be nice, instead of a list of plugins and price tags with a small description. What if I want to know more about a specific product, without having to sweep over the Unity forums or mailing prime’s support email address? A page with the product’s information would help and even save everyone some time.
Then using such a page it would also be possible to aggregate a couple of examples on how to implement the plug-in, again without having to read endless posts on Unity Forums or nagging Prime’s support (again).
And finally, we applaud the new support forum, it is a good and productive solution for tech support and to mitigate some of the before mentioned shortcomings.
So, we hope you found this article productive and that it may shed some light over the subject matter.
Your humble minion Rui Guedes.
( Follow Rui on Twitter )