Java 8: Awesome Changes for Game Developers

Java8, releasing March 18 2014, has several great changes to it. One in particular has me interested from the aspect of game development and AI, and that is Lambda expressions.

A common structure in games is an event system. Register an entity or agent to listen to certain events and then respond to them. For example:

eventSystem.registerEventListener(“buildingDestroyed”, listeningEntityID, new EventListener() {
    public void onEvent() {
        System.out.println(“Run away!”); // respond to event here
    }
});
eventSystem.broadcastEvent(“buildingDestroyed”, buildingID);

Here we have an Event System that lets listeners register to particular events and a way to broadcast events to those listeners. In this example there is a “buildingDestroyed” event that is registered by an entity with the ID ‘listeningEntityID’. It’s response to the event is an anonymous inner class, with a single method: onEvent. There is handles the event.

This has been the way Java has handled single-method classes for a long time now (what is known as a Functional Interface). It’s a bit clunky, you end up with another interface class floating around, and there is extra code you just don’t use. There’s just nothing elegant about it.

To overcome this limitation I have extracted a lot of code out to Groovy where I can use Closures to clean it up. But I don’t always want to go into Groovy and set up bindings to the scripts and classes. Luckily Java 8 is giving us Lambda Expressions to clean all of this up.

Here’s what the above listener implementation would look like:

EventListener listener = () -> System.out.println(“Run away!”);
eventSystem.registerEventListener(“buildingDestroyed”, listeningEntityID, listener);

If there is only one line in the lambda, then you can use the expression as I have above, but if you want a more complex response you can surround it with braces like so:

EventListener listener = () -> {
    System.out.println(“Run away!”);
    System.out.println(“Find a place to hide”);
}

Lets tighten it up a little more and inline the whole expression:

eventSystem.registerEventListener(“buildingDestroyed”, listeningEntityID, () -> System.out.println(“Run away!”) );

That’s much shorter and cleaner.

Now we still need our EventListener interface. The implementation of EventSystem.registerEventListener needs to know what parameters it is accepting and it shouldn’t care that is might get a lambda expression. It will look like this:

interface EventSystem {
    void registerEventListener(String eventName, int entityId, EventListener listener);
}

 

Scoping in Lambda expressions

So what variables do you have access to in a lambda expression? Well they are kind of like closures in that sense: you get access to variables outside your immediate lexical scope.

Example:

class Bob {

    private String name = “Bob”;

    public void setupEvents(EventSystem eventSystem) {
        eventSystem.registerEventListener(“buildingDestroyed”, listeningEntityID,  () ->
        System.out.println(name+“ is running away from a destroyed building!”) );
    }
}

 

This would output:

Bob is running away from a destroyed building!

 

Other Uses in Games

Finite state machines are very common in game AI. You want an agent to perform tasks, and to control when it performs those tasks you need to have some method of organization, usually via states. Finite state machines allow an agent to switch between one state to another, say from gather logs to chop firewood.

Usually you would have a State class that has a perform() or update() method that is called every frame. Each state class would have its own implementation of what is is supposed to do. So for the GatherLogsState it would send the agent to a tree and then chop it down; when completed, move to the ChopFirewoodState . The ChopFirewoodState would require that there already be some wood in its possession and would then proceed to chop that wood. When done it would switch to the GatherLogsState.

Defining classes for each state can be a pain and pollute your scripts folder, especially if you are using slightly different different actions for a state that are small to begin with. It can be much cleaner and easier to supply the state’s update() action as a lambda expression:

interface State {
    void update();
}

interface Agent {
    void init();
    void update();
}

class FirewoodAgent implements Agent {

    private State currentState;

    private State gatherLogs = () -> {
        findTree();
        chopTree();
        pickUpLogs();
        currentState = chopFirewoodState; // switch states
    }

    private State chopFirewoodState = () -> {
        pickUpAxe();
        chopLogs();
        currentState = gatherLogs; // switch states
    }

    public void init() {
        currentState = gatherLogs;
    }

    public void update()  {
        currentState.update();
    }
}

That is a gross simplification, but you get the idea.

Method References

Another great feature related to lambda expressions in Java 8 is Method References. Here we can reuse existing code in our classes as a lambda expression; no need to write the code again if we don’t have to.

Say we have a Tree class that describes the type of wood it is composed of. That wood has a moisture level that is more desirable depending on what it is used for. We also define an Axe class that converts a Tree into Logs, where Log also has a moisture rating. We use a TreeFinder interface that picks the best log for the Axe to chop. This interface will have several implementations, each one passed to Axe depending on what is wanted. Finally we have an old TreeUtility class for finding the correct tree for the job at hand. It contains some tree finding code.

class Tree {
    public String name;
    public int moistureLevel;
}

/**
* Find the best tree.
*/
interface TreeFinder {
    Tree find(Collection<Tree> trees);
}

class Log {
    public int moisture;
}

/**
* Converts trees into wood.
*/
class Axe {

    public static Log chop(TreeFinder treeFinder, Collection<Tree> trees) {
        Tree bestTree = treeFinder.find(trees);
        Log log = new Log();
        log.moisture = tree.moistureLevel;
        return log;
    }
}

/**
* Some static utility methods to find the perfect tree for the job.
*/
static class TreeUtility {

    public static Tree getMoistestTree(Collection<Tree> trees) {

        Tree moistestTree = null;
        int moistest = 0;

        for (Tree t : trees) {
            if (moistestTree == null || t.moistureLevel > moistest) {
                moistestTree = t;
                moistest = t.moistureLevel;
            }
        }
        return moistestTree;
    }

    public static Tree getDriestTree(Collection<Tree> trees) {

        Tree driestTree = null;
        int driest= 0;

        for (Tree t : trees) {
            if (dryestTree == null || t.moistureLevel < driest) {
                driestTree = t;
                driest= t.moistureLevel;
            }
        }
        return driestTree;
    }

}

We want to use the Axe to chop trees and produce Logs. The Axe’s chop() method uses the TreeFinder to pick the best tree. Normally we would create separate subclasses of TreeFinder to pick what tree we want and pass them to the chop() method. But since there is a utility class laying around that does that for us, TreeUtility, we can use its code instead.

To do that we use a Method Reference:

Collection<Tree> allTrees = GameWorld.findAllTrees(); // made up utility method
TreeFinder dryTree = TreeUtility::getDryestTree;
TreeFinder wetTree = TreeUtility::getMoistestTree;
Axe.chop(dryTree, allTrees);

This works because, as we can see, the signature of the getDryestTree() and getMoistestTree() methods match the signature of TreeFinder.find(). The code will be substituted in and the correct tree will be found. In this example we are want to chop dry trees.

Conclusion

The way to think about Lambda expressions is “code as data”. Instead of passing around primitive data types and objects as parameters you are new passing around code chunks.

Some care must be taken when architecting your code in this way. In many applications it would just confuse the whole codebase and fragment the logic leaving you wondering “why is it doing this weird thing here in just the one case, and where in the code is it doing this?” Games by nature allow for this fragmented code. Game objects usually have their own implementation of an action or response to an event, especially for AI. So it will feel natural to use lambda expressions throughout your entities and agents.

Method references are a little tricky and I’m not sure how robust they are to refactoring. It was a bit tricky to think up a use-case example for this blog post, but I’m sure there’s a better one out there that applies well to games.

I hope you will be excited to upgrade to Java8 and give these a try! It should make your game development more enjoyable.

Please note that I have not compiled these code examples! Take them as pseudo code.

2 thoughts on “Java 8: Awesome Changes for Game Developers

  1. jmaasing says:

    You can sort of turn a few things around. Like this

    persistence.save((ObjectOutputStream oos) -> {
    oos.writeObject(this.foo);
    });

    The persistence class handles the stream, opening, closing, errors et c.
    My ‘this’-class doesn’t have to implement a ‘Savable’ interface or be special in any way. A nice compact way of letting the persistence class initialize a resource, let the caller use the resource and then clean up.

    • bowens says:

      That’s really nice! I wish the they had brought these features into Java several years ago. Better late than never I guess.

Leave a Reply

Your email address will not be published. Required fields are marked *


4 × six =