In app purchases in GameMaker: Studio have been set up in such a way as to be store independent as much as possible. This means that the same general code-base should work on all target platforms with the minimal of changes, so the general outline given here is more or less the same for all target modules that support in app purchases with the code shown in the examples and the events that they trigger being identical for all.
Before continuing, you should have a project ready for testing IAPs, and if you are wanting to create IAPs for an Android project then you will also need to have installed the Google Play Services Extension, along with the Google Play Services IAP extension. These can be installed from the Social tab of the Global Game Settings (simply check the box beside the two options to get them from the YoYo Games Marketplace).
Setting Up The Store
Since testing your app in real-time is essential for IAP to work correctly, it is recommended that you upload a version of your game to the target Store as soon as possible for testing. You can even make a specific "test app" as long as you make sure you use the same package URL or name as you intend your "real" game to have (ie: com.MacSweeney Games.CatchTheHaggis), or the in app purchases won't be found by your final game later. The full testing process would go something like this:
- Go to the Global Game Settings and take note of your app id for the target platform (ie: For Android, it would be something like "com.MacSweeney Games.CatchTheHaggis"). Note that on Google Play, you have to pay attention to the release version as it automatically increments each time you compile an .apk (although this behaviour can be switched off in the Preferences for GameMaker: Studio) and Google requires that each new .apk should have a higher version number than the one currently uploaded, or it will reject the new *.apk.
- Now go to the Global Game Settings - In App Purchases Tab and enable in app purchases. You may need to add additional information depending on the target platform, like adding your Android public key for Google Play.
- Upload the test app (with In App Purchasing enabled) to the store.
- Create the product information for each available purchase on the stores. Be sure to give each of your IAP products a unique product id. This can be anything, but whatever you decide, try and keep it consistent throughout your apps, as it will make tracking purchases and adding them in later much simpler. For example, you can use the format appname_product for each of them, like "catchthehaggis_noads".
NOTE: When creating apps for MacOSX and iOS, you cannot use the same product name for both platforms. So, you may have to have something like "catchthehaggis_mac_noads" and "catchthehaggis_noads" for your product names to differentiate. You will then need to handle this discrepancy within the IAP code (this is shown in the example below).
- Ensure that the billing information for your target store account is filled in and has been tested and approved by the store, otherwise you can't proceed to use In App Purchases.
It is worth remembering that you can't test some IAP's using the YoYo Runner. So you may have to create the final executable of your game (using the Create Executable option from the IDE) and test on a device instead.
Coding In App Purchases
There are two basic types of in app purchase: Consumables, and Non-Consumables (also called "Durable" purchases), with the fundamental difference between the two being that the first one can be bought multiple times, while the second can only be bought once. However, coding for either type of purchase is handled in the same way, and both will be shown here.
Overview
The way that GameMaker: Studio deals with IAP is event driven, and most of it will be handled by the asynchronous IAP Event. The general workflow is as follows:
- You check for a secure stored purchase map and load it if found, otherwise you create a new one and save it.
- You then activate all purchases, which will trigger an IAP Event where you can query the status of the available products.
- In your game you have objects that connect to the store to request a product on user interaction, which also triggers an IAP event where you can deal with the purchase (or failure thereof).
- The IAP event is parsed to deal with the purchase, taking all data from a special ds_map iap_data.
- If successful, you write the purchase to your purchase map and secure save it, then (if the purchase is non-consumable) activate the product that the user has bought.
- If the purchase is consumable, you can use it at any point in the game, which will trigger another IAP event informing you of the consumption in which case you would update the purchase map and secure save again.
Initialise Your IAPS
The following code is an example of how you would typically initiate in app purchases and this would normally only be done once at the start of a game, either in a dedicated start up script or object, or in the Game Start event of the first object in your game.
The first thing required from us, is to create a purchase map for tracking purchases between runs of the game. GameMaker: Studio does not perpetuate purchase information automatically, so you will have to do this yourself. Creating this map has the added benefit of permitting you to check it directly for purchases even when the target store is offline (as you will see in the code below).
In our example code, the consumable will simply be "gold", with each purchase adding 1000 "gold" to a global variable, and our non-consumable will be to get rid of in app advertising. So, our purchase map will hold two product keys, and they will have an initial value of 0 (false). It should look something like this:
var map_create = true;
if file_exists("iap_data.json")
{
global.purchaseMap = ds_map_secure_load("iap_data.json");
if ds_exists(global.purchaseMap, ds_type_map)
{
if os_type == os_macosx
{
var product = "catchthehaggis_mac_noads";
}
else
{
var product = "catchthehaggis_noads";
}
if ds_map_exists(global.purchaseMap, product)
{
map_create = false;
if ds_map_find_value(global.purchaseMap, product) == 0
{
ads_enable(display_get_gui_width() / 2, 0, 0);
}
}
}
}
if map_create
{
global.purchaseMap = ds_map_create();
if os_type == os_macosx
{
var product1 = "catchthehaggis_mac_noads";
var product2 = "catchthehaggis_mac_gold";
}
else
{
var product1 = "catchthehaggis_noads";
var product2 = "catchthehaggis_gold";
}
ds_map_add(global.purchaseMap, product1, 0);
ds_map_add(global.purchaseMap, product2, 0);
ds_map_secure_save(global.purchaseMap, "iap_data.json");
}
As you can see, we first check to see if there is a file with saved purchase data, and if there is we parse it for the non-consumable "catchthehaggis_noads" product and then if that has not been bought we enable ads. Notice that if the file is not found, or the file data is corrupted in some way, then we create a new purchase map, initialise the products as not being bought, and then secure save that.
With that done, the next thing to do is to set up the purchase data itself and connect the game to the target store. All this is done using the iap_activate function, as shown below:
var pNoAds = ds_map_create();
var productList = ds_list_create();
if os_type == os_macosx
{
ds_map_add(pNoAds, "id", "catchthehaggis_mac_noads");
}
else ds_map_add(pNoAds, "id", "noads");
ds_map_add(pNoAds, "title", "No Ads");
ds_map_add(pNoAds, "type", "Durable");
pGold = ds_map_create();
if os_type == os_macosx
{
ds_map_add(pGold, "id", "catchthehaggis_mac_gold");
}
else ds_map_add(pGold, "id", "catchthehaggis_gold");
ds_map_add(pGold, "title", "1000 Gold!");
ds_map_add(pGold, "type", "Consumable");
ds_list_add(productList, pNoAds);
ds_list_add(productList, pGold);
iap_activate(productList);
ds_map_destroy(pNoAds);
ds_map_destroy(pGold);
ds_list_destroy(productList);
With that, our products have been activated and each one will trigger its own IAP Event of the type "iap_ev_product", where (if you wish), you can get the full details of the purchase as they are pulled from the store. Note that we include a "type" key when activating each product, but this is only actually used for Windows 8 and WindowsPhone, so if you have no intention of selling on those platforms, you can omit it all together (but leaving it in is not a problem as it will simply be ignored).
Aquiring A Product
Within your game you will have a button instance or something similar for the user to click to purchase a product, and it's here that you would call the function iap_acquire to send off the purchase request to the target store. The actual code to do this is pretty simple, and will look something like the following:
if iap_status() == iap_status_available
{
if os_type == os_macosx
{
var product = "catchthehaggis_mac_noads";
}
else
{
var product = "catchthehaggis_noads";
}
if ds_map_find_value(global.purchaseMap, product) == 0
{
iap_acquire(product, "");
}
}
else
{
show_message_async("Store is not available.");
}
As you can see, we first check to see that the store is available, then we check to see if our game is running on a Mac OSX machine or not, then we check our previously created purchase map to see if the product has been bought or not previously. Note that this is the exact same code that you would use for a consumable purchase too, and it will trigger an IAP Event of the type "iap_ev_purchase", which will contain details of the purchase being made as well as whether it has succeeded or not.
The Asynchronous IAP Event
The IAP Event is triggered by a number of different functions, as well as changes in store status and connection/disconnection to the internet. As such it is in this event that you will need to do the bulk of the processing for your products and purchases. As mentioned before, this event creates a special ds_map with the index "iap_data" which will always hold a key "type". This key is the event type that has been triggered and will hold a built in constant which can be parsed to determine the type of event being called so you can react accordingly.
In our example we are simply going to check for the purchase and consumption of products (see the IAP Event documentation for full details), so the easiest way to do this is to create a switch that checks the "type" of event and runs only the appropriate code, something like this:
var val = ds_map_find_value(iap_data, "type");
switch (val)
{
case iap_ev_purchase:
var map = ds_map_create();
var purchase_id = ds_map_find_value(iap_data, "index");
iap_purchase_details(purchase_id, map);
if ds_map_find_value(map, "status") == iap_purchased
{
var product_id = ds_map_find_value(map, "product");
ds_map_replace(global.purchaseMap, product_id, 1);
switch(product_id)
{
case "catchthehaggis_noads":
case "catchthehaggis_mac_noads":
ads_disable(0);
break;
case "catchthehaggis_gold":
case "catchthehaggis_mac_gold":
iap_consume(product_id);
break;
}
}
ds_map_destroy(map);
break;
case iap_ev_consume:
var product_id = ds_map_find_value(iap_data, "product");
if ds_map_find_value(iap_data, "consumed")
{
ds_map_replace(global.purchaseMap, product_id, 0);
global.Gold += 1000;
}
break;
}
ds_map_secure_save(global.purchaseMap, "iap_data.json");
The above code checks the "type" of event from the iap_data map, then uses a switch to take the appropriate action, and in this case we are only checking for two possible entries: a purchase, or a consumption. If it is a purchase we get the purchase details, which will populate the ds_map we have created with all the relevant details for the purchase, and we then check the "status" key from this map to make sure that it was successful. If it was, we then check the product id to see which one has been purchased and act accordingly. For a product being consumed, we simply need to get the product id and then deal with that.
Summary
As you can see, creating In App Purchases in GameMaker: Studio is relatively simple, and one code base should work for all platforms with a minimal of changes. The important things to note are:
- Your store Product Id string must match that which you use to activate the product in your game
- You should have fail-safe code in all events to check each step of a purchase in case there are connection errors or store errors at any stage
- You cannot accumulate consumable purchases, which means that you must call iap_consume on any consumable already purchased before processing a further purchase.
- Your products and purchases are not automatically perpetuated between runs of your game by GameMaker: Studio, meaning that you are responsible for saving these details at all times.
- The IAP functionality does not include any mechanism for downloading files to your game. This is your responsibility and can easily be achieved by using the http_* functions on confirmation of a purchase. Note that there are functions for downloading files from a server which can be used, and GameMaker: Studio has a special function to unzip any zipped files that you may have(zip_unzip()), making adding level packs and other extras a viable possibility.
You can find further information on all the IAP functions in GameMaker: Studio from the manual, and there is a further article on the Knowledge Base with information on how to test your games with IAP which can be found here: http://help.yoyogames.com/entries/29736883-Testing-In-App-Purchases-GMS-v1-3-