ENGi - Making Games with Go

The 5 second pitch: ENGi is a cross platform game library that supports Mac, Linux, Windows, Web, (soon) native Android, and (right now) iOS and Android through CocoonJS. You can check out the project on Github.

Back-story

I have been trying to create a cross platform 2D game engine in Go for quite a while now. There have been a few good starts, but building for some important platforms (like the web and mobile) was impossible. Even building and distributing for desktop platforms has been nontrivial. All that has changed recently.

Go -> Web

If you haven’t heard of GopherJS, do yourself a favor and check it out right now. It allows you to compile just about any Go to Javascript. The js interop is pretty great and the resulting code is efficient. Recently, the maintainer added goroutine support, so there is nothing holding it back now.

GopherJS comes with a build flag, just like every other platform Go supports. What this means is you can separate your web specific code in a file with ‘// +build netgo’ and your desktop specific code in a file with ‘// +build !netgo’ and then share the rest of the code between the two.

I have used this feature to abstract out the windowing, rendering, and file handling for each platform so that the rest of the engine is generic. A game can be compiled for desktop or web, and it will behave exactly the same.

Mobile

GopherJS also opens up the possibility for releasing a game on iOS and Android by using CocoonJS. ENGi is already working pretty well in their simulator. I intend to fully support this option with bug fixes and performance enhancements.

There is already a project that lets you compile Go programs to Android goandroid. The project seems to work pretty well but is on hiatus because… Go 1.4 seems to be getting native support for compiling to Android. I will start supporting that option as soon as it hits the dev branch.

Tooling

Getting started with GopherJS is as simple as ‘go get’. The build work flow is very similar to the ‘go’ tool. Each new project, though, needs an html file that loads the compiled javascript. Every time you make a change you will need to recompile and reload. This process makes trying out new ideas a bit slow.

I have created a project SRVi that you can run in the same directory as your GopherJS project. If your game is in main.go, you can access http://localhost:8080/ and SRVi will compile and serve it for you. Each time you refresh the page, your game is recompiled. See the docs for more options.

The other major headache is with packaging up a native game for every platform. I am working on another utility BLDi to make the process as easy as possible. Right now the tool only works for OSX and needs more work.

It is able to generate all the needed files and icons, build the .app file, copy all needed dynamic library dependencies, and rewrite the binary to point to the local libs. All resources your game uses will also be bundled up. In the future I will support Windows and Linux as well.

Scala on iOS

Now that RoboVM has started to mature, I decided to see how viable Scala on iOS can really be. I have released an sbt plugin, sbt-robovm, that is nearing a 0.1.0 release and added support for that in libgdx-sbt as a proof of concept. After spending many days trying to wrangle Xamarin / IKVM into compiling Scala, it is satisfying to finally see games running on a real iOS device.

I’ll talk more about the use of libgdx-sbt, especially the iOS bits, another day. Today’s post is going to focus on using the cocoatouch APIs to create an actual Scala iOS app.

RoboVM

RoboVM is a JVM bytecode ahead-of-time compiler and runtime library that can target iOS devices. It is based on the Android libraries, so you should be able to share your backend code between the iOS and Android versions of your app. This is similar to the approach you would take with the Xamarin products.

At the moment, sbt-robovm does not download and manage a RoboVM distribution install for you like the Maven plugin does, so you will have to get that set up first. Download the latest version and unzip it.

RoboVM searches for the distribution in a few standard places when compiling. The default is $ROBOVM_HOME, so feel free to install anywhere and set the environment variable in your .profile. It will also search in some standard places:

  • ~/Applications/robovm/
  • ~/.robovm/home/
  • /usr/local/lib/robovm/
  • /opt/robovm/
  • /usr/lib/robovm/

sbt-robovm

The next step will be setting up your sbt project to use RoboVM. Until there are giter8 templates ready, this will have to be done manually, but luckily it is a pretty straight forward process.

Create three files in your project directory:

build.properties

sbt.version=0.12.4

plugins.sbt

resolvers += Resolver.url("scalasbt snapshots", new URL("http://repo.scala-sbt.org/scalasbt/sbt-plugin-snapshots"))(Resolver.ivyStylePatterns)

addSbtPlugin("com.hagerbot" % "sbt-robovm" % "0.1.0-SNAPSHOT")

build.scala

import sbt._
import Keys._

import sbtrobovm.RobovmPlugin._

object ScaliOSBuild extends Build {
  lazy val hello = RobovmProject("hello", file("."),
      settings = Defaults.defaultSettings ++ Seq(
        scalaVersion := "2.10.2",
        executableName := "Hello, RoboVM"
      )
    )
  }
}

As you can see, we are using Scala 2.10.2 and sbt 0.12.4 because sbt-robovm has not yet been tested with anything else. There are plenty of options that can be customized like adding native paths, using an Info.plist file, or setting iOS frameworks to be linked into the executable. For now, all we are going to customize is the name of the app.

Code

First we need to import some libraries that wrap cocoatouch and set up an entry point for our program.

Main.scala

import org.robovm.cocoatouch.coregraphics._
import org.robovm.cocoatouch.foundation._
import org.robovm.cocoatouch.uikit._

object Main {
  def main(args: Array[String]) {
      val pool = new NSAutoreleasePool()
      UIApplication.main(args, null, classOf[Main])
      pool.drain()
  }
}

Every iOS app needs an application delegate. The one below is both simple enough for a first program and interesting enough to prove that you can do something useful with RoboVM right now. The code should look familiar if you are used to the Objective-C APIs, but the transformation isn’t 1 to 1. You will need to use the documentation and/or code as a guidebook when translating to the RoboVM way of doing things.

Main.scala

class Main extends UIApplicationDelegate.Adapter {
  var window: UIWindow = _
  var alert: UIAlertView = _

  override def didFinishLaunching(application: UIApplication) {
    alert = new UIAlertView()
    alert.setTitle("Hello, Human!")
    alert.setMessage("Would you like to play a game?")
    alert.addButton("Yes")

    val button = UIButton.fromType(UIButtonType.RoundedRect)
    button.setTitle("Hello, Robo!", UIControlState.Normal)
    button.setFrame(new CGRect(0, 0, 200, 100))

    button.addOnTouchUpInsideListener(new UIControl.OnTouchUpInsideListener {
      override def onTouchUpInside(c: UIControl, e: UIEvent) {
        alert.show
      }
    })

    window = new UIWindow(UIScreen.getMainScreen().getBounds())
    window.setBackgroundColor(UIColor.lightGrayColor())

    window.addSubview(button)
    val bounds = button.getSuperview().getBounds()
    button.setCenter(new CGPoint(bounds.origin.x + bounds.size.width / 2, bounds.origin.y + bounds.size.height / 2))

    window.makeKeyAndVisible()
  }
}

The only thing left to do at this point is to test it out on a device or simulator. In sbt-robovm you can run on device, iphone sim, or ipad sim. More options, like retina simulators and building an ipa, will be added in the future.

    $ sbt device
    $ sbt iphone-sim
    $ sbt ipad-sim

You can find the full project source for this demo, as well as many other demos to come, at Scala iOS Demos. Feel free to leave questions and comments there, or send a pull request with a small demo app of your own!

The Future

There is a lot of room here for building DSLs and wrapper libraries on top of Scala to make app development simpler. RoboVM shares the same niche as RubyMotion in this regard, and it will be interesting to see what can be learned from that community.

RoboVM is still in alpha, but it is already proving itself useful as games and apps continue to be accepted into the app store. Here is to a bright and interesting future.