Tip: Using GBC Language Cabinet With Larger Applications

Background

GBC Language Cabinet is my free CoronaSDK plug-in that helps simplify the addition of multi-language support in your games and applications. Using GBC Language Cabinet is very easy… define the languages you want to use, add the translation text, and then call a function to retrieve the appropriate text in a specific language.

For smaller games, it is very convenient to create a separate Lua module to create and add all the required text that you need within the game, but what about larger applications that have many scenes and/or a large amount of text? What about scenes that are only displayed one time? In these cases, one large Lua file will work, but it is possible to use multiple Lua files to help you manage the large amount of text needed for games such as this.

Modular Approach to Translation Text Managment

For example, translation files can be created per scene, or included in each scene’s create() method…

-- remove all text entries currently stored in GBC Language Cabinet
GBCLanguageCabinet.clearText()

-- load scene specific text
GBCLanguageCabinet.addLanguage("English", "en")
GBCLanguageCabinet.addLanguage("Deutsch", "de")
GBCLanguageCabinet.addLanguage("Español", "es")

GBCLanguageCabinet.addText("RedBall", {
    {"en", "That is a ##color## ##object##"},
    {"de", "Das ist eine ##color## ##object##"},
    {"es", "Esa es una ##object## ##color##"},
})

Notice that for each scene, you remove all previous text, add the language, and then add the text.  This ensures that only the scene specific text is available.  This keeps the memory footprint lower, and makes management of text easier.

In Closing

GBC Language Cabinet is flexible, so the best approach is really up to you. It depends on your game and how you would like to manage it. Using a modular approach allows you to manage translation text in cases where a large number of scenes or a large amount of text makes a single file solution difficult.

I am always interested in knowing who uses GBC Language Cabinet… show me your games! Also, if you have any suggestions for improvements, please let me know.

Post Mortem – Pumpkin Patch Match

I originally wrote this article in October of 2015, but never published it.  Since I just released an update to Pumpkin Patch Match, and since we are coming up on Halloween, I decided to make a few edits and finally release this Post Mortem.

Background

Pumpkin Patch Match bannerI wanted to create a Halloween themed game along the lines of my past two holiday releases, Tappy Holidays, and Tappy Valentines Day. Tappy Halloween, as it was going to be called, was going to be a simple game with the same game mechanics as the first two games in the series, but I just couldn’t figure out a game that would be interesting.

After a couple of weeks of trashing ideas, it came to me… make a game where you have to match a pattern of jack-o-lanterns. Shortly after, I decided to change the name to Pumpkin Patch Match since it seemed better suited for the game.

Putting It Together

I have been learning Unity throughout the first part of 2015, and I thought that this would be a good “first game”. I got pretty far, and was pretty impressed with it, but what I thought were a couple of limitations prevented me from continuing, so I went back to CoronaSDK for this app.

I put together a CoronaSDK based application in no time and I am very happy with the results. I feel it is one of my better developed apps.

Stuff I Encountered
  • CoronaSDK is great for 2D games. I built Pumpkin Patch Match rather quickly, but I’ve been trying to find a reason to move completely to Unity. I guess both have their strengths and weaknesses. I am close though, since there are a few things that are a bit limiting with Corona.
    • 2016: Looking back, I think I should of continued to use Unity… it would of been a great learning experience.
  • I decided to try Vungle for monetization… let’s see how this pans out. I hate ad based games, and I’ve been trying unsuccessfully to get out of that, but I don’t seem to have a choice. My games with In-App Purchases, or games I originally released as a pay-to-play (no ads) were unsuccessful.
    • 2016: Still not sure Vungle is the way to go.  It fits well with the game (allowing the player to continue at the same level if he/she watches a video), but I do not see the profits I expected.
  • I am also going to release on the Windows Phone platform. I have no experience there, and have no idea how this will turn out. CoronaSDK recently released a Windows Phone build.
    • 2016: I did release a Windows Phone version, but since Vungle on CoronaSDK on Windows Phone is not supported, I had to go with a $0.99 app, and as I expected, not a single purchase was made.

Space Mission: Survival – Mobile Update

A rather large update to the mobile version of Space Mission: Survival has finally been released on the Apple App StoreAmazon Store, and Google Play. After about a month of work on the update, Version 2.0 is live! Changes include:

  • Larger UI for smaller devices (phones)
  • Additional Achievements
  • For iOS, partial Achievements
  • Support for multiple languages… English, German, Spanish, Chinese, Japanese, and Klingon.  That’s right… KLINGON.  Other languages coming soon.

For those interested in the development side, SM:S is written in CoronaSDK, and has the following features:

  • The language translation features were developed using the plugin I wrote called GBC Language Cabinet.  You can use it too… it’s available on the Corona Market for FREE.
  • Persistent data is being managed by another plugin I wrote called GBC Data Cabinet. Again, another FREE plugin for Corona Developers to use.
  • I implemented IAP using a plugin called IAP Badger. A great plugin to make in-app purchase development easy. Support is great too.

May 2016 Update

I’ve been a bit quiet lately, and since this is the end of the month, I figured this is a good time for an update to go over what i have been working on.

I took a break for a couple weeks after Ludum Dare 35, and started up again in mid April.  I was all set to continue working on the update to Space Mission: Survival, currently named “SMS: The Next Mission“, but I am still in a bit of a funk deciding what I want to add to the Shmup version of the game.  The 70s and 90s version is pretty much complete, except for some tweaks. Until I figure out exactly what I want to see in the Shmup version, I decided to work on some updates to the original mobile version of SMS.

The mobile version was written back in 2014 and one of the features I coded, but did not utilize, was language support. I decided it was a good time to add language support now. Late last year, I wrote a Corona plug-in called GBC Language Cabinet that made it easy to add support for additional languages. Works like a charm… it’s also free for Corona developers. I spent a few hours adding language support to the game via the plug-in, and several days adding language support to the iOS and Android app stores in preparation for the update.

There’s a few other things I wanted to add to SMS, so I decided to keep going and make this upcoming version a massive update. Some improvements you will see, others are internal. Other obvious visible improvements are better support for smaller (ie. phone) devices, and some bug fixes.

Using GBC Language Cabinet is so easy, but while using it for this update, I noticed there are some other things I want to add that will make this plug-in incredible. I am seriously thinking of continuing development on this plug-in and releasing a “Enhanced” version as a paid version (keeping the free to use option also available).

SMS: The Next Mission is still on my radar, and I hope to get back to it very soon. The mobile update should be completed soon, and then I will decide whether to continue development on GBC Language Cabinet Enhanced. If yes, then SMS comes a bit later.  If no, sooner.

Eating Your Own Dog Food

There’s a popular term in business called “Eating your own dog food”. In other words, “use your own products”. I had that opportunity during my recent upgrade to Tappy Holidays. I wanted to add a persistent high score and a game counter for achievements, and what better way to do that than with GBC Data Cabinet.

GBC Data Cabinet is a Corona SDK library I wrote to manage temporary and persistent data within your application. It’s on Corona’s plug-in store, and it’s free! Let me know what you think.

The following is the code I used to check for an existing persistent cabinet file. If it does not exist, then create one, and get it ready for upcoming data during game play. If persistent data does exist, then read the values.

CabExist = GBCDataCab.load("playerdata")

if CabExist == false then
    CabExist = GBCDataCab.createCabinet("playerdata")
end
    
if CabExist then
    highScore = GBCDataCab.get("playerdata", "highscore")
    if highScore == nil then highScore = 0 end
        
    intTotalGamesPlayed = GBCDataCab.get("playerdata", "gamesplayed")
    if intTotalGamesPlayed == nil then intTotalGamesPlayed = 0 end
else
    highScore = 0
    intTotalGamesPlayed = 0
end

 

Halloween Achievement in Pumpkin Patch Match

I guess Halloween is a perfect time for a bone-headed move. For those players of Pumpkin Patch Match, you may notice that there is an achievement that will be awarded if you play the game on October 31… today.

For some reason beyond my comprehension, the check for that achievement is coded incorrectly.  To get this achievement, you will need to do the following:

  • Play the game on October 31.
  • Get a score of 1.

Any other score will not reward the achievement at this time.

I am in the process of uploading an updated version of the game (version 1.1) to all the stores, but in the event that the game is not approved in time, please note that ending a game with a score of 1 will reward the achievement.

My apologies.

Announcing GBC Language Cabinet Plugin for CoronaSDK

GBC Language Cabinet is an easy to use way to display text in different languages. Simply specify which languages you are supporting, create or import tables of translated text, and use a basic function call to return the text in the correct language.

Several options exist to make your life even easier:

  • Text can be created with embedded keys. During the getText call, parameters can then be passed via a table. The returning text will be formatted to replace the keys with the parameters you passed. See below for examples.
  • You can import a comma-delimited file or a json file containing your text (including embedded keys!) instead of using multiple addText calls.

GBC Language Cabinet is available on the Corona Plugin Store.

Announcing GBC Data Cabinet for Corona SDK

GBC Data Cabinet, my free Corona SDK plugin, has been released on the Corona Plugin Store.

GBC Data Cabinet is used to create one or more data repositories, which is then used to store and recall data needed in your application. Data can be used throughout the application, without using global variables.

Data Cabinets can also be saved and recalled at a later time, which is ideal for keeping persistent data such as scores and other values that should not be reset between launches of the application.

For more information, check out the Corona Plugin Store, or view the plugin’s documentation.

Coding and Testing In-App Purchases in Corona – Part 1

Recently, I spent some time updating one of my apps, and spent a lot of time looking over my in-app purchase (IAP) code. Corona makes this very easy to implement, but there are some minor things to watch out for, especially if you are publishing to multiple stores. This page on the Corona site is a valuable resource, and hopefully this post will give you additional information.

Testing, on the other hand, is not as easy as you would think, since each store has their own way of handing this.

In this two part blog, I will outline my recent experiences in coding and testing IAP in Corona. Part one is all about coding IAP using Corona. We will cover testing in Part 2.

Coding for Various Stores
Three Stores, Three Libraries

Let’s configure the Apple, Google, and Amazon stores. First, we need to determine which library to use.

local store = nil
local platform = system.getInfo("targetAppStore")

if platform == "apple" then
    store = require ("store")
elseif platform == "google" then
    store = require("plugin.google.iap.v3")
elseif platform == "amazon" then
    store = require ("plugin.amazon.iap")
end

Easy enough, find the target app store and load the library. Of course, make sure your build.settings file includes the Google and Amazon IAP plugins.

Next, we need to initialize the store library.  If using Composer, the scene:show or scene:create function is probably a good place for this.

if platform == "amazon" then
    store.init(StoreListener)
else
    store.init(platform, StoreListener)
end

Notice the difference?  The Amazon store does not require the store name.

That’s really all you need for set up. Next, let’s cover making purchases. This is also not too hard to code in Corona, but you need to be aware of what is returned to your listener for each store, since it is different. To get you started, we’ll build a simple store listener below.

When a user attempts to make a purchase, you need to let the store know, and you also need to listen for the store’s response.  The following code does this.

local function StoreListener(event)
    local transaction = event.transaction

    if transaction == "purchased" then
        -- successful purchase. Give user what they paid for

    elseif transaction == "cancelled" then
        -- User decided to cancel purchase

    elseif transaction == "failed" then
        -- D'oh, something went wrong. User could not purchase item.
    end

    store.finishTransaction(transaction)
end

-- Enter your purchase string here
local itemToPurchase = "com.mydomain.item"

-- make a purchase
if platform == "apple" then
    store.purchase({itemToPurchase})
else
    store.purchase(itemToPurchase)
end

Notice that the Apple store expects a table of items… even if there is only one. Amazon and Google do not support purchasing multiple items, so we pass in the actual string of the item we wish to purchase.

In the store listener, we are listening for a successful purchase, a cancel, or a failure. It’s up to you to code what you want to do when these events come in.

Notice that we must call finishTransaction to clean up, otherwise the store will not know that the transaction is complete. This is required for Apple, but no harm calling this for all other stores.

Restoring A Purchase

In the event a user had to reinstall your app, or in the event a person upgrades their phone, it’s a good idea to allow a user to restore their purchase. This is usually done when a user taps on a “Restore Purchase” button, and it’s as simple as calling store.restore() and listening for the “restored” event, with one exception. Google does not have a “restored” event. When restoring on the Google Play store, a “purchased” event is returned. So, how do you determine whether the “purchased” event is from a purchase or a restore? Let’s look at our modified store listener:

local function StoreListener(event)
    local transaction = event.transaction

    if transaction == "purchased" then
        if isGoogleRestore then
            -- Restore purchase code goes here
        else
            -- successful purchase. Give user what they paid for
        end

    elseif transaction = "restored" then
        -- Apple or Amazon restore code goes here

    elseif transaction == "cancelled" then
        -- User decided to cancel purchase

    elseif transaction == "failed" then
        -- D'oh, something went wrong. User could not purchase item.
    end

    store.finishTransaction(transaction)
end

-- define this variable 
local isGoogleRestore

-- place this in your restore button handler
if platform == "google then isGoogleRestore = true else isGoogleRestore = false end
store.restore()

Here it gets a bit tricky. For Apple and Amazon, you handle a restore by listening for a “restored” event. For Google, you can set a boolean value (isGoogleRestore) during your restore purchase code and listen for a “purchased” event. In the purchased event of your store listener, determine whether this is a Google restore or a normal Google/Apple/Amazon purchase by checking isGoogleRestore, and handle appropriately.

A Couple of Other Events

Amazon can send a “revoked” event, and Google can send a “refunded” event in cases where refunds were applied. If you listen for these, it’s again up to you to decide how to handle, but it basically involved removing the features that were previously purchased.

Consumables

Consumables are items that can be purchased over and over again (coins, power-ups, etc). You should not and can not restore consumables via the store, but a user can buy these as many times as they want.

Google treats all items as non-consumable (Apple and Amazon allow you to create these items and identify them as consumable in their dashboards). Since non-consumable items can only be purchased once, how do we allow a consumable item to be purchased again?  Easy, with this piece of code:

if platform == "google" then
    -- call this for every consumable item you have in the store
    store.consumePurchase(myConsumableItem)
end

This call may take some time, so I like to place it in my scene:show event (in Composer). You can listen for a “consumed” event in your store listener if you need to do some post-processing. If the user now decides to purchase a consumable item, Google will allow it.

Conclusion

This is the basis of coding IAP in Corona. I did not cover everything (such as loading products), but this should get you going. In a future blog post, we will discuss how to test IAP on each store.