BHL is a strictly typed programming language specifically tailored for gameplay logic scripting. It combines pseudo parallel execution 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.
- ANTLR based: C# frontend + C# interpreting backend
- Statically typed
- Cooperative multitasking support
- Built-in support for pseudo parallel code orchestration
- Golang alike defer
- Basic types: float, int, bool, string, enums, arrays, maps
- Supports imperative style control constructs: if/else, while, foreach, break, continue, return
- Allows user defined: functions, lambdas, classes, interfaces
- Supports C# bindings to user types and functions
- Passing arguments to function by ref like in C#
- Multiple returned values like in Golang
- Strict control over memory allocations
For comprehensive documentation of all BHL features and usage, see the language documentation.
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)
}
}
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
enum Status {
None = 0
Connecting = 1
Connected = 2
}
Status s = Status.Connected
class Vec3 {
float x
float y
float z
}
Vec3[] vs = [{x: 10}, {y: 100, z: 100}, {y: 1}]
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)
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)
Unit u = FindTarget()
float distance = 4
u.InjectScript(coro func() {
paral_all {
yield PushBack(distance: distance)
yield Stun(time: 0.4, intensity: 0.15)
}
})
func bool(int) p = func bool(int b) { return b > 1 }
return p(10)
{
RimColorSet(color: {r: 0.65, a: 1.0}, power: 1.1)
defer { RimColorSet(color: {a: 0}, power: 0) }
...
}
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))
}
}
coro func Selecto
AF52
r([]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()
}
}
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.
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.
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 script assumes you are using *nix platform.
BHL comes with example code in the example
directory that demonstrates key language features:
Located in example/bindings/
, this example shows:
- How to integrate BHL with C# code through bindings
- Using coroutines (
coro func
) for asynchronous behavior - Parallel execution with
paral
blocks - State management and event handling
The example implements a simple AI behavior system with:
coro func Unit() {
Trace("Unit starts...")
int state = 0
int target_id = 0
paral {
yield RandomStateChanger(ref state)
while(true) {
paral {
yield StateChanged(ref state)
yield Selector(
[
coro func bool() { return yield AttackTarget(ref target_id) },
coro func bool() { return yield FindTarget(ref target_id) },
coro func bool() { return yield Idle() }
]
)
}
yield()
}
}
}
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
There are many unit tests which cover all BHL features.
You can run unit tests by executing the following command:
$ cd tests && dotnet test
- Generics support
- More compact byte code
- More optimal runtime memory storage layout
- Weak references semantics
- Improved debugger support
Byte code optimizationMore optimal executor (VM)Better runtime errors reportingMore robust type systemUser class methodsInterfaces supportNamespaces supportPolymorphic class methodsNested classesNested in classes enumsStatic class members supportVariadic function argumentsMaps supportBuilt-in strings basic routinesImplicit variable types using 'var'- LSP integration
Basic debugger support
ref semantics similar to C#Generic functors supportGeneric initializersMultiple return values supportwhile syntax sugar: for(...) {} supportwhile syntax sugar: foreach(...) {} supportTernary operator supportUser defined structsUser defined enumsPostfix increment/decrement