Wednesday, 11 September 2013

Programming a Game in OCaml

Herein I'll provide an introductory taste of what it's been like making a game in OCaml.

I've been developing a "Tactical RPG" game, which is based on the Ars Magica roleplaying setting and rules. Developement name is "Ars Tactica". (I haven't sought out Atlas Games about legal ramifications, deals, or licensing — not until I have something worthwhile to share!)

There isn't much to show off now, but here's a screenshot:



The figures are placeholders. They're from photos of painted miniatures, found online. At this point I'm using other people's images, fonts, and game system — so what am I doing?

I'm writing code, in a language which should be better known: OCaml.

OCaml is an unusual choice for games. Almost all games are written in C++; before that it was C, and before that ASM. Now we have games being made in C#, Java, Python, and more... and these are imperative languages. OCaml is an unusual choice because, at heart, it's functional.


Rising awareness

In the past couple of years I've watched the growing interest in functional programming with some elation. Game developers have been making forays into it for a bit longer. Chris Hecker tried OCaml way back around 2005. Tim Sweeney did a presentation: The Next Mainstream Programming Language (link is to a PDF of the slides). Carmack has addressed the value of functional techniques applied toward games numerous times: Functional Programming in C++ (blog post), Portion of 2013 keynote (youtube). Of course, there's also Naughty Dog with their scripting language being Scheme-like... since Crash Bandicoot?


How do you make a game in a functional language?

When I was first looking into OCaml (2005), it was beyond my comprehension how a (non-trivial) game could be made. What does functional game code look like!?

Functional code favors return-values, rather than changing a variable in-place. However, typical game code looks like a whole bunch of loops, controlled by counters or other changing variables, with the loop bodies mutating data in-place, step by step. Probably easy to imagine if you think of a loop over "all active game pieces", calling some update function on each — which might loop over sub-components, updating those in turn.

So how do you even do a loop in functional code without some kind of mutable counter? (Well, a practical language like OCaml does support imperative loops... but I rarely use them.) Recursion works...

  let rec loop count =
    Printf.printf "Countdown: %d\n%!" count;
    if count > 0 then loop (count-1)
    else ();;

  loop 10

This will loop with a count-down, feeding back the new count each time. If you think this is pretty terrible, I'll agree — a while or for-loop would be more straight-forward in this trivial example.


Here's a bit of my current main-loop, showing its overall form:

  let rec mainloop ~stage ~actors ~controls ~lasttime =
    let t = time () in
    let dt = min (t -. lasttime) dt_max in
    (* ... *)
    let controls' = Control.run controls surface dt in
    (* ... *)
    if run_state = StateQuit then ()
    else mainloop ~stage ~actors ~controls:controls' ~lasttime:t


  mainloop
    ~stage
    ~actors: ordered
    ~controls: (Control.init [ exit_control; time_control; game_control cam_id ])
    ~lasttime: 0.

It follows the same structure as the simple recursive countdown: initial value(s), a terminal condition, and feeding-back the changing state.

I used labeled parameters here (eg ~controls) to help make it clear what can change from frame-to-frame. Since almost everything can change in a game, the data hiding under stage (for example) might be quite extensive.

Now it might be apparent how functional code looks: rather than updating things in-place, you create new values, (old values are discarded if unused, via garbage-collection). This might seem atrocious from a game-programming perspective: you've already allocated space — re-use it!

Honestly, it took me a while to be able to put aside that concern and just accept the garbage collector. But over time, the continual feedback was a combination of: fewer bugs, more pleasant code with less worry, and the garbage-collector was quiet enough that I rarely notice it. Much like acquiring a taste for a suspicious food, I was hooked once the suspicion was put to rest and the taste turned out to be good.

Note that a sufficiently smart compiler could re-use allocations, effectively generating the same code as in-place mutation — and only in cases where it has deemed this to be safe! I don't know if OCaml does this in any cases, but its garbage collector has been handling my massive per-frame allocations surprisingly well.

Returning to looping, most loops aren't explicit like my mainloop. Instead they are abstracted as a fold, map, or iter. These abstractions are enough to cover most use-cases, but you don't see them in imperative languages because they rely on higher-order functions.


OCaml has imperative features. You can modify values in-place, and I'll sometimes use global references for changable state until I figure out a better fit:

  let g_screenwid = ref 0
  g_screenwid := 800;

Arrays are mutable by default, and I use these for image and vertex data. Most of my game logic and data-structures are immutable lists, trees, zips, or queues.

I've made another post with some of my take on: What is bad about mutable state?


With most of the code striving for immutability, I get to enjoy easier composition of functions. Some of the most elegant code (I think) ends up piping data. An example from the game is part of the "casting score" calculation:

    (value,botch) |> fast_cast fastcasting
                  |> raw_vis pawns
                  |> Realm.(apply_aura aura Magic)
                  |> Modifier.apply magus score'k
                  |> apply_mastery mastery

Here, the computed (value,botch) pair is further modified by circumstances, passed through other systems, finally returning the result of apply_mastery mastery. This is a simple case of such piping, in that the type of input and output is the same at each stage (an integer pair). Often there will be a transformative aspect, which works as long as an output type is matched to the input type in each stage.

This post may be a bit haphazard and rambling, as I'm not clear who my audience might be... game-developers looking into functional programming, or OCaml programmers looking to make a game? I think I tried to cut a middle-ground. I expect future posts will be much more focused.

2 comments:

  1. Which graphics library are you using write your game?

    ReplyDelete
    Replies
    1. I'm using OpenGL (subset which is compatible with 4.0 and ES2.0) with SDL. The bindings I'm using are glcaml+sdlcaml with local modifications. I've read that LablGL now supports shaders and more modern GL... if so, I'd recommend that instead.

      My reasons for using glcaml are that it's easy to add newer GL functionality (very thin bindings), and it's familiar to me from using OpenGL in C.

      On top of this I have my own layer of functionality for fonts, textures, models, and transforms -- still a lot of work to finish, such as the GUI! Daniel Bünzli recently announced his Gg and Vg modules (http://erratique.ch/tags/software). It's very similar to what I've done, though Vg right now is only 2D (but a nice general vector/path renderer -- I only have code to generate signed-distance fields from fonts and svg instead).

      Delete