Trending
Opinion: How will Project 2025 impact game developers?
The Heritage Foundation's manifesto for the possible next administration could do great harm to many, including large portions of the game development community.
This article describes the problem of platform and different framework fragmentation and how you can solve it by creating common interfaces, that will allow you to use Ads and In-app purchases using the same code for every platform/framework.
As one of the developers of Gideros - cross platform mobile game development tool, my everyday task consist of incorporate different features of different platforms into our development eco system.
As Gideros offers plugin system for extending available features, it is quite easy to add almost any native functionality of each supported platform, but.
Developers do not come to cross platform tool to write platform specific plugins
There such a huge varriaty of possible plugins, there are tons of possible Advertising platforms, not even talking about other plugin options
So the problem was, how to satisfy the needs of the most clients, by making it easier for them to integrate Ads, In-app purchases and other platform specific libraries and without having to write and bind to our system every one of them.
And the idea was to create one single plugin for each type of commonly needed library, which would function as an interface and could work with any library wrapped underneath.
What it accomplishes:
It speeds up plugin development - you can reuse the same binding code, only wrapping library's functions under same interface
It speeds up code integration for developers - they are getting familiar only with one single Ads Interface, for example, and integrate it in their every app
It allows to easily switch between libraries - developers can basically reuse same code for every Ads library which is under provided interface
So the first one we approached was the Ads Interface. While it was quite easy to come up with the interface itself, the implementation proved quite difficult, due to huge difference of the native implementations. For example, some Ads behaved like standard Android Views, other held some internal objects and tried to get atteched to the ViewGroup somehow internally. Ones instantiated immediately, while others waited for a response from server, and only then could be added, positioned and displayed.
The resulting interface we have looks something like that, and covers most of the developer needs:
Ads.new("frameworkname") --contructor
Ads:setKey() --provide library specific keys
Ads:showAd(adType) --display specific ad
Ads:hideAd() --hide ad
Ads:enableTesting() --enable test ads if available
Event.AD_RECEIVED --ad received and is displayed
Event.AD_FAILED -- failed to display ad
Event.AD_ACTION_BEGIN --user began action on ad
Event.AD_ACTION_END --user action ended
Event.AD_DISMISSED --ad was removed from the screen
Event.AD_ERROR --there was an error (incorrect set up, etc)
Implementing common In-App interface was much more difficult. Mostly because the workflows between different libraries, were completely different. But there were also other problems that should be considered.
Product identifiers for different stores could be different, so you would have to match them up into one single set of internal product Ids.
Detecting which In-app purchases are available on the phone, where the app is installed. This really differs from market to market, but on Android it mostly end with checking if app with specific package is installed, to check if it would support a specific market.
Main difference for Google Billing was the fact, that you had to knew all your product identifiers before you could do anything with them. Additionally you would need to consume the consumable products, while leaving entitlements.
Main difference for IOS StoreKit, was that every purchase had to be confirmed.
In ouya's worflow there are no confirmations of the purchase event received by the app, thus it brings to a situation, where the purchase was processed by the server, but the app was not notified of it. So you must remember pending purchases and recheck them by trying to restore them form time to time.
The most annoying thing with Amazon In-app purchases was the pagination of results, so when you call for restoring the purchases, they give you bunch of results, but that might not be all of them, you need to check if there is more and request new page with the current offset.
Additionally Amazon does not provide receipt ID, so you must implement it on your own (or use some 3rd party server) to verify restored purchases, etc. And don't forget this ID should be device independent and only link to user, so user could restore the purchase from his account on any device.
And it seems that upon restoring Amazon returns all purchases, not only entitlements, but also consumables.
To cover all frameworks, we required the user to provide the array (well actually table, since it is Lua) of product Ids, by using keys as app internal product Ids, and also provide which of them are consumables. Of crouse we could leave consumption and confirmation to end developer, but it seemed too appealing to cover all that internally in the interface and provide a small clean API for in-app purchases.
So the end united workflow for all mentioned In-app libraries looks like this:
require "iab"
local iaps = IAB.detectStores()
iab = nil
if iaps[1] == "google" then
iab = IAB.new(iaps[1])
iab:setUp("google-key")
--using google product identifiers
iab:setProducts({p1 = "googleprod1", p2 = "googleprod2", p3 = "googleprod3"})
elseif iaps[1] == "amazon" then
iab = IAB.new(iaps[1])
--using amazon product identifiers
iab:setProducts({p1 = "amazonprod1", p2 = "amazonprod2", p3 = "amazonprod3"})
elseif iaps[1] == "ios" then
iab = IAB.new(iaps[1])
--using ios product identifiers
iab:setProducts({p1 = "iosprod1", p2 = "iosprod2", p3 = "iosprod3"})
end
--load previous purchases
purchases = dataSaver.loadValue("purchases")
--if there were no purchases
if not purchases then
--create and store empty table
purchases = {}
dataSaver.saveValue("purchases", purchases)
end
--if we have a supported store
if iab then
--set which products are consumables
iab:setConsumables({"p1", "p2"})
--if purchase complete
iab:addEventListener(Event.PURCHASE_COMPLETE, function(e)
--if it was not previousle purchases
if not purchases[e.receiptId] then
--save purchase
purchases[e.receiptId] = true
dataSaver.saveValue("purchases", purchases)
if (e.productId == "p1") then
--p1 was purchased
elseif (e.productId == "p2") then
--p2 was purchased
elseif (e.productId == "p3") then
--p3 was purchased
end
AlertDialog.new("Purchase Completed", "Purchase successfully completed", "Ok"):show()
end
end)
--if there was an error
iab:addEventListener(Event.PURCHASE_ERROR, function(e)
AlertDialog.new("Purchase Canceled", e.error, "Ok"):show()
end)
--call restore on each app starts
--or make a button to allow users to restore purchases
iab:restore()
end
--some where in your code to make a purchase
stage:addEventListener(Event.MOUSE_UP, function()
if iab then
iab:purchase("p1")
end
end)
Other library types that we will work on in future would include Controller Interface (Work in progress), where the most difficult part will be to map different controller device buttons to one single common mapping across all platforms
Next idea is to create leader board and achievement interface, where the most difficult task could be synchronizing achievements and stats between different frameworks.
And master class of common interfaces would be to create multiplayer support for different frameworks on different platforms under the same interface.
Read more about:
BlogsYou May Also Like