Event Tracking
GameAnalytics features the following event types:
Event | Description |
---|---|
Ad | Ads shown and clicked, fill rate. |
Business | In-App Purchases supporting receipt validation on GA servers. |
Design | Submit custom event id’s. Useful for tracking metrics specifically needed for your game. |
Error | Submit exception stack traces or custom error messages. |
Health | Automatically submits health metrics related to your game such as FPS. |
Impression | Impression data from different ad networks |
Progression | Level attempts with Start, Fail & Complete event. |
Resource | Managing the flow of virtual currencies - like gems or lives |
Read more about events here
Business Events
Business events are used to track (and validate) real-money transactions.
Some configuration is needed before receipt validation will be active. Read information about validation and requirements for different platforms here.
Business Event with receipt
When submitting a Business Event supply the following from the IAP procedure.
- receipt (
INAPP_PURCHASE_DATA
) - signature (
INAPP_DATA_SIGNATURE
)
Read more about retrieving these needed fields at the Google documentation here.
Add a business event when an in-app purchase is completed.
GameAnalytics.addBusinessEventWithCurrency("USD", 1000, "item", "id", "cart", "[receipt]", "google_play", "[signature]");
Field | Type | Description | Example |
---|---|---|---|
currency | string | Currency code in ISO 4217 format | USD |
amount | integer | Amount in cents | 99 is $0.99 |
itemType | string | The type/category of the item | GoldPacks |
itemId | string | Specific item bought | 1000GoldPack |
cartType | string | The game location of the purchase. Max 10 unique values | EndOfLevel |
receipt | string | INAPP_PURCHASE_DATA . Null allowed. Read more about Android receipt here | |
signature | base64 string | INAPP_DATA_SIGNATURE . Null allowed. Read more about Android signature here. |
If the receipt/signature is null (or is an invalid receipt) then it will be submitted to the GA server but will register as not validated.
Receipt JSON Schema
{
"orderId": "",
"packageName": "",
"productId": "",
"purchaseTime": 1484080095335,
"purchaseState": 0,
"purchaseToken": ""
}
Example: Correct receipt information
It is possible to use code frameworks that handle IAP flow and of course every is done a bit differently This code example uses the sample code included in Android SDK found under this directory: android_sdk_folder/extras/google/play_billing/samples/TrivialDrive/src/com/example/android/trivialdrivesample/util
, it assumes you have downloaded Google Play Billing.
First of all the SkuDetails
class has been modified slightly in this example to expose all information needed:
public class SkuDetails {
String mItemType;
String mSku;
String mType;
String mPrice;
String mTitle;
String mDescription;
String mJson;
String mCurrencyCode;
int mPriceInCents;
public SkuDetails(String jsonSkuDetails) throws JSONException {
this(IabHelper.ITEM_TYPE_INAPP, jsonSkuDetails);
}
public SkuDetails(String itemType, String jsonSkuDetails) throws JSONException {
mItemType = itemType;
mJson = jsonSkuDetails;
JSONObject o = new JSONObject(mJson);
mSku = o.optString("productId");
mType = o.optString("type");
mPrice = o.optString("price");
mTitle = o.optString("title");
mDescription = o.optString("description");
mCurrencyCode = o.optString("price_currency_code");
mPriceInCents = o.optInt("price_amount_micros") / 10000;
}
public String getSku() { return mSku; }
public String getType() { return mType; }
public String getPrice() { return mPrice; }
public String getTitle() { return mTitle; }
public String getDescription() { return mDescription; }
public String getCurrencyCode() { return mCurrencyCode; }
public int getPriceInCents() { return mPriceInCents; }
@Override
public String toString() {
return "SkuDetails:" + mJson;
}
}
public void onIabPurchaseFinished(IabResult result, Purchase purchase)
{
// Code before business event...
SkuDetails skuDetails = currentInventory.getSkuDetails(purchase.getSku());
GameAnalytics.addBusinessEventWithCurrency(skuDetails.getCurrencyCode(), skuDetails.getPriceInCents(), "", purchase.getSku(), "", purchase.getOriginalJson(), "google_play", purchase.getSignature());
// Code after business event...
}
// Current inventory is set after querying inventory
public void onQueryInventoryFinished(IabResult result, Inventory inventory)
{
if (result.isFailure())
{
// Handle failure
}
else
{
// Other code...
currentInventory = inventory;
}
}
The above example assumes you have setup onIabPurchaseFinished
and onQueryInventoryFinished
to called on the correct events.
Price and the currency format (ISO 4217)
The amount is an integer with the price cent value. Also the currency has to conform to the ISO 4217 format. Make sure the price is in cents and that the currency strings are returned as required.
Custom Event Fields
It is possible to use a set of key-value pairs to add extra fields but it will only be available through raw data export. Here is an example of how to use it:
HashMap<String, Object> fields = new HashMap<>();
fields.put("test", 100);
fields.put("test_2", "hello_world");
GameAnalytics.addBusinessEventWithCurrency("USD", 1000, "item", "id", "cart", "[receipt]", "google_play", "[signature]", fields);
For more information on custom event fields and raw data export go here.
For more information on Business Events go here.
Ad Events
The GameAnalytics ad event needs to be called when certain events happen for the implemented ad sdk’s. An ad sdk has callback methods activating code when certain things are activated (like ad show or ad clicked).
To use the ad event it is need to call the GameAnalytics SDK when these delegates are called.
The examples below describe how to implement this for the following ad-types:
- rewarded video
- interstitial
- bannner
All code examples use AdMob SDK to showcase example usage. Other ad networks might differ in naming/structure, but the overall process should be applicable to all.
The ad SDK name argument for the ad event needs to be all lower-case with no spaces or underscore used. Here some examples of valid values to use for ad SDK names:
- admob
- unityads
- ironsource
- applovin
Rewarded Video
Showing a rewarded video
It is possible for an ad to fail to show, either because there's no internet, or the provider has failed to find a suitable ad for you. You can track such errors by sending an AdEvent
.:
if (rewardedVideoAd != null && rewardedVideoAd.isLoaded()) {
rewardedVideoAd.show();
}
else
{
GameAnalytics.addAdEvent(GAAdAction.FailedShow, GAAdType.RewardedVideo, "admob", "[AD_PLACEMENT_OR_UNIT_ID]");
}
If you want to track errors then we recommend defining this method to map the error message stored earlier in the latestRewardedVideoError
variable:
// TRACKING ERRORS
private GAAdError getLatestAdError(int error)
{
GAAdError result = GAAdError.Unknown;
// implementation
return result;
}
Track time spent
If you want to track how much the user watched then start timer and keep track of current rewarded video ad when onRewardedVideoAdOpened()
listener method is called:
@Override
public void onRewardedVideoAdOpened() {
// keep track of current rewarded video ad
currentRewardedVideoPlacement = "[AD_PLACEMENT_OR_UNIT_ID]";
// start timer for this ad identifier
GameAnalytics.startTimer(currentRewardedVideoPlacement);
}
// when application goes to background (during the display of a rewarded video ad) then the timer needs to stop.
// therefore we need to call code in onPause() and onResume().
@Override
public void onPause() {
if(currentRewardedVideoPlacement != null)
{
GameAnalytics.pauseTimer(currentRewardedVideoPlacement);
}
}
@Override
public void onResume() {
if(currentRewardedVideoPlacement != null)
{
GameAnalytics.resumeTimer(currentRewardedVideoPlacement);
}
}
Tracking ad rewards
If you want to track rewards from rewarded videos, then call an ad event in the method where rewards are registered. For example, in admob this method is called onRewarded(RewardItem reward)
and the following example is a method for how you could handle this:
@Override
public void onRewarded(RewardItem reward) {
// send ad event - reward recieved
GameAnalytics.addAdEvent(GAAdAction.RewardReceived, GAAdType.RewardedVideo, "admob", "[AD_PLACEMENT_OR_UNIT_ID]");
}
Tracking successful ads
Finally we can track that the rewarded ad has been shown succesfully. For admob this method is called onRewardedVideoAdClosed()
and the following example is a method for how you could handle this.
@Override
public void onRewardedVideoAdClosed() {
if(currentRewardedVideoPlacement != null)
{
long elapsedTime = GameAnalytics.stopTimer(currentRewardedVideoPlacement);
// send ad event for tracking elapsedTime
GameAnalytics.addAdEvent(GAAdAction.Show, GAAdType.RewardedVideo, "admob", "[AD_PLACEMENT_OR_UNIT_ID]", elapsedTime);
currentRewardedVideoPlacement = null;
// OR if you do not wish to track time
// send ad event without tracking elapsedTime
GameAnalytics.addAdEvent(GAAdAction.Show, GAAdType.RewardedVideo, "admob", "[AD_PLACEMENT_OR_UNIT_ID]");
}
}
If onRewardedVideoCompleted()
is called send the following ad event:
@Override
public void onRewardedVideoCompleted() {
if(that.get().currentRewardedVideoPlacement != null)
{
long elapsedTime = GameAnalytics.stopTimer(AD_UNIT_ID);
// send ad event for tracking elapsedTime
GameAnalytics.addAdEvent(GAAdAction.Show, GAAdType.RewardedVideo, "admob", "[AD_PLACEMENT_OR_UNIT_ID]", elapsedTime);
that.get().currentRewardedVideoPlacement = null;
// OR if you do not wish to track time
// send ad event without tracking elapsedTime
GameAnalytics.addAdEvent(GAAdAction.Show, GAAdType.RewardedVideo, "admob", "[AD_PLACEMENT_OR_UNIT_ID]");
}
}
Interstitial
Showing an interstitial ad
As for rewarded videos, it is advised to track if the ad has failed to be served. Please take a look at the following example:
if (interstitialAd != null && interstitialAd.isLoaded()) {
interstitialAd.show();
GameAnalytics.addAdEvent(GAAdAction.Show, GAAdType.Interstitial,"admob", "[AD_PLACEMENT_OR_UNIT_ID]");
}
else
{
GameAnalytics.addAdEvent(GAAdAction.FailedShow, GAAdType.Interstitial, "admob", "[AD_PLACEMENT_OR_UNIT_ID]");
}
Track user clicks
You can track if the user has clicked the ad and has been redirected to the product's website. This is done by sending an GAAdAction.Clicked
event:
@Override
public void onAdLeftApplication() {
// send ad event
GameAnalytics.addAdEvent(GAAdAction.Clicked, GAAdType.Interstitial,"admob", "[AD_PLACEMENT_OR_UNIT_ID]");
}
Banner
Showing a banner
In similiar fashion to rewarded video ads & interstitial ads, you can track if the ad has been shown succesfully or not. The example below will show how you could handle it:
if (bannerAd != null && bannerAd.isLoaded()) {
bannerAd.show();
GameAnalytics.addAdEvent(GAAdAction.Show, GAAdType.Banner,"admob", "[AD_PLACEMENT_OR_UNIT_ID]");
}
else
{
GameAnalytics.addAdEvent(GAAdAction.FailedShow, GAAdType.Banner, "admob", "[AD_PLACEMENT_OR_UNIT_ID]");
}
Track banner clicks
You can track if an user has clicked a banner ad by sending a GAAdAction.Clicked
. Please check out the example below:
@Override
public void onAdOpened() {
// send ad event
GameAnalytics.addAdEvent(GAAdAction.Clicked, GAAdType.Banner, "admob", "[AD_PLACEMENT_OR_UNIT_ID]");
}
Custom Event Fields for banners
It is possible to use a set of key-value pairs to add extra fields but it will only be available through raw data export. Here is an example of how to use it:
HashMap<String, Object> fields = new HashMap<>();
fields.put("test", 100);
fields.put("test_2", "hello_world");
GameAnalytics.addAdEvent(GAAdAction.Show, GAAdType.Interstitial,"admob", "[AD_PLACEMENT_OR_UNIT_ID]", fields);
Ad Event Info
In the table below you can find all the values supported for ad events.
Field | Type | Description | Example |
---|---|---|---|
adAction | enum | A defined enum for ad action (for example clicked). | GAAdAction.Clicked GAAdAction.Show GAAdAction.FailedShow GAAdAction.RewardReceived |
adType | enum | A defined enum for ad type (for example interstitial). | GAAdType.Video GAAdType.RewardedVideo GAAdType.Playable GAAdType.Interstitial GAAdType.OfferWall GAAdType.Banner |
adSdkName | string | Name of the Ad/Ad mediation SDK. | admob |
adPlacement | string | Identifier of ad in the game or the placement of it. | level_complete_ad |
duration | int | Optional. Only used for video ads to track how long the user watched the video for. | 10 |
noAdReason | enum | Optional. Used to track the reason for not being able to show an ad when needed (for example no fill). | GAAdError.Unknown GAAdError.Offline GAAdError.NoFill GAAdError.InternalError |
For more information on Ad Events go here.
Impression Events
Impression events are used to get impression data from different ad networks. Currently the following ad networks are supported:
- Fyber
- IronSource
- TopOn
- MAX
- Aequus
- AdMob
Fyber
To use impression data from Fyber add the following code inside the onCreate function of the first activity of your game:
Banner.setBannerListener(new BannerListener() {
@Override
public void onShow(String placementId, ImpressionData impressionData) {
JSONObject impressionDataJson = new JSONObject();
try
{
impressionDataJson.put("advertiserDomain", impressionData.getAdvertiserDomain());
impressionDataJson.put("campaignId", impressionData.getCampaignId());
impressionDataJson.put("creativeId", impressionData.getCreativeId());
impressionDataJson.put("countryCode", impressionData.getCountryCode());
impressionDataJson.put("currency", impressionData.getCurrency());
impressionDataJson.put("impressionDepth", impressionData.getImpressionDepth());
impressionDataJson.put("demandSource", impressionData.getDemandSource());
impressionDataJson.put("impressionId", impressionData.getImpressionId());
impressionDataJson.put("networkInstanceId", impressionData.getNetworkInstanceId());
impressionDataJson.put("priceAccuracy", impressionData.getPriceAccuracy().ordinal());
impressionDataJson.put("placementType", impressionData.getPlacementType().ordinal());
impressionDataJson.put("renderingSDK", impressionData.getRenderingSdk());
impressionDataJson.put("renderingSDKVersion", impressionData.getRenderingSdkVersion());
impressionDataJson.put("netPayout", impressionData.getNetPayout());
}
catch (JSONException e)
{
e.printStackTrace();
}
GameAnalytics.addImpressionEvent("fyber", impressionData.toString());
}
// rest part of BannerListener...
});
Interstitial.setInterstitialListener(new InterstitialListener() {
@Override
public void onShow(String placementId, ImpressionData impressionData) {
// Use same code as in 'BannerListener'
}
// rest part of InterstitialListener...
});
Rewarded.setRewardedListener(new RewardedListener() {
@Override
public void onShow(String placementId, ImpressionData impressionData) {
// Use same code as in 'BannerListener'
}
// rest part of RewardedListener...
});
IronSource
To use impression data from IronSource add the following code inside the onCreate function of the first activity of your game:
protected void onCreate(Bundle savedInstanceState)
{
//...other code
IronSource.setImpressionDataListener(new ImpressionDataListener()
{
@Override
public void onImpressionSuccess(ImpressionData impressionData)
{
GameAnalytics.addImpressionIronSourceEvent(IronSourceUtils.getSDKVersion(), impressionData.getAllData());
}
});
}
TopOn
To use impression data from TopOn add the following code inside the onCreate function of the first activity of your game:
protected void onCreate(Bundle savedInstanceState)
{
//...other code
topOnInterstitial = new ATInterstitial(this, TOPON_INTERSTITIAL_PLACEMENT_ID);
topOnInterstitial.setAdListener(new ATInterstitialListener()
{
// ...rest of listener implementation
@Override
public void onInterstitialAdShow(ATAdInfo atAdInfo)
{
GameAnalytics.addImpressionTopOnEvent( ATSDK.getSDKVersionName().replace("UA_", ""), atAdInfo.toString());
}
});
}
MAX
To use impression data from MAX add the following code inside the onCreate function of the first activity of your game:
protected void onCreate(Bundle savedInstanceState)
{
//...other code
maxInterstitial = new MaxInterstitialAd(MAX_AD_UNIT_ID, this);
maxInterstitial.setAdListener(new MaxAdListener()
{
// ...rest of listener implementation
@Override
public void onAdDisplayed(MaxAd ad)
{
JSONObject json = new JSONObject();
try
{
json.put("country", AppLovinSdk.getInstance(referenceToActivity).getConfiguration().getCountryCode());
json.put("network_name", ad.getNetworkName());
json.put("adunit_id", ad.getAdUnitId());
json.put("adunit_format", ad.getFormat().getLabel());
json.put("placement", ad.getPlacement());
json.put("creative_id", ad.getCreativeId());
json.put("revenue", ad.getRevenue());
}
catch(JSONException e)
{
}
GameAnalytics.addImpressionMaxEvent( AppLovinSdk.VERSION, json);
}
});
}
Aequus
To use impression data from Aequus add the following code inside the onCreate function of the first activity of your game:
protected void onCreate(Bundle savedInstanceState)
{
//...other code
Aequus.setILRDListener(new AequusILRDListener()
{
// ...rest of listener implementation
@Override
public void onAequusILRDImpressionShow(ImpressionData impressionData)
{
GameAnalytics.addImpressionAequusEvent( mobi.aequus.sdk.BuildConfig.SDK_VERSION_NAME, impressionData.getJsonRepresentation());
}
});
}
AdMob
To use impression data from AdMob add the following code inside the onCreate function of the first activity of your game:
protected void onCreate(Bundle savedInstanceState)
{
//...other code
interstitialAd.setOnPaidEventListener(new OnPaidEventListener()
{
@Override
public void onPaidEvent(AdValue adValue)
{
JSONObject impressionDataJson = new JSONObject();
try
{
impressionDataJson.put("adunit_id", interstitialAd.getAdUnitId());
impressionDataJson.put("currency", adValue.getCurrencyCode());
impressionDataJson.put("precision", adValue.getPrecisionType());
impressionDataJson.put("adunit_format", GameAnalytics.INTERSTITIAL);
impressionDataJson.put("network_class_name", interstitialAd.getResponseInfo().getMediationAdapterClassName());
impressionDataJson.put("revenue", adValue.getValueMicros());
}
catch (JSONException e)
{
e.printStackTrace();
}
GameAnalytics.addImpressionAdMobEvent(MobileAds.getVersionString(), impressionDataJson);
}
Custom Impression Event Fields
It is possible to use a set of key-value pairs to add extra fields but it will only be available through raw data export. Here is an example of how to use it:
HashMap<String, Object> fields = new HashMap<>();
fields.put("test", 100);
fields.put("test_2", "hello_world");
GameAnalytics.addImpressionEvent([adNetworkName], [adNetworkVersion], [impressionData], fields);
For more information on custom event fields and raw data export go here.
For more information on Impression Events go here.
Resource Events
Resource events are used to register the flow of your in-game economy (virtual currencies) – the sink (subtract) and the source (add) for each virtual currency.
Before calling the resource event it is needed to specify what discrete values can be used for currencies and item types in the Configuration phase.
GameAnalytics.addResourceEventWithFlowType(GAResourceFlowType.Source, "Gems", 400, "IAP", "Coins400");
// sink (subtract) Gem currency to buy an item.
GameAnalytics.addResourceEventWithFlowType(GAResourceFlowType.Sink, "Gems", 400, "Weapons", "SwordOfFire");
// sink (subtract) Gem currency to source (buy) some amount of another virtual currency (BeamBooster).
GameAnalytics.addResourceEventWithFlowType(GAResourceFlowType.Sink, "Gems", 100, "Boosters", "BeamBooster5Pack");
GameAnalytics.addResourceEventWithFlowType(GAResourceFlowType.Source, "BeamBooster", 5, "Gems", "BeamBooster5Pack");
// sink (subtract) 3 BeamBooster currency that were used during a level.
GameAnalytics.addResourceEventWithFlowType(GAResourceFlowType.Sink, "BeamBooster", 3, "Gameplay", "BeamBooster5Pack");
Custom Resource Event Fields
It is possible to use a set of key-value pairs to add extra fields but it will only be available through raw data export. Here is an example of how to use it:
HashMap<String, Object> fields = new HashMap<>();
fields.put("test", 100);
fields.put("test_2", "hello_world");
GameAnalytics.addResourceEventWithFlowType(GAResourceFlowType.Sink, "BeamBooster", 3, "Gameplay", "BeamBooster5Pack", fields);
For more information on custom event fields and raw data export go here.
Field | Type | Required | Description |
---|---|---|---|
flowType | enum | yes | Add (source) or subtract (sink) resource. |
currency | string | yes | One of the available currencies set in GA_Settings (Setup tab).This string can only contain [A-Za-z] characters. |
amount | float | yes | Amount sourced or sunk. |
itemType | string | yes | One of the available item types set in GA_Settings (Setup tab). |
itemId | string | yes | Item id (string max length = 32) |
Be careful to not call the resource event too often! In a game where the user collect coins fairly fast you should not call a Source event on each pickup. Instead you should count the coins and send a single Source event when the user either complete or fail the level.
For more information on Resource Events go here.
Progression Events
Progression events are used to track attempts at completing some part of a game (level, area). A defined area follow a 3 tier hierarchy structure (could be world:stage:level) to indicate what part of the game the player is trying to complete.
When a player is starting a progression attempt a start event should be added.
When the player then finishes the attempt a fail or complete event should be added along with a score if needed.
Add a progression start event.
GameAnalytics.addProgressionEventWithProgressionStatus(GAProgressionStatus.Start, "world01", "stage01", "level01");
It is not required to use all 3 if your game does not have them:
- progression01
- progression01 and progression02
- progression01 and progression02 and progression03
Custom Progression Event Fields
It is possible to use a set of key-value pairs to add extra fields but it will only be available through raw data export. Here is an example of how to use it:
HashMap<String, Object> fields = new HashMap<>();
fields.put("test", 100);
fields.put("test_2", "hello_world");
GameAnalytics.addResourceEventWithFlowType(GAResourceFlowType.Sink, "BeamBooster", 3, "Gameplay", "BeamBooster5Pack", fields);
For more information on custom event fields and raw data export go here.
Field | Type | Required | Description |
---|---|---|---|
progressionStatus | enum | yes | Status of added progression (start, complete, fail). |
progression01 | string | yes | 1st progression (e.g. world01). |
progression02 | string | no | 2nd progression (e.g. level01). |
progression03 | string | no | 3rd progression (e.g. phase01). |
score | int | no | The player’s score. |
For more information Progression Events go here.
Error Events
Used to track custom error events in the game. You can group the events by severity level and attach a message. By default the SDK will automatically send error events for uncaught exceptions for more information on this and how to disable it click here.
To add a custom error event call the following function:
GameAnalytics.addErrorEventWithSeverity(GAErrorSeverity.GAErrorSeverityDebug, "Something went bad in some of the smelly code!");
Custom Error Event Fields
It is possible to use a set of key-value pairs to add extra fields but it will only be available through raw data export. Here is an example of how to use it:
HashMap<String, Object> fields = new HashMap<>();
fields.put("test", 100);
fields.put("test_2", "hello_world");
GameAnalytics.addErrorEventWithSeverity(GAErrorSeverity.GAErrorSeverityDebug, "Something went bad in some of the smelly code!", fields);
For more information on custom event fields and raw data export go here.
Design Events
Every game is unique. Therefore some needed events might not be covered by our other event types. The design event is available for you to add your own event-id hierarchy.
Please note that custom dimensions and progression filters will not be added on design and error events. Therefore you cannot (at the moment) filter by these when viewing design or error metrics.
To add a design event call the following method.
GameAnalytics.addDesignEventWithEventId("Kill:Sword:Robot");
It is also possible to add a float value to the event. This will (in addition to count) make the mean and sum aggregation available in the tool.
GameAnalytics.addDesignEventWithEventId("BossFights:FireLord:KillTimeUsed", 234);
Custom Design Event Fields
It is possible to use a set of key-value pairs to add extra fields but it will only be available through raw data export. Here is an example of how to use it:
HashMap<String, Object> fields = new HashMap<>();
fields.put("test", 100);
fields.put("test_2", "hello_world");
GameAnalytics.addDesignEventWithEventId("BossFights:FireLord:KillTimeUsed", 234, fields);
For more information on custom event fields and raw data export go here.
Field | Type | Required | Description |
---|---|---|---|
eventName | string | yes | The event string can have 1 to 5 parts. The parts are separated by ‘:’ with a max length of 64 each. e.g. “world1:kill:robot:laser”. The parts can be written only with a-zA-Z, 0-9, -_.,:()!? characters. |
eventValue | float | no | Number value of event. |
It is important to not generate an excessive amount of unique nodes possible in the event hierarchy tree. A bad implementation example. [level_name]:[weapon_used]:[damage_done]. level_name could be 100 values, weapon_used could be 300 values and damage_done could be 1-5000 perhaps. This will generate an event hierarchy with: 100 x 300 x 5000 = 1.5M
possible nodes. This is far too many. Also the damage should be put as a value and not in the event string. The processing will perhaps be blocked for a game doing this and cause other problems when browsing our tool. The maximum amount of unique nodes generated should be around 10k.
Read more about event types here. You will get the most benefit of GameAnalytics when understanding what and how to track.
You will get the most out of GameAnalytics when understanding what and how to track.
For more information on Design Events go here.