Behavior Tree: Model a Weapon

It always starts simple

For my platformer game All Fucked Up I have different kind of weapons in place, which should feel good.

My code for the weapons started simple. When the trigger is pulled launch a bullet, eject a shell, put a flash in front of the gun and make a sound. I thought well yes how complicated can that possibly go? I started to think about reloading, just wait a bit and reload it. Still very simple. But then a double barrel shotgun came along the way...

DoubleBarrelShotgun.gif

But it always ends up complicated

I started to need a timer and a counter to eject the shells after the second shot and with a delay of at least half a second. The code went out of hand pretty quickly and I was close to something but couldn't make it work without breaking other stuff. And after some refactoring and machine gun later I started to think about how I could fix the mess at hand.

Just for a comparsion here my old code

public class ShotGun implements Weapon {

    private final EntityData ed;
    private final EntityId parentId;
    private double time;
    private boolean pull;
    private WeaponEffect effect;
    private double ejectTime;
    private boolean eject;

    @Inject
    public ShotGun(final EntityData ed, final EntityId parentId) {
        this.ed = ed;
        this.parentId = parentId;
        this.effect = new EmptyWeaponEffect();
        this.time = 0.8;
        this.ejectTime = 0.0;
    }

    @Override
    public void addEffect(WeaponEffect effect) {
        this.effect = effect;
    }

    @Override
    public void pullTrigger(boolean pull) {
        this.pull = pull;
    }

    @Override
    public void shoot(double tpf, Vec3d location, Quatd orientation) {
        time += tpf;
        if (pull) {
            if (time > 0.8) {
                time = 0;
                ejectTime = 0;
                eject = true;
                launchBullet(orientation, location);
                effect.call();
            }
        }
        if (eject && ejectTime < 0.4) {
            ejectTime += tpf;
        } else if (eject) {
            eject = false;
            ejectShell(orientation, location);
        }
    }

and this was without reloading and it ejected the shell after every shot. But I wanted a double barrel shot gun and that ejects two shells after the second shot with a delay.

Behavior tree?

I used behavior tree to model my very simple and super stupid soldiers. And I thought well why not use behavior tree for my weapon behavior?
It took me some time to get my head around the behavior tree idea. Basically you break everything up in small tasks. Tasks can either FAIL, be SUCCEED or be still RUNNING (it stays there). The behavior tree gets executed every frame, like the whole tree gets exectuted.

You have either a sequence which runs its children as long none FAILs. A sequence which gets SUCCEED from all children will return SUCCEED else FAIL.

Or you have a selector which runs its children as long none return SUCCEED. It returns SUCCEED if one child returns SUCCEED else FAIL if none SUCCEED.

Check out this article to get a better understanding what behavior trees are Behavior trees for AI: How they work

Let's see the code of the double barrel shotgun with proper shell ejection and reloading

    this.bb = new WeaponBlackboard(main, this);

    Sequence<WeaponBlackboard> reloading = new Sequence<>();
    reloading.addChild(new Wait(1.4f));
    reloading.addChild(new Reload());

    Selector<WeaponBlackboard> needReload = new Selector<>();
    needReload.addChild(new AmmoCount());
    needReload.addChild(reloading);

    Sequence<WeaponBlackboard> shoot = new Sequence<>();
    shoot.addChild(new DetectPullTrigger());
    shoot.addChild(new LaunchProjectile());
    shoot.addChild(new WaitUntilSuccess<>(new Invert(new DetectPullTrigger())));
    shoot.addChild(new Invert<>(new AmmoCount()));
    shoot.addChild(new Wait<>(0.6f));
    shoot.addChild(new EjectShell());

    Sequence<WeaponBlackboard> gun = new Sequence<>();
    gun.addChild(needReload);
    gun.addChild(shoot);

    this.bh = new BehaviorTree<>(gun, bb);

Looks pretty neat doesn't it? It is straight forward. It has two possible main tasks "reload" and "shoot".

    gun.addChild(needReload);
    gun.addChild(shoot);

In case the ammo count is zero it initates a reload after waiting 1.4 seconds (pretty quick reload for a double barrel shotgun).

    Sequence<WeaponBlackboard> reloading = new Sequence<>();
    reloading.addChild(new Wait(1.4f));
    reloading.addChild(new Reload());

    Selector<WeaponBlackboard> needReload = new Selector<>();
    needReload.addChild(new AmmoCount());
    needReload.addChild(reloading);

Else it checks if the trigger is pulled and launchs the projectils and waits until the trigger gets released. In case the ammo count is zero I wait 0.6 seconds and eject 2 shells.

    Sequence<WeaponBlackboard> shoot = new Sequence<>();
    shoot.addChild(new DetectPullTrigger());
    shoot.addChild(new LaunchProjectile());
    shoot.addChild(new WaitUntilSuccess<>(new Invert(new DetectPullTrigger())));
    shoot.addChild(new Invert<>(new AmmoCount()));
    shoot.addChild(new Wait<>(0.6f));
    shoot.addChild(new EjectShell());

This is it. The code is much simpler, maintainable, reusable, testable and surprisingly stable. In the beginning I thought behavior trees only for NPCs, I never thought this could be so handy for other behavior stuff in my game. But it is, not just weapons.

I hope you enjoyed the article and I could give you some new inputs.

Comments

comments powered by Disqus