Cross Platform Games with PlayN

Lilli Thompson, Game Developer Advocate, Google

Nice to meet you!

Overview

What is PlayN?

The basics

In use in the wild!

Why do you need it?

What's the problem?

Isn't HTML5 supposed to solve this?

OK, but why Java?

Box2D for Physics

Demo Source: savagelook.com

What's so cool about GWT?

Where's the HTML5?

How does it work?

PlayN API Structure

API concept

Components of PlayN

The Game Loop

public class MyGame implements Game {
  public void init() {
    // initialize game.
  }
  
  public void update(float delta) {
    // update world.
  }
  
  public void paint(float alpha) {
    // render world.
  }
}

Rendering

// Resize to the available resolution on the current device.
graphics().setSize(
  graphics().screenWidth(),
  graphics().screenHeight()
);
		
// Keep the same aspect ratio.
float sx = graphics().screenWidth() / (float) WIDTH;
float sy = graphics().screenHeight() / (float) HEIGHT;

// Fit to the available screen without stretching.
graphics().rootLayer().setScale(Math.min(sx, sy));

Drawing API

Layer system

Layer types

SurfaceLayer

// Clear the surface.
surface.clear();

// Draw an image into the surface.
surface.drawImage(myImage, x, y);
// Draw a scaled subsection of the source image into the surface.
surface.drawImage(image, dx, dy, dw, dh, sx, sy, sw, sh);

Image fragments

GroupLayer

ImageLayer

CanvasLayer

canvas.setFillColor(Color.rgb(100, 255, 0));
canvas.fillCircle(x, y, r);
canvas.fillRect(x, y, w, h);
canvas.drawImage(myImage, x, y);
canvas.drawText("Hello text!", x, y);
canvas.drawLine(x0, y0, x1, y1);

Layers recap

Layer strategy example

Layer strategy example

Transforms and surfaces

// Like OpenGL push.
surface.save();
					
surface.translate(p.x, p.y);	
surface.rotate(p.r);
surface.scale(p.sx, p.sy);	
// Draw something at transformed coordinates!

// Like OpenGL pop.
surface.restore();

IO System: Platform Abstractions

Most general screen event
Works everywhere Left, right, mid buttons & wheel

IO System

pointer().setListener(new Pointer.Adapter() {
  public void onPointerStart(Pointer.Event event) {
    // Handle mouse down event.
  }
  // ...Same pattern for onPointerEnd, onPointerDrag
});

keyboard().setListener(new Keyboard.Adapter() {
  public void onKeyDown(Event event) {
    // Handle key down event.
  }
  // ... Same pattern for onKeyUp
});	

Cross platform input

import static playn.core.PlayN.platformType;
if (platformType().equals(Platform.Type.ANDROID)) {
  touch().setListener(new Touch.Adapter() {			
    @Override
    public void onTouchStart(Event[] touches) {
      // Process touch events into a zoom start position.
    }	
    @Override
    public void onTouchMove(Event[] touches) {
      // Update zoom based on touches.
    }			
  });
}

Asset Management

public interface AssetManager {

  Image getImage(String path);
  Sound getSound(String path);
  void getText(String path, ResourceCallback callback);
  
  boolean isDone();
  int getPendingRequestCount();
}

Loading Images

// Ask the Asset Manager to create a new image.
Image image = assetManager().getImage("myImage.png");

// Specify an onLoaded callback.
image.addCallback(new ResourceCallback() {
  
  public void done(Image image) {
    // handle new image.
  }
}
// To draw the image that was loaded, render it to a layer.
mySurfaceLayer.surface().drawImage(image, x, y);

Asset Watchers

AssetWatcher watcher = new AssetWatcher(new Listener() {
  public void done() {
    startGame();
  }
});

// Add assets to check.
watcher.add(image1);
watcher.add(image2);
// ...

// Start the watching now.
watcher.start();

Sounds

public interface Sound {
  boolean play();
  void stop();
  void setLooping(boolean looping);
  void setVolume(float volume);
  boolean isPlaying();
}
  assetManager().getSound("mySound.mp3");

Saving data

Storage

/** Abstraction for Storage across platforms. */
public interface Storage {

  public void setItem(String key, String data);
  public void removeItem(String key);
  public String getItem(String key);

  // Returns true if the Storage data will be persisted across restarts.
  public boolean isPersisted();
}

JSON loading

PlayN.assetManager().getText(JSON_FILE, new ResourceCallback() {
  @Override
  public void done(String resource) {
    Json.Object json = PlayN.json().parse(resource);
    Json.Array myArray = json.getArray("myArray");
    int myInt = json.getInt("myInt");
  }

  @Override
  public void error(Throwable err) {
    PlayN.log().error("Failed to load: " + err.getMessage());
  }
});		

JSON saving

Writer writer = PlayN.json().newWriter();

writer.object();
	
writer.key("myInt");
writer.value(123);  

writer.key("myArray");
writer.array();
for (String s : arrayOfStringParams) {
  writer.value(s);
}
writer.endArray();	
writer.endObject();

String output = writer.write();	

Where are we now?

Cross Platform Magic! (Java, HTML5)

public class MyGameJava {
  public static void main(String[] args) {
    JavaAssetManager assets = JavaPlatform.register().assetManager();	
    assets.setPathPrefix("src/myGame/resources");
    PlayN.run(new MyGame());
  }
}
public class MyGameHtml extends HtmlGame {
  public void start() {
    HtmlAssetManager assets = HtmlPlatform.register().assetManager();
    assets.setPathPrefix("myGame/");
    PlayN.run(new MyGame());
  }
}

More Cross Platform Magic! (Android, Flash)

public class MyGameAndroid {
  public static void main(String[] args) {
    AndroidPlatform.register();
    PlayN.run(new MyGame());
  }
}
public class MyGameFlash {
  public static void main(String[] args) {
    FlashPlatform.register();
    PlayN.run(new MyGame());
  }
}

Monetization and Distribution

In-App Payments

In-App Payments Code

private InAppPayments inappPayments = InAppPaymentsFactory.payments();
PurchaseRequest request = new PurchaseRequest.Impl("Gold Star", ...);

inappPayments.encodeJWT(request, new InAppPayments.EncodeJWTCallback() {
  public void successHandler(String JWT) {
    purchaseJWT = JWT;
  }
});

inappPayments.setCallback(new InAppPayments.Adapter() {
  public void successHandler(PurchaseResponse result) {
    PlayN.log().info("Congratulations! You've bought a product!");
  }
});

inappPayments.buy(purchaseJWT);

World Golf Tour + Chrome Web Store FTW

"Chrome users play 23% more than those from other ... sources, and are buying virtual goods ... [at] 147% more than average."

World Golf Tour Logo

Learn more

Spryfox + Chrome Web Store FTW

Early indications ... traffic from Chrome Web Store is twice as sticky as traffic from other sources.

Realm of the Mad God

Sounds Great! How do I start?

What's next for PlayN?

... Samples, libraries, modules [your component here]!

Wrapping up

Check out developers.google.com/playn today!

Join us at New Game. Use coupon HTML5ROCKS for 15% off registration

New Game Logo