Bayaya - PC game developed in Scala

I’ve been working on a game called Bayaya, which is now nearing its Early Access release on Steam.

Before this, I was lead programmer and co-founder of Bohemia Interactive, working on titles like Operation Flashpoint, Arma, and Arma 2, all programmed in C++.

I’ve recently written a post reflecting on my experience using Scala in game development. You can read it here:

The functional programming advocated by Scala was something we liked from the start. Imagine how delighted we were when we saw that solving data access concurrency issues was possible by completely avoiding them using immutable objects! We have experienced the thrill of writing code which is beautiful and elegant, while also simple and efficient.

Hope you find it interesting!

14 Likes

Do you use a custom engine? Some known libraries? What is the architecture looking like?

1 Like

I use a custom engine, for the rendering I use custom port of three.js to Scala JVM. The list of open source libraries I use is quite extensive.

Some of the more prominent are:

  • LWJGL for OpenGL
  • Flying Saucer and GLGraphics2D for HTML rendering (contemplating switching to Ultralight)
  • Rhino to run scripts
  • Borer for JSON/CBOR serialization
  • Quicklens
  • ScalaTest / ScalaCheck

I really like being part of Open Source communities.

Some fun facts - while working on the project I have:

  • implemented about 100 of Pull Requests which got accepted (Three.js, Quicklens, Borer, Flying Saucer)
  • reported about 50 Scala 3 issues
  • reported uncounted number of JetBrains Scala plugin issues
7 Likes

As for the architecture, the scene representation is dictated by Three.js. For the representation of the world we use immutable state, with static and dynamic objects being stored in separate quad-trees. Using immutable state allows us to run simulation in parallel with rendering and simulation of individual entities in parallel to each other.

To define entity behaviour we use “plans” which are slightly modifed futures running on custom executors. Here is an example which defines how to “care about a flower bed”:

    protected override def process: ProcessAction = { (npc, options) =>
      // gardenPlants contain representatives only
      // all items (flowers, carrots...) can be found in actionClusters
      // note: we assume no hiding here
      val allItems = place.actionClusters.flatMap(item => item.highlight.include.toSeq.map(path => item -> path))
      val itemCount = allItems.size
      val rng = scala.util.Random
      val plan = npc.plan
      val todoCount = rng.between(5 min itemCount, 20 min itemCount + 1)
      @tailrec
      def processRandomly(count: Int, todo: Set[Int], done: List[Int]): List[Int] = {
        if (count <= 0) done // reverse does not matter, order is random anyway
        else {
          val next = rng.nextInt(todo.size)
          val value = todo.view.drop(next).head
          processRandomly(count - 1, todo - value, value :: done)
        }
      }
      val todo = processRandomly(todoCount, (0 until itemCount).toSet, Nil)
      plan.repeatForEach(todo) { index =>
        if (!plan.value.finishAsSoonAsPossible(plan.state)) {
          val (workItem, path) = allItems(index)
          val model = plan.state.currentModel(item)
          val pos = ApproachPosFunc.onCircle(model, item.pos, workItem)(plan.state, plan.value, plan.value.pos).get
          plan.goto(pos, true, Some("approaching")).whenDone {
            touchItem(item, path, workItem.cfg, plan.value)
          }.recover() // ignore any failures (most likely inaccessible location?)
        } else plan.futureDone
      }
    }

Video of garden work

3 Likes

Cool, do you see any chance, to release the engine open source one day? :slightly_smiling_face:

1 Like

My current plan is to open source the three.js port after the game is released. As for the game engine itself, this is yet to be decided, depending on what interest there will be about the game and the concept.

1 Like

Awesome! What about GC pauses and memory consumption?

I have not observed this as an issue. Modern concurrent GC seems to be working fine it this respect. Game is currently running on Java 21, which means G1 (default since Java 9 I think).

I have observed it now with largest possible visibility:

  • GC throughput is about 500 MB/s.
  • typical VM pause seems to be about 2-5 ms and it happens about each second. There also exist rare pauses up to 10 ms.

It might be perhaps possible to finetune this, but so far subjectively the game seems to be running smooth so I never had a reason to spend my time on it.

3 Likes