News

Make a game on Pebble

Develop for the Pebble smartwatch and play games on the go

Flappy Tux on your wrist!

Under a superficial examination, the Pebble doesn’t exactly look like an ideal device for gaming. If we look at its puny 80MHz processor, which is paired up with a monochrome e-paper screen, the display does not only lack the ability to display colour but is furthermore not particularly well suited to frequently updating content. The Pebble also interacts with its user via a grand total of four buttons, one of which cannot be accessed easily by third-party applications.

Paradoxically, the Pebble’s success is directly rooted in its utilitarian approach to smartwatch design. Using low-powered hardware permits the watch to be cheap and cheerful: most of its competitors tip the scales far harder and therefore last for significantly less time on a single charge.

In the past, developers have frequently risen to the challenge of doing almost impossible
things. Given that some gamers have managed to shoehorn a complete 3D engine into a Commodore 64, creating a game for one of the world’s favourite smartwatches seems as though it should be more than doable.

Dong Nguyen’s very popular game Flappy Bird recycled a simple game concept first seen on Palm OS. Originally known as SFCave, the game involves you leading an object through a cave where activating a booster rocket makes the object rise, while gravity makes it fall on its own.

Flappy Bird is primitive when analysed from a conceptual point of view, but this is beneficial for our cause; getting started with game programming becomes much easier if the example at hand does not distract you from the actual coding work. We shall make Flappy Tux.

The first – and utmost important – concept involves the idea of the game loop. GUI- driven applications spend most of their time gallivanting around and waiting for user input – as long as the user does not press a control, nothing has to be done. Input causes the app to perform more or less complex computations, after which the results are displayed.

Games find themselves in a less satisfactory situation. They are to model the real world, which does not provide its subjects such a serene existence. Instead, everything is continuous: gravity pulls, people chatter and wines age in real time.

Flappy Tux on your wrist!
Flappy Tux on your wrist!

The first step to digital bliss involves a process called discretisation. This mathematically complex process splits real time into a group of slots (see graph above), which are then run one after the other. Inside each slot, all movements are considered discrete: the wine will age by one slot, while a person says a letter (or two).

When the individual steps are small enough, they cannot be taken apart from a normal and contiguous motion. In most cases, games will work with a loop-like structure similar to the one shown above: each update cycle is followed by one dedicated to refreshing the screen. The number of screen redraws is often referred to as the frame rate. Once rates reach more than about 30 frames per second, users tend to perceive them as continuous.

Coding on the Internet

[Grab your assests]

Even though Pebble OS is supported by a Linux-based software development kit, most developers choose to do their work via CloudPebble. It provides a set of private repositories and a network-based compiler. Remote deployments are enabled via a smartphone connected to the Pebble.

Getting started with CloudPebble requires you to visit their website with a browser of your choice. Proceed to creating an account in order to maintain your projects on the server. Your Pebble smartwatch must be connected to the Pebble Conduit app, which can be downloaded from the Google Play Store.

Once the initial configuration is done, click Menu>Settings and enable the developer connection. Then, open the menu in order to find the developer mode settings and check the Enabled checkbox in order to activate the forwarding (if your CloudPebble account matches the one used on the phone, the connection will be established automatically).

At the time of writing, developers need to use a beta version of the handset app because it only runs on Android 4.3 and above; the previously possible method of manual IP address entry has been disabled.

Next, click the Create button in order to start the New Project wizard. Flappy Tux is a Pebble C SDK Project based on the Minimal template. It consists of but one file named main.c and its default content looks like the following:

#include 

Window *my_window;
TextLayer *text_layer;

Main.c starts out by including pebble.h – this file contains definitions for the various operating system functions. We then proceed to create two pointers: one of them refers to a Window object, whereas the other pointer will address a TextLayer.

Pebble OS contains a layer-driven GUI stack. This means that normal applications tend to be made up of one or more windows cascaded above one another. Each of these window objects can contain one or more sublayers, which realise the actual user interface.

Game developers tend to shy away from the user interface resources provided by the OS due to their less than stellar performance. In the case of the Pebble, games based on multiple BitmapLayers tend to be significantly slower than ones based on the sprite drawing technique used in our example.

Handle_init and handle_deinit are responsible for setting up the user interface. It consists of an empty form containing an also empty label:

void handle_init(void) {
   my_window = window_create();

   text_layer = text_layer_create(GRect(0,0, 144, 20));
   window_stack_push(my_window, true);
}

void handle_deinit(void) {
   text_layer_destroy(text_layer);
   window_destroy(my_window);
}

Pebble OS calls the main() function in order to start an application. It invokes the app_event_ loop() method, which is responsible for starting the aforementioned event loop. The invocation will not return until the program is intended to terminate. Following on from that, handle_ deinit() is invoked:

int main(void) {
handle_init();
app_event_loop();
handle_deinit();
}

With that, we are ready to deploy our application to the smartwatch. Ensure that developer mode is enabled in the conduit app, then click the play button in the GUI of CloudPebble. An empty window should appear on your Pebble after a few seconds worth of waiting.

Tick, tick, tick

Our game will draw itself into a single layer. This requires us to start a game loop – a process that is ideally accomplished by changing the content of handle_init in order to look like the version shown below:

void handle_init(void)
{
   my_window = window_create();

   myCanvas = layer_create(GRect(0, 0, 144, 168));

   window_stack_push(my_window, true);

   Layer* motherLayer=window_get_root_layer(my_window);
   layer_add_child(motherLayer, myCanvas);

   layer_set_update_proc(myCanvas, updateGame);
   app_timer_register(34, timer_handler, NULL);
}

Handle_init now starts out by creating a fullscreen layer. It is then pushed into the main window in order to make it appear on the display. Then layer_set_update_proc assigns an update handler to the layer.

Most operating systems don’t permit developers to update the visuals as they please, but rendering can be made more effective if changes are made only in response to an event. Our method updateGame() will be invoked whenever myCanvas needs to be redrawn.

Since timers in Pebble OS are always single- shot, the timer handler must retrigger itself in order to keep the loop running. Furthermore, the layer is marked dirty in order to invoke its redrawing method:

static void timer_handler(void *context)
{
   layer_mark_dirty(myCanvas);
   app_timer_register(34, timer_handler, NULL);
}

Finally, the actual redrawing will take place in updateGame. This method is provided with a GContext variable which will now point at the layer in question:

static void updateGame(Layer *layer, GContext *ctx)
{

}

Add some physics

Our version of Flappy Bird does not have to be full of features – we will, for now, be happy if our little Tux moves across the screen. This can be accomplished by ‘integrating’ the steering input in every pass of the game loop:

static void updateGame(Layer *layer, GContext *ctx)
{
   totalPos+=moves_per_frame;
   moves_per_frame+=0.04;
   if(totalPos < 30) totalPos=30;
   if(totalPos > 114) totalPos=114;

   flownWay+=1;

Newtonian physics live and fall by the law of constant motion. The position of an object can be determined by summing up its speed over time, and speed itself can be derived by summing up acceleration. Our example does this with two global variables, which are furthermore given a sanity check in order to keep Tux from falling off the screen.

Sprites and Bitmaps

Pebble OS provides developers with a group of graphical primitives that can be used for drawing lines, rectangles and circles. These methods can work really well when faced with simple tasks, especially because creating more complex visuals by hand imposes a significant performance penalty due to the complex mathematics required.

Ready-made elements can be brought on- screen much faster by using a prerendered bitmap. Displaying it involves but a few calls to memcpy. Our graphic artist has created a few ready-made PNG files for our image.

Click the Add New button next to the Resources header in order to open the resource adding wizard. The identifier field must be provided with a string that makes a valid C constant, which your code will use for finding the resource in question.

The background image is to be added as a normal PNG image. Due to the Pebble’s lack of direct support for bitmasks, the character sprites need to be uploaded twice: both the ‘black-centric’ and the ‘white-centric’ versions should be transferred with a resource type of “PNG Image with transparency”. This leads to a resource structure which is similar to the one shown on the left.

Any embedded resources must be decompressed before use. We accomplish this in handle_bmps, which is to be invoked from handle_init:

void handle_bitmaps(void)
{
   myBG=gbitmap_create_with_resource(RESOURCE_ID_BG_SPRITE);
   myCharWhite=gbitmap_create_with_resource(RESOURCE_ID_CHAR_WHT_WHITE);
   myCharBlack=gbitmap_create_with_resource(RESOURCE_ID_CHAR_BLK_BLACK);
   myEnemyWhite=gbitmap_create_with_resource(RESOURCE_ID_WALL_WHT_WHITE);
   myEnemyBlack=gbitmap_create_with_resource(RESOURCE_ID_WALL_BLK_BLACK);
}

UpdateGame() must be expanded to include the drawing code responsible for handling the bitmaps:

static void updateGame(Layer *layer, GContext *ctx)
{
   ...

   graphics_context_set_compositing_mode(ctx, GCompOpAssign);
   graphics_draw_bitmap_in_rect(ctx, myBG, GRect(-flownWay%144, 0, 144, 159));
   graphics_draw_bitmap_in_rect(ctx, myBG, GRect(144-(flownWay%144), 0, 144, 159));

   graphics_context_set_compositing_mode(ctx, GCompOpClear);
   graphics_draw_bitmap_in_rect(ctx, myCharBlack, GRect(10, (int)totalPos, 20, 30));
   graphics_context_set_compositing_mode(ctx, GCompOpOr); graphics_draw_bitmap_in_rect(ctx, myCharWhite, GRect(10, (int)totalPos, 20, 30));

}

Look carefully at our background tile. You will see that the contents of its right-most border now match the ones on the left: drawing more than one tile next to one another creates a continuous pattern.

We utilise this by drawing the background in two steps. Tile number one is drawn slanted to the left: the farther our character moves, the more of it gets drawn off-screen. The remaining white space is then filled with a second image, which partially overflows off the right-hand side of the display.

Drawing the actual sprite is a bit more involved. The calls to set_compositing_mode determine how the source image is to be rendered onto the underlying canvas. We start out by using Clear, which permits us to write the black parts of the Tux figure. After that, Or is used in order to paint the white eyes and belly of the penguin.

Compositing is quite a complex affair. You can find further information in the official documentation by looking up the term GcompOp at the Pebble developer site.

Add interactivity

Running the game as it stands will yield a moving background and a Tux falling down in a more or less naturally accelerated fashion. Take note to see that its belly is not transparent thanks to our expedient efforts.

Our Pebble has a total of four buttons. The back knob on the left-hand side of the watch closes the currently opened window; repurposing it requires significant effort, so we will restrain ourselves to handling pushes of the middle button. Sadly, handling knob events is quite a procedure under Pebble OS. Let’s start the process by creating a configuration provider function, which will then be invoked from handle_init():

void config_provider(Window *window)
{
   window_raw_click_subscribe(BUTTON_ID_SELECT, sel_click_handler, sel_ release_handler, NULL);
}

void handle_init(void) {
   my_window = window_create();
   window_set_click_config_provider(my_window, (ClickConfigProvider)
config_provider);
   ...

Its raison d’être involves informing the system about your application’s needs in relation to keyboard input. Our implementation registers interest in the central button, which will be routed to the two methods responsible for handling the clicking and unclicking of the button.

void sel_click_handler(ClickRecognizerRef recognizer, void *context)
{
   myIsClicked=true;
}

void sel_release_handler(ClickRecognizerRef recognizer, void *context)
{
   myIsClicked=false;
}

Our game loop analyses the flag. This information is used for determining the acceleration working on the penguin: if the button is held down, an imaginary booster cancels gravity and furthermore provides a slight jolt to the top:

static void updateGame(Layer *layer, GContext *ctx)
{
   totalPos+=moves_per_frame;

   if(myIsClicked)
   {
      moves_per_frame-=0.06;
   }
   else
   {
      moves_per_frame+=0.04;
   }
   ...

Flap away

It’s now time to call it quits for this tutorial. We’ve managed to create the beginnings of a Flappy Bird clone, which further introduced us to a variety of interesting concepts related to game programming. Adding support for enemies and walls should be a breeze.

Things like game loops, sprite handling and basic physics are universal. The knowledge gathered here can be applied to games running on smartphones and PCs – even high-budget game titles such as Call of Duty are based on similar paradigms.

Next time will present an example wall implementation. It will then proceed to look at the second reason for the tremendous success of the Pebble: it can connect itself to the most commonly used smartphones and can display the content stored on them in a more convenient fashion.

We will exploit this by creating a basic Android-based conduit app that interacts with our smartwatch game.

×