Monday, May 14, 2012

EEClone PlayN Tech Demo

A little over a week ago, I came across PlayN, a library from Google that allows you to write applications in Java and have them compiled into Java applications, HTML+Javascript Web applications, Flash, Android, and iOS. I had been looking for libraries for HTML5 game development. I've been an advocate for GWT for years despite not having done anything serious with it, and now here's a related library that lets me leverage my Java skills and—at least in theory—produces browser-friendly, plugin-free games.

I decided to use my old EEClone case study as an example. EEClone was a study in design patterns and game development. In the intervening six years, I've gained considerable interest in a few tools and techniques, including test-driven development, entity system architectures, and clean code. I decided to invest the first week of the Summer on an integration of these ideas.

You can see the result of my week's worth of effort online. It's more of a technology demo than a game. Use the arrow keys to move around and space to explode. Lives are not tracked and collisions with obstacles are not processed, nor is there a fancy title screen or scoreboard. Also, in the absence of any optimization, performance degrades over time. With those caveats, I would call it a rousing success.

There is a good tutorial for getting started with PlayN on the project wiki. One of the first things you encounter when tinkering with PlayN is Maven, which I had heard of but not previously used. It took some time to understand what it was actually doing, but once I got over that learning curve, I was blown away by the power of this tool. There's a discussion on the PlayN google group in which some folks are upset about Maven's complexity. It's true that I had some Maven-related headaches, but now that I see what it's doing, it's hard to conceive of another portable, platform-neutral way of managing the incredible complexity of the task.

I tried to do the whole project from within Eclipse, but even after updating the Maven indexes, I could not find the most current (1.2) PlayN files. When I followed the command-line instructions, it worked fine. I am still uncertain why Eclipse's Maven Repositories view shows that the 1.2 files are available and yet the newest archetype I can create from is 1.0.3.

I ported the core of the Morgan's Raid entity system implementation into EEClone-PlayN project. Each of the components and systems were created using test-driven development and Clean Code principles, using JUnit for unit testing (of course) and Mockito for mocking. I had previously only used EasyMock for mocking in Java, but after reading some recommendations for Mockito, I figured I'd give it a try. I am quite pleased with the balance between expressiveness and terseness of the API. For example, here's a test:

@Test
public void testExplosionCreatedNotification() {
    ExplosionFactory.Listener listener = mock(ExplosionFactory.Listener.class);
    factory.addListener(listener);
    factory.createExplosion(0, 0);
    verify(listener).explosionCreated();
}

The listener is mocked, and after creating the explosion, we verify that the explosionCreated method was indeed called, where verify is a static method on Mockito. That's about as tight as it could be.

Here's an example of what makes Maven amazing. I wanted to use Mockito, but only during testing. Previously, I would have downloaded the jar, copied it into my project folder, and updated my build path, thus having access to Mockito. Then, I would be careful to only use it in my unit tests and then exclude it from exportation in my ant build scripts. With Maven, I just dropped into my pom.xml configuration file and added these few lines:

<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-all</artifactId>
    <version>1.9.0</version>
    <scope>test</scope>
</dependency>

Seriously, that's all I had to do, and Maven took care of downloading the library and putting it in the right place.

A similar trick was all I had to do to incorporate Guava. Looking for something unrelated, I came across Guava's EventBus, which looks great. Sadly, it's not gwt-compatible, and so I stuck with my Holub-style publisher/distributor architecture for implementing the Observer design pattern. One of the benefits of this approach is that it avoids concurrent modification exceptions, such as when processing an event results in a modification of the listener list itself.

When I got to the point that I wanted to track framerate, I decided to use the TriplePlay library. This comes from the good people at Three Rings. I know them from Puzzle Pirates, but many of my students are more familiar with Spiral Knights. Three Rings is the largest Java game development shop I know of, and they should be applauded for their community contributions. In addition to TriplePlay, a library to ease PlayN development, they have contributed much of their core engine in open source licenses. I ended up using TriplePlay to handle the FPS/UPS labels as well as the explosion animations. If you pick up PlayN, I'll strongly recommend TriplePlay's animation support. It defaults to a layer-oriented approach. I used a single immediate mode layer for rendering, but I could still use TriplePlay since you can plug in your own interpolation value wrappers. For those who are curious, I did experiment with PlayN's ImageLayer system that is featured in the GettingStarted and demos, but then I came across Lilli Thompson's Cross Platform Games with PlayN presentation slides, and it seemed that immediate mode would work best for my purposes.

I spent about a day trying to incorporate dependency injection into EEClone-PlayN via Gin and Guice, partially based on a question on StackOverflow that implied someone else had got it working. It seems to me that dependency injection would be a convenient way to pass entity managers around to different systems, if nothing else. I never did get this to work, though, and I think it was a bit of a pipe dream. I have been eager to use Guice for DI in a Java project, but because it won't work with GWT, it would have had to be pulled out completely from my game logic anyway. At that point, the benefits just weren't worth it to me.

PlayN is designed so you can quickly and easily debug your program interactively in Java, which worked great. Then, when you want to make sure the HTML+Javascript version is working, you do the much-lengthier GWT compile and test it in the browser. This worked well, except for those cases where I inadvertantly incorporated a non-GWT class in Java, where it was happily included and then only rejected when I attempted GWT compilation (for example, Guava's EventBus).

This morning, I revised the architecture to use the state pattern, as in the original EEClone. I decided to put a moratorium on new features—essentially making this a tech demo rather than a game—and so there are only two states: playing and exploding. Pressing spacebar triggers the transition from playing to exploding, and the end of the explosion chain triggers the return. When I first put this pattern into place, there was no discernible impact on performance. However, when I refactored common code into an abstract state class, the Java version of the program began to slow down. Interestingly, the HTML version did not, although my GWT compilation time nearly tripled. I did not rigorously analyze this situation, but I suspect that the aggressive inlining and optimization from the GWT compiler eliminated the indirection caused by following a Clean Code approach, especially removing all code redundancies and keeping methods to a maximum of four lines.

Once I was reasonably content with the source code, feature set, and stability, I packaged the application using Maven. This produced a war file, which frankly, I did not know what to do with. I was not interested in hosting this tech demo on Google App Engine, and I don't have an application server freely and easily accessible to me. However, EEClone-PlayN does not do any persistence or network communication, so it turns out I could just unpack it into my server space and it worked. Well, it almost worked. The first time I uploaded it, the screen flickered to black erratically. In fact, it would be more appropriate to say it flickered from black to the game, as it was more black than not. On closer inspection, I realized that the FPS/UPS labels were missing. This inspired me to open up the console in Chromium, where I found stack traces from TriplePlay instructing me that the application had to be compiled without disabling class metadata in order for its CSS support to work. Another post on stackoverflow prompted me to stick the following code into my HTML pom.xml file, after the artifactId of the org.codehaus.mojo plugin:

<configuration>
    <disableClassMetadata>false</disableClassMetadata>
</configuration>

Re-compile, re-upload, re-extract, and that's the version you see now online.

One of the observations I made while working on this was how the entity system architecture and TDD work well together to produce modular code. A few times when I was working on user-facing elements—which are notoriously difficult to develop via TDD—I found myself taking too large of steps. This resulted in a lot of badly-written code and wasted time. Pulling back and thinking about the system in smaller pieces allowed me to write unit tests with very quick turnaround. Mockito certainly facilitated modular design as well, as I was able to insert dependencies with one-off mock objects. Of course, I knew all these things going into the project, but it felt great to experience them again first-hand. I've spent the last ten months with my head in Unity3D game development, where the tools make it very easy to slap together systems, but we never found any good solutions for serious software development concerns such as distributed version control, object mocking, or continuous integration.

I want to take a moment to point out what may already be obvious to the reader. I wrote a game in Java using best practices such as test-driven development, design patterns, and Clean Code. I used professional tools of the trade, such as Mockito, Maven, Eclipse, and Guava. The resulting tech demo runs in HTML and Javascript in any modern browser without requiring plugins. It has great performance without investing any time in optimization. This is awesome. Thank you, PlayN developers!

Working on EEClone-PlayN was a great way to spend the first week of the Summer. After months of project management, it was great to get my hands dirty, to play with some of my favorite libraries while checking out some new ones. I have a half-written blog post about why I'm doing this at all. Suffice it to say, this Summer I'm knocking a few monkeys off my back while investing in personal and professional growth. Stay tuned for more!

3 comments:

  1. (I disavow any knowledge of maven*, but...) You might try the -U maven option to force it to update libraries.

    ((*It's firewalled at work where some inherited projects I maintain are maven based...)).

    ReplyDelete
  2. Great post and I share your feelings about playn. It is awesome and great fun to work with. I've worked on my own hobby project with it and I gotta say what you wrote here sounds very familiar. I also work with unity and there as well I agree with you. It's lacking in some very important areas which are given in Java development.

    One note about DI though. You can use gin in your gwt projects to replace guice. You just have to bootstrap the app slightly differently but after that gin works almost identically to guice.

    ReplyDelete
    Replies
    1. I have been doing more work with PlayN since writing this, and I've enjoyed it. I first used it in a formal course last Fall (which I have yet to fully write up for my blog), and my Spring Serious Game Studio is using it for our Bone Wars game. Unit testing and continuous integration have already saved us significant problems.

      Regarding DI, I tinkered with Gin on the GWT build, but I was unable to find any solution that works across the Java and HTML targets. I have resorted to using manual constructor injection, which has been fine although verbose.

      Delete