Sponsored By

Tapping into gamer's usage behavior - Touch Heatmaps

Optimizing game controls is crucial for a game's success. Many a times people push out games that are not very easy to play. Some insights into whether your controls are easy to use or not can be helpful. We figured out a way of doing this efficiently.

Osama Khalid, Blogger

January 20, 2014

5 Min Read
Game Developer logo in a gray background | Game Developer

Game development is a pretty rewarding experience. Watching people actually play what you made and get amused is a great feeling. But things don’t always go according to the plan. What works for us may not necessarily work for the gamers.

This is especially true for the controls. Game developers and testers get used to them after playing the game endlessly throughout the development cycle and they cannot be objective about whether they are easy or not. That’s why having some insights into whether they are actually working for the gamers is pretty crucial. One great way to do this is by asking friends and family or random strangers to test out the game. Another alternate can be to include it in your game code to record where the gamers are touching.

Doing this can be a bit resource-intensive but we figured out a way to do this efficiently.

In iOS, a UIWindow serves as an applications root view and everything originates from there. The hierarchy is shown in the illustration below:

Descrizione: http://www.objc.io/images/issue-1/view-insertion@2x.png

(image taken from http://www.objc.io/issue-1/containment-view-controller.html)

A UIApplication gets all of the messages and dispatches everything to the UIWindow through the sendEvent method.  To tap into it, one would need to subclass UIWindow and specify a different implementation for the sendEvent method. Doing this would require you to specify an intercept delegate too.

APWindow.h


@protocol APEventInterceptWindowDelegate
- (BOOL)interceptEvent:(UIEvent *)event; // return YES if event handled
@end

@interface APWindow : UIWindow {
    // It would appear that using the variable name 'delegate' in any UI Kit
    // subclass is a really bad idea because it can occlude the same name in a
    // superclass and silently break things like autorotation.
    id  __unsafe_unretained eventInterceptDelegate;
}

@property(unsafe_unretained) id  eventInterceptDelegate;
@end

APWindow.m


- (void)sendEvent:(UIEvent *)event {
    [eventInterceptDelegate interceptEvent:event];
    [super sendEvent:event];
}

Now every UIEvent will be passed to the interceptEvent method of your delegate. As per the class reference here, we know that there are three types of events; touch events, motion events, and remote-control events. We are only interested in the touch events. We can extract the exact coordinates of those touch events from a UIEvent by:


for (UITouch * touch in [event allTouches]) {
    CGPoint point = [touchlocationInView:touch.window];
}

Storing these points for a complete session (which may last from a few seconds to an hour or more) can prove to be difficult, given that it doesn’t affect the games performance.

Some might consider storing these points in an NSMutableArray and pass on that array to the server for computation. The challenge here is to keep the footprint minimal while making the access to the storage as fast as possible to prevent lag.

Another approach would be to store the number of taps at a co-ordinate in a 2 dimensional array, but that too can take up a lot of memory, as per the following formula:

M = Wd x Hd x I

Wd: width in pixels
Hd: height in pixels
I: bytes in one integer (4)

By this formula, for an iPad retina (W = 2048, H = 1536), we would require 12 MB (12582912 bytes) of memory for a single 2d array holding the touches.

But, we can reduce it significantly. How?

First, the size that iOS would report for an iPad retina would still be 1024x768, and a scale of 2. If we create a 2D array with that size it would require 1.5 MB (1572864 bytes). This is a huge difference, but we still don’t want this much detail and given that we need to transfer all of this information over the network, it wouldn’t be efficient.

Now, according to the human interface guidelines, we know that a tap-able area should be around 44x44 pixels. Given this information, we can reduce the size of our array by ten folds.

Let’s define an appropriate block size (a power of 2), such that we don’t really lose a lot of information.

 

 

M = (Wd / S) x (Hd / S) x I

S = block size, 16 in our case

The maximum size (1024x768) would now just require a mere 3 KB (3072 bytes) of memory, which is even small enough to transmit over a network.

To access the index of the array, one would just divide the value with the block size.

Mx = Px / S
My = Py / S

Px = The X coordinate of a given CGPoint
Py = The Y coordinate of a given CGPoint

Given a block size of 16, 3 KB is the maximum amount of memory that it would take to store the touches that happen in a single session.

 

 

But, it wouldn’t make much sense to the developer if a heat-map gets generated through the recorded data, as he wouldn’t be able to identify which touch happened on which screen.

Due to the significantly low memory footprint, we can store different arrays for different screens.

A game with 10 different screens would only take 30 KB of memory on the iPad retina, and much lower on the iPhones.

This would be more meaningful to the developer. Again, we have only computed this for a single user in a single session. As a developer, I wouldn’t be interested in where Sarah tapped on the screen, I would want to know a bigger picture, a heat-map showing an average session activity per screen would be beneficial for me.

(The method described has been tested, and it is live at Apppulp)

Read more about:

Featured Blogs
Daily news, dev blogs, and stories from Game Developer straight to your inbox

You May Also Like