Home

Awesome

BeHavior Language

CI

BHL is a strictly typed programming language specifically tailored for gameplay logic scripting. It combines Behaviour Trees(BT) primitives with familiar imperative coding style.

First time it was presented at the nucl.ai conference in 2016. Here's the presentation slides.

Please note that BHL is in beta state and currently targets only C# platform. Nonetheless it has been battle tested in the real world projects and heavily used by BIT.GAMES for mobile games development built with Unity.

BHL features

Quick example

coro func GoToTarget(Unit u, Unit t) {
  NavPath path
  defer {
    PathRelease(path)
  }
  
  paral {
   yield while(!IsDead(u) && !IsDead(t) && !IsInRange(u, t))
   
   {
     path = yield FindPathTo(u, t)
     yield Wait(1)
   }
   
   {
     yield FollowPath(u, path)
   }
}

Code samples

Structs

class Color3 {
  float r
  float g
  float b
}

class Color4 : Color3 {
  float a
}

Color4 c = {}
c.r = 0.9
c.g = 0.5
c.b = 0.7
c.a = 1.0

Enums

enum Status {
  None       = 0
  Connecting = 1
  Connected  = 2
}

Status s = Status.Connected

Generic initializers

class Vec3 {
  float x
  float y
  float z
}

Vec3[] vs = [{x: 10}, {y: 100, z: 100}, {y: 1}]

Passing by ref


Unit FindTarget(Unit self, ref float dist_to_target) {
...
  dist_to_target = u.position.Sub(self.position).length
  return u
}

float dist_to_target = 0
Unit u = FindTarget(self, ref dist_to_target)

Multiple returned values


Unit,float FindTarget(Unit self) {
...
  float dist_to_target = u.position.Sub(self.position).length
  return u,dist_to_target
}

Unit u,float dist_to_target = FindTarget(self)

Closures

Unit u = FindTarget()
float distance = 4
u.InjectScript(coro func() {
  paral_all {
    yield PushBack(distance: distance)
    yield Stun(time: 0.4, intensity: 0.15)
  }
})

Function pointers

func bool(int) p = func bool(int b) { return b > 1 }
return p(10)

defer support

{
  RimColorSet(color: {r:  0.65, a: 1.0}, power: 1.1)
  defer { RimColorSet(color: {a: 0}, power: 0) }
     ... 
}

Pseudo parallel code execution

coro func Attack(Unit u) {
  Unit t = TargetInRange(u)
  Check(t != null)
  paral_all {
   yield PlayAnim(u, trigger: "Attack")
   SoundPlay(u, sound: "Swoosh")
   {
     yield WaitAnimEvent(u, event: "Hit")
     SoundPlay(u, sound: "Damage")
     yeld HitTarget(u, t, damage: RandRange(1,16))
   }
}

Example of some unit's top behavior

coro func Selector([]coro func bool() fns) {
  foreach(var fn in fns) {
    if(!yield fn()) {
      continue
    } else {
      break
    }
  }
}

coro func UnitScript(Unit u) {
  while(true) {
    paral {
      yield WaitStateChanged(u)
      Selector(
            [
              coro func bool() { return yield FindTarget(u) },
              coro func bool() { return yield AttackTarget(u) },
              coro func bool() { return yield Idle(u) }
            ]
       )
    }
    yield()
  }
}

Architecture

BHL architecture

BHL utilizes a standard interpreter architecture with a frontend and a backend. Frontend is responsible for reading input files, static type checking and bytecode generation. Binary bytecode is post-processed and optimized in a separate stage. Processed byte code can be used by the backend. Backend is a interpreter responsible for runtime bytecode evaluation. Backend can be nicely integrated with Unity.

Frontend

In order to use the frontend you can use the bhl tool which ships with the code. See the quick build example below for instructions.

Quick build example

Currently BHL assumes that you have dotnet installed and its binaries are in your PATH.

Just try running run.sh script:

cd example && ./run.sh

This example executes the following simple script

Unit starts...
No target in range
Idling 3 sec...
State changed!
Idle interrupted!
Found new target 703! Approaching it.
Attacking target 703
Target 703 is dead!
Found new target 666! Approaching it.
State changed!
Found new target 902! Approaching it.
...

Please note that while BHL works fine under Windows the example assumes you are using *nix platform.

Building

BHL comes with its own simple build tool bhl. bhl tool is written in C# and should work just fine both on *nix and Windows platforms.

It allows you to compile BHL sources into a binary, lauch an LSP server etc.

You can view all available build tasks with the following command:

$ bhl help

Tests

For now there is no any documentation for BHL except presentation slides. However, there are many unit tests which cover all BHL features.

You can run unit tests by executing the following command:

$ cd tests && dotnet test

Roadmap

Version 3.0

  1. Generics support
  2. More compact byte code
  3. More optimal runtime memory storage layout
  4. Weak references semantics
  5. Improved debugger support

Version 2.0

  1. Byte code optimization
  2. More optimal executor (VM)
  3. Better runtime errors reporting
  4. More robust type system
  5. User class methods
  6. Interfaces support
  7. Namespaces support
  8. Polymorphic class methods
  9. Nested classes
  10. Nested in classes enums
  11. Static class members support
  12. Variadic function arguments
  13. Maps support
  14. Built-in strings basic routines
  15. Implicit variable types using 'var'
  16. LSP integration
  17. Basic debugger support

Version 1.0

  1. ref semantics similar to C#
  2. Generic functors support
  3. Generic initializers
  4. Multiple return values support
  5. while syntax sugar: for(...) {} support
  6. while syntax sugar: foreach(...) {} support
  7. Ternary operator support
  8. User defined structs
  9. User defined enums
  10. Postfix increment/decrement