This is the second part in a multi-part series discussing best-practices in product management and analytics for mobile games.
In the previous post, DON’T Measure Everything, I broadly espoused the advantages of the ‘simpler is better’ approach when applied to mobile game design, analytics and live ops., and promised to get more tactical in future posts. As mentioned before, the discussion here will be most relevant to mobile product managers already familiar with the basics of mobile game telemetry.
Picking up where we left off, speed and clarity in decision-making is essential for success in the mobile F2P business. In this highly competitive market, sustained top-100 grossing performance requires that you get a lot of things right. You need a bit of luck, a fantastic product and team, and a proven playbook for how to rapidly translate data into actionable insights.
Focusing on the latter, the foundation of a game’s telemetry is effective data design. In other words, the PM must define an effective events and properties taxonomy, and then work with engineering to ensure a clean implementation. Admittedly, for some of us this won’t be the most fun part of the PM’s job, but it’s essential that we get the foundation right and build from there.
To start, let’s assume that:
- We have a game in development that has yet to be wired for analytics.
- We have already chosen (or developed) software that handles the logging and server-storage of event and user property data.
With the above complete, your task is to define a specific and elegant event and properties taxonomy for your game.
I. A quick primer on game events
If you drive a car in the U.S, you’ve undoubtedly seen the black sensor strips installed on roads to help city planners understand traffic patterns. When the strip detects a vehicle’s weight, it logs a message that includes the exact time at which it was crossed.
“Game events” essentially work by the same principle, except that they can send more robust information. For example, when a user reaches a certain game screen where we’ve implemented a ‘screen viewed’ event, the message log that we receive includes not only the timestamp, but also the user’s unique ID, the name of the screen in question and any other information we choose to include.
At the most basic level, when we look at the messages sent by thousands of players in aggregate, we are able to quickly make inferences about how the average player behaves.
Some basic examples:
‘Screen Viewed’ Events:
- Screen A: 1,000,000 ‘view events’ logged this week
- Screen B: 100,000 ‘view events’ logged this week
- Screen A is being visited by more users than Screen B
- Screen A is being visited more times per individual user
- Some combination of the above
‘Level Started’ Events:
- Level 5: 500,000 ‘level5_started’ events logged this week
- Level 6: 250,000 ‘level6_started’ events logged this week
- Level 5 is particularly difficult, so many players give up and never reach level 6.
- Level 5 is being replayed multiple times by each player, to a larger degree than level 6. Perhaps level 5 is more difficult.
- During a play session, level 5 feels like a natural stopping point, after which many players close the app and, for some reason, don’t return.
- There is an irritating bug during or immediately after level 5 that causes players to quit before trying level 6.
In both cases above, by looking at the total volume of each event type we were able to quickly make observations and enumerate theories about ‘average’ player behavior.
Total volume is but one of many ways that we can evaluate events, but for now, it’s enough just to understand that all game events follow this conceptual format:
“Hey! [your event name] happened at time [X], was triggered by player [Y], and [here’s additional information that your engineer attached to the event].
With that, we can discuss the types of events that most games will need.
II. A minimum-viable events implementation
After much trial and error, I’ve found it most effective to define just a small, minimum-viable set of events and properties, and then to dedicate significant time to validating and debugging the implementation. It is tempting and usually counterproductive to go overboard when creating this list, so start small.
Or, put more elegantly:
In that spirit, the framework below should suffice for most mobile games:
Sample Events Framework
- Game Sessions: Session start and stop events
- Gameplay Engagement: Events that log each time a player starts or finishes one unit of gameplay (e.g. game level), and the result.
- Gameplay Progression: Events that log linear player progress through the game, starting with the tutorial, and continuing to reach new levels for example
- Resource Changes: Events that log situations causing an increase or decrease to a player’s resources (currency, items, etc)
- Feature Usage: Events that log when players visit or utilize different game features
- Offer Engagement: Events that log when players see and purchase in-game offers
If your studio has the luxury of dedicated data science and data engineering resources (you know who you are), you may be able to afford a more sophisticated implementation after these bases are covered. For the rest of us, the list above should do the trick.
III. Naming conventions
Getting more tactical, our job is to define the list of specific event names to be instrumented in code. These are also the event names that the team will see in the data, so we’ll want to establish a naming convention template to ensure our events are descriptive, simple, human readable, and consistent. Here’s an example of such a template, and how it can help us organize and name events in our six categories.
Sample Naming Conventions Template:
Game Sessions: Only two events are needed:
Gameplay Engagement: (levels, battles, etc)
Template: engage.level[#].[started/won/lost] Example: engage.level5.won
Template: progress.[axis name].[step number]_[describe action taken]* Example: progress.tutorial.step3_play_button_tapped /* Games often employ multiple axes of player progression. For example, an RPG may have “Tutorials Step Completed,” “Player Exp. Level Reached” and “Game Level Unlocked.” In these cases, just define the axes separately.
Template: resource.[optional: resource category].[resource name].[gained/spent].[reason or context] Example: resource.powerup.lollipop.spent.level4_used** /** Getting into the weeds here, but note the use of DOT (.) notation in some cases to create a nested hierarchy, and, alternatively, underscores (_) to join words at the same level. Your syntax may differ with how your system handles segmentation. Discuss with engineering 🙂
Template: feature.[feature name].[visited/acted] Example: feature.add_friends.visited
Template: offer.[offer name].[offered/converted] Example: offer.starter_pack.offered
So that’s it! Most, if not all of the events you’ll want to track can be permutations of the basic types above.
For the sake of familiarity, let’s create an example breakdown of the categories above for the ubiquitous Candy Crush Saga.
IV. Sample events list – Candy Crush Saga
Template: engage.level[#].[started/won/lost] (+event properties: ‘score_earned,’ ‘stars_earned’) engage.level1.started engage.level1.won engage.level2.started engage.level2.won engage.level3.started engage.level3.lost engage.level3.won etc.
Template: progress.[axisname].[step or level number]_[describe specific action or achievement] [Tutorial] progress.tutorial.step1_titlescreen_playbutton_tapped progress.tutorial.step2_level1_mapicon_tapped progress.tutorial.step3_level1_playbutton_tapped progress.tutorial.step4_level1_resultscreen_displayed* progress.tutorial.step5_level1_resultscreen_nextbutton_tapped progress.tutorial.step6_level2_mapicon_tapped progress.tutorial.step7_level2_playbutton_tapped progress.tutorial.step8_level2_resultscreen_displayed progress.tutorial.step9_level2_resultscreen_nextbutton_tapped etc. [Level Progression] progress.levels.level2_unlocked progress.levels.level3_unlocked progress.levels.level4_unlocked progress.levels.level5_unlocked etc. /* Note above that we have defined and named events in a manner that minimizes ambiguity. “resultscreen_displayed” is a better choice than “level_finished” because, with the latter, it isn’t clear whether the event is triggered before or after the results screen is seen.
Template: resource.[optional: resource category name].[resource name].[gained/spent].[reason or context] [Powerups] resource.powerup.lollipop_hammer.gained.gold_spent resource.powerup.lollipop_hammer.spent.level1_used [Hearts] resource.heart.spent.level_failed (+ event property: level number)* resource.heart.gained.friend_gifted resource.heart.gained.hearts_refill_purchased [IAP] resource.cash.spent.beginner_bundle_purchased (Trigger after receipt validation) [Gold] resource.gold.gained.beginner_bundle_purchased resource.gold.spent.lollipop_hammer_purchased /* Custom event properties are additional, event-specific properties used to attach additional information to events. All events in the resources category above also need two custom properties: ‘amount_gained,’ and ‘amount_spent.’
Template: feature.[feature name].[offered/visited/acted]* [Ask for lives feature] feature.ask_for_lives.visited feature.ask_for_lives.acted [Hypothetical Halloween Event and Prize Wheel] feature.halloween_event_map.visited feature.halloween_event_map.acted feature.halloween_event_prizewheel.visited feature.halloween_event_prizewheel.acted etc. /* Following a strict ‘visited / acted’ convention makes it easy to assess feature conversion rates. Here we are defining conversion as choosing to engage with a feature after receiving an impression.
Template: offer.[offer name].[offered/visited/converted] [Candy Shop]* offer.candy_shop.visited offer.candy_shop.converted [More Offers] offer.shop_more_offers.visited offer.shop_more_offers.converted (any offer) [Get More Lives] offer.get_more_lives.offered offer.get_more_lives.converted /* Note that although Candy Shop is a storefront, we can treat it as an offer for our purposes. An impression is generated when the user visits the store, and any purchase from that storefront counts as a conversion.
So there you have it. Once we have defined the events covering the six types of user behavior in our game, we can move to the next step, defining user properties.
Thanks for reading!
As always, don’t hesitate to reach out with ideas, criticisms or other feedback. Reach me at firstname.lastname@example.org.
This series of posts will continue with Part III: What you need to know about your users.