A Game Engine in the Elm Style!

A ‘Nu’ way to make games!

The Nu Game Engine was the world’s first practical, purely-functional game engine. And it has recently accomplished another first — allowing developers to use Elm-style architecture (AKA, model-view-update) to build their games in the cleanest, most understandable possible way!

This article will go over two examples often used by the Elm developer written in Nu. The first shows an tiny Elm-style UI in Nu, and the second a tiny little Mario-like example.

Because it is simpler, let’s start by looking at the canonical Elm-style UI example in Nu –

The full code is as follows (see it on Github here) –

namespace Nelmish
open Prime
open Nu
open Nu.Declarative

// this is our Elm-style model type
type Model =
    int

// this is our Elm-style message type
type Message =
    | Decrement
    | Increment
    | Reset

// this is our Elm-style game dispatcher
type NelmishDispatcher () =
    inherit GameDispatcher<Model, Message, unit> (0) // initial model value

        // here we handle the Elm-style messages
        override this.Message (model, message, _, _) =
            match message with
            | Decrement -> just (model - 1)
            | Increment -> just (model + 1)
            | Reset -> just 0

        // here we describe the content of the game including its one screen, one layer, three
        // button entities, and one text control.
        override this.Content (model, _, _) =
            [Content.screen Default.Screen.Name Vanilla []
                [Content.layer Default.Layer.Name []
                    [Content.button "Decrement"
                        [Entity.Text == "-"
                         Entity.Position == v2 -256.0f 64.0f
                         Entity.ClickEvent ==> msg Decrement]
                     Content.button "Increment"
                        [Entity.Text == "+"
                         Entity.Position == v2 0.0f 64.0f
                         Entity.ClickEvent ==> msg Increment]
                     Content.text "Counter"
                        [Entity.Text <== model --> scstring
                         Entity.Position == v2 -128.0f -32.0f]
                     Content.entityIf (model --> isNonZero) $ fun _ _ _ ->
                        Content.button "Reset"
                            [Entity.Text == "Reset"
                             Entity.Position == v2 -128.0f -128.0f
                             Entity.ClickEvent ==> msg Reset]]]]

This example code creates a Nu game program that shows a + button as well as a button that automatically change the numeric value of the counter button when clicked.

Additionally, there is a reset button that will revert the counter to its original value (and only exists when the value has changed).

Let’s step through each part of the code –

// this is our Elm-style model type
type Model =
    int

Here we have the Model type that users may customize to represent their simulant’s ongoing state. Here we use just an int to represent the counter value shown by the Counter label. If we were to write, say, a custom Text widget, the Model type would be a sting instead of an int. You’re not limited to primitive types, however — you may make your model type as sophisticated as you see fit.

// this is our Elm-style message type
type Message =
    | Decrement
    | Increment
    | Reset

Next is the Message type that represents all possible changes that the Message function will handle (in Elm, the Message function is called Update, but we don’t call it Update in Nu because that function already exists as a lower-level function for simulant ticking).

// this is our Elm-style game dispatcher
type NelmishDispatcher () =
    inherit GameDispatcher<Model, Message, unit> (0) // initial model value

This code does three things –

  1. It declares a containing scope for the Elm-style function overrides (seen coming up next) and packages them as a single plug-in for use by external programs such as Nu’s world editor, Gaia.
  2. It allows the user to specify the Elm-style model, message, and command types. Here we pass unit for the command type since this simulant doesn’t utilize commands.
  3. The base constructor function takes as a parameter the initial Model value (here, 0).

Let’s look at the next bit –

        // here we handle the Elm-style messages
        override this.Message (model, message, _, _) =
            match message with
            | Decrement -> just (model - 1)
            | Increment -> just (model + 1)
            | Reset -> just 0

This is the Message function itself. All it does is match each message it receives to an expression that changes the Model value in an appropriate way.

“But what is that just function?”

Good question! Strictly speaking, the Message functions return both a new Model as well as a list of commands for processing by the unused Command function. But since we’re not using commands here, I use the just function to automatically pair the changed model value with an empty command list. It’s just a little bit of syntactic sugar to keep things maximally readable!

    // here we describe the content of the game including its one screen, one layer, three
    // button entities, and one text control.
    override this.Content (model, _, _) =
        [Content.screen Default.Screen.Name Vanilla []
            [Content.layer Default.Layer.Name []
                [Content.button "Decrement"
                    [Entity.Text == "-"
                     Entity.Position == v2 -256.0f 64.0f
                     Entity.ClickEvent ==> msg Decrement]
                 Content.button "Increment"
                    [Entity.Text == "+"
                     Entity.Position == v2 0.0f 64.0f
                     Entity.ClickEvent ==> msg Increment]
                 Content.text "Counter"
                    [Entity.Text <== model --> scstring
                     Entity.Position == v2 -128.0f -32.0f]
                 Content.entityIf (model --> isNonZero) $ fun _ _ _ ->
                    Content.button "Reset"
                        [Entity.Text == "Reset"
                         Entity.Position == v2 -128.0f -128.0f
                         Entity.ClickEvent ==> msg Reset]]]]

Finally, we have the Content function. The Content function is mostly equivalent to the View function in Elm. Here the Content function defines the game’s automatically-created (and destroyed as is needed) simulants. Studying this structure, we can see that we have a simulant structure like this –

The Content function declares that the above hierarchy is instantiated at run-time.

                [Content.button "Decrement"
                    [Entity.Text == "-"
                     Entity.Position == v2 -256.0f 64.0f
                     Entity.ClickEvent ==> msg Decrement]

Each Content clause can also define its respective simulant’s properties and event handlers in a declarative way. Here we have the DecrementButton’s Text property defined as “-”, its Position translated up and to the left, and its ClickEvent producing the Decrement message that was handled above.

                 Content.button "Increment"
                    [Entity.Text == "+"
                     Entity.Position == v2 0.0f 64.0f
                     Entity.ClickEvent ==> msg Increment]

Here we see how the CounterText derives its Text property by mapping the model with the scstring function. scstring is just a special string conversion function and in functional terms the operator maps the right-hand side function over the model. We also see it producing the Reset message off of its ClickEvent

                 Content.entityIf (model --> isNonZero) $ fun _ _ _ ->
                    Content.button "Reset"
                        [Entity.Text == "Reset"
                         Entity.Position == v2 -128.0f -128.0f
                         Entity.ClickEvent ==> msg Reset]]]]

And lastly, here we have the special entityIf function that defines an entity that exists only while the given predicate is satisfied. In this case, the predicate is (model → isNonZero). So, as long as the model value is non-zero, the entity will exist. Otherwise, it will not. The engine takes care of creating and destroying the entity accordingly.

In detail, the entityIf function is implemented in terms of the entities function. The entities function defines a list of entities that each exist so long as they have a corresponding mapping from the model. In this way, the Content function is like Elm’s View functions — simulants are automatically created and destroyed by the engine according to whether or not they map from the model. This is the power of declarative programming as enabled by the Elm style.

Now let’s look at Elm-Mario in Nu (see it on Github here) –

The code here is a bit more involved. It demonstrates how Nu’s uses its scalable built-in physics engine by applying forces rather than using ad-hoc physics routines –

namespace Elmario
open Prime
open Nu
open Nu.Declarative
module Simulants =

    // here we create an entity reference for Elmario. This is useful for simulants that you want
    // to refer to from multiple places
    let Elmario = Default.Layer / "Elmario"

// this is our Elm-style command type
type Command =
    | Jump
    | MoveLeft
    | MoveRight
    | Nop

// this is our Elm-style game dispatcher
type ElmarioDispatcher () =
    inherit GameDispatcher<unit, unit, Command> (())

    // here we define the bindings used to connect events to their desired commands
    override this.Bindings (_, game, _) =
        [game.KeyboardKeyDownEvent =|> fun evt ->
            if evt.Data.KeyboardKey = KeyboardKey.Up && not evt.Data.Repeated
            then [cmd Jump]
            else [cmd Nop]
         game.UpdateEvent =|> fun _ ->
            if KeyboardState.isKeyDown KeyboardKey.Left then [cmd MoveLeft]
            elif KeyboardState.isKeyDown KeyboardKey.Right then [cmd MoveRight]
            else [cmd Nop]]

        // here we handle the Elm-style commands
        override this.Command (_, command, _, world) =
            let world =
                match command with
                | MoveLeft ->
                    let physicsId = Simulants.Elmario.GetPhysicsId world
                    if World.isBodyOnGround physicsId world
                    then World.applyBodyForce (v2 -30000.0f 0.0f) physicsId world
                    else World.applyBodyForce (v2 -7500.0f 0.0f) physicsId world
                | MoveRight ->
                    let physicsId = Simulants.Elmario.GetPhysicsId world
                    if World.isBodyOnGround physicsId world
                    then World.applyBodyForce (v2 30000.0f 0.0f) physicsId world
                    else World.applyBodyForce (v2 7500.0f 0.0f) physicsId world
                | Jump ->
                    let physicsId = Simulants.Elmario.GetPhysicsId world
                    if World.isBodyOnGround physicsId world then
                        let world = World.applyBodyForce (v2 0.0f 2000000.0f) physicsId world
                        World.playSound 0.5f (asset "Gameplay" "Jump") world
                    else world
                | Nop -> world
            just world

        // here we describe the content of the game including elmario, the ground he walks on, and a rock.
        override this.Content (_, _, _) =
            [Content.screen Default.Screen.Name Vanilla []
                [Content.layer Default.Layer.Name []
                    [Content.character Simulants.Elmario.Name
                        [Entity.Position == v2 0.0f 0.0f
                         Entity.Size == v2 144.0f 144.0f]
                     Content.block "Ground"
                        [Entity.Position == v2 -384.0f -256.0f
                         Entity.Size == v2 768.0f 64.0f
                         Entity.StaticImage == asset "Gameplay" "TreeTop"]
                     Content.block "Rock"
                        [Entity.Position == v2 320.0f -192.0f
                         Entity.Size == v2 64.0f 64.0f
                         Entity.StaticImage == asset "Gameplay" "Rock"]]]]

First we see something new – an explicit simulant reference for our main entity, Elmario –

// here we create an entity reference for Elmario. This is useful for simulants that you want
// to refer to from multiple places
let Elmario = Default.Layer / "Elmario"

This simply allows us to refer to our Elmario entity from multiple places in the code without duplicating its name string.

Also new here is the use of our game physics system. Because the physics here update the engine state (yet still in a purely functional manner), Elm-style commands are used rather than messages.

// this is our Elm-style command type
type Command =
    | Jump
    | MoveLeft
    | MoveRight
    | Nop

Here we have three commands, one to move the character left, one to move him right, and one to make him jump! Oh, and then there’s Nop, which is a command that doesn’t do any operations but does allow us to make bindings that may or may not result in a command (as we’ll see below).

// this is our Elm-style game dispatcher
type ElmarioDispatcher () =
    inherit GameDispatcher<unit, unit, Command> (())

Since we don’t need a model this time, we simply pass unit for the first type parameter. And since we’re using just commands (no messages this time), we pass unit for the second type parameter and Command for the third type parameter.

Next up, we will use Nu’s ability to define bindings explicitly instead of from simulant property lists –

// here we define the bindings used to connect events to their desired commands
override this.Bindings (_, game, _) =
    [game.KeyboardKeyDownEvent =|> fun evt ->
        if evt.Data.KeyboardKey = KeyboardKey.Up && not evt.Data.Repeated
        then [cmd Jump]
        else [cmd Nop]
     game.UpdateEvent =|> fun _ ->
        if KeyboardState.isKeyDown KeyboardKey.Left then [cmd MoveLeft]
        elif KeyboardState.isKeyDown KeyboardKey.Right then [cmd MoveRight]
        else [cmd Nop]]

Let’s break each part down –

    [game.KeyboardKeyDownEvent =|> fun evt ->
        if evt.Data.KeyboardKey = KeyboardKey.Up && not evt.Data.Repeated
        then [cmd Jump]
        else [cmd Nop]

The first binding we encounter is one that updates every frame. It checks if the left or right arrow keys are down and, if so, issues a command to walk in that direction (otherwise, Nop).

     game.UpdateEvent =|> fun _ ->
        if KeyboardState.isKeyDown KeyboardKey.Left then [cmd MoveLeft]
        elif KeyboardState.isKeyDown KeyboardKey.Right then [cmd MoveRight]
        else [cmd Nop]

The second binding works instantaneously on a key down event. When the up arrow key is pressed, the Jump command is issued.

                | MoveLeft ->
                    let physicsId = Simulants.Elmario.GetPhysicsId world
                    if World.isBodyOnGround physicsId world
                    then World.applyBodyForce (v2 -30000.0f 0.0f) physicsId world
                    else World.applyBodyForce (v2 -7500.0f 0.0f) physicsId world

When we encounter the MoveLeft command, we don’t actually move the character by setting its position. For a physics-based entity like the one we have here, that position would only be overridden by the physics system. Instead, when we have a physics-based object, we must issue apply forces to the physics body that correlates with its physics ID. So first, we get the entity’s physics ID, and then we apply a leftward force vector by calling World.applyBodyForce passing the physics ID. Note that we check to see if the physics body is on the ground, and if it is, we apply more force than if it were in the air. This is to limit how much movement can take place while jumping (just like an actual Mario game!)

            | Jump ->
                let physicsId = Simulants.Elmario.GetPhysicsId world
                if World.isBodyOnGround physicsId world then
                    let world = World.applyBodyForce (v2 0.0f 2000000.0f) physicsId world
                    World.playSound 0.5f (asset "Gameplay" "Jump") world
                else world

Jumping is done similarly, but we also tell the engine to play a jump sound when the entity performs his acrobatic feat.

Having studied the code for previous example, Nelmish, the rest of the code for Elmario should be self-explanatory.

Zoom Out

So that wraps up the introductory explanation, but let’s zoom out to add some interesting conceptual detail. In Nu, unlike Elm, this approach is fractal. Each simulant, be it a Game, a Screen, a Layer, or an Entity, is its own self-contained Elm-style program. In this way, Nu is perhaps more sophisticated than Elm — it’s more like a hierarchy of dynamically-configured Elm programs where each one can be individually loaded by the game as needed, and configured by external programs such as Gaia, the real-time world editor. This gives Nu an additional level of pluggability and dynamism that is required for game development.

Iterative Functional Reactive Programming (iFRP) is a great new way to build games, and the new Elm-style architecture makes it even easier to use! By leveraging these powerful new tools, we turn game development from being big-ball-of-mud OO nightmare into a task that is fun again!

Likely Questions

Let’s wrap up with some questions that people might be likely to ask about this approach –

Q. “Why not just use Elm to make games?”

A. Well, unfortunately, Elm isn’t really set up for that. Unlike Nu, Elm does not (and perhaps cannot) integrate a fast, imperative physics engine. Elm doesn’t include a WYSIWYG editor like Gaia. Elm doesn’t have a game-centric asset pipeline. Elm was not build to scale in the way that a modern game engine must. There are so many game-specific tasks that an engine needs that are entirely out of the scope of Elm’s intended use case. In short, Elm wasn’t designed to build games — but Nu was.

Q. “Can someone build a similar Elm-style programming API for Unity?”

A. The answer is, IMO, a firm ‘No’. In order to expose an Elm-style interface, you need an iFRP back-end. For performance reasons, the only backend that Unity could use would be React-style FRP, which, being based on tree-diffing, would be way too slow. Unlike Unity — and all other existing commercial game engines, an iFRP backend was built directly into Nu as one of its primary features.

Q. “What about the performance of using this high-level programming interface?”

A. Well, it depends on what you’re using it for. When you’re writing high-level simulants like UI controls, Character Controllers, Game, Screen, and Layer simulants, this is a sufficiently performant approach in all likely cases. Where it is not sufficiently performant, however, is when encoding things like bullet entities in a bullet hell shooter. The Elm-style API is a good approach by default, but when you need simulants that appear in the thousands, you’ll be using a more low-level approach such as with Nu’s imperative entities (and the upcoming data-oriented entities). That’s good though, because you get simplicity-by-default — and scalability when you need it. That’s what functional programming is all about!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: