James Williams
LinkedInMastodonGithub

Creating Pong with PlayN

If you have followed my work for a while, you've undoubtedly seen my code for Pong. There are good reasons for that. Firstly, it's to pay homage to the first digital computer game. Secondly, Pong, in my humble opinion, is the perfect "Hello World" for gaming.

So as a first game with PlayN, I did Pong. I'll admit in advance that there are more complex ways to code Pong but I stuck pretty close to the code presented in my book (*Learning HTML5 Game Programming*) to show how coding with PlayN compares to using the HTML5 canvas.

Organizing Objects with Layers

Layers are atomic elements for rendering in PlayN. Among other things, layers can be notified of mouse or pointer events, can be tested for collisions, or moved in space. For every game, there is a single rootLayer to which all other layers attach. The rootLayer is in fact an instance of GroupLayer that allows you to make changes to child layers all at once. PlayN can use the root layer and its children to optimize how an individual frame of your scene is rendered. This hierachy is also known as a scene graph.

In addition to GroupLayer, there are three other layer types: ImageLayer, SurfaceLayer, and CanvasLayer. All three can draw images but differ in their usage. ImageLayer is appropriate for images that won't change for the duration for the game. SurfaceLayer is more appropriate for more performant rendering and compositing (like sprite animation). Finally, CanvasLayer provides an API that closely follows the HTML5 Canvas2D Context.

Pong has simple needs so using ImageLayer is perfectly adequate. In the snippet below, we instantiate an Image and ImageLayer. Images are loaded asynchronously so we need to add a callback to set the position and translation of the layer and add it to its parent when done.

public Ball(final GroupLayer parentLayer, final float x, final float y) {
   Image image = assets().getImage(IMAGE);
   layer = graphics().createImageLayer(image);
   pickDirection();

   image.addCallback(new ResourceCallback<Image>() {
       @Override
       public void done(Image image) {
           layer.setOrigin(image.width() / 2f, image.height() / 2f);
           layer.setTranslation(x,y);
           parentLayer.add(layer);
       }

       @Override
       public void error(Throwable throwable) {
           log().error("Error loading image!", throwable);
       }
   });
}

Capturing Keyboard Input

To move our paddles, our Game class implements the Keyboard.Listener interface functions:

  • onKeyDown(Keyboard.Event event)
  • onKeyTyped(Keyboard.TypedEvent event)
  • onKeyUp(Keyboard.Event event)

We only need onKeyDown for our game. One of the cool things about coding with PlayN vs using a JavaScript framework is that for our Keyboard.Events, we don't need to lookup the ASCII key codes for the buttons we want to use. We can instead use simple names like Key.ESCAPE and Key.UP. The snippet below shows our truncated function to respond to keyboard events, in this case, moving a paddle up or down in response to the up and down keys.

@Override
public void onKeyDown(Keyboard.Event event) {
    if (event.key().equals(Key.UP)) {
        if (paddleOne.getY() > 0)
            paddleOne.translateY(-10);
    } else if (event.key().equals(Key.DOWN)) {
        if (paddleOne.getY() < graphics().height())
            paddleOne.translateY(10);
    }
            // truncated ...
}

Summary

In this post, I covered some of the core elements of PlayN and how you would use them to create a simple Pong game. For this project, I didn't have to manually adapt any code to a specific platform. All of my code and logic is in the base Game classes and PlayN smartly translates it.

Check out the source code for this project here.