Camera System

Finally, we started to think about the camera logic. How to move the camera relatively with the character we control in a way that the player gets the most out of it. There was not so much to find but this Gamasutra article from Itay Keren helped me a bunch. I know at least have a clue what I’m talking about. And a tweet about the camera system of qomp from Stuffed Wombat inspired me to do something similar.

Head Into

The problem I had was that the camera was always following the character which made it quite a challenge to watch and might cause motion sickness.

Camera Systems

Lerp Camera

I found on this page a good lerp implementation which I use as well. It solved almost all my jitter problems I had with my own lerp camera implementation. It’s pretty cool and worth a read.

Projected-Focus

This is a projected-focus camera that looks a bit ahead of the player while moving.

The initial camera code was very straight forward and simple

Vector2 velocity = new Vector2(player.dyn4jBody.getLinearVelocity());
camera.setLocation(new Vector3f((float) (player.location.x + velocity.x / 2),
            (float) (player.location.y + velocity.y / 2), 0));

I just used the velocity to catch up with the player and look in the direction he currently moves. This was needed because the underlying lerp-smoothing logic, will let the camera fall back on high speed like a free fall so a velocity-based look ahead seemed the best solution.

This camera always follows the player. This can be annoying if we do wall jumps where the character always moves in and out. I guess after a while people will get sick.

Camera-Window But Keep Projected-Focus

The goal was to reduce the camera movement to a minimum and focus on the direction we move. For this, I implemented a combined camera which was camera-window and projected-focus. This means inside a window the character can move while the camera does not move. As soon the player touches the edge of the virtual window the camera starts to follow and look ahead.

You can imagine that this code would be much more complicated. As well I only wanted the window for the horizontal move but not for the vertical moves (for the vertical moves I do not yet have a clear plan). After a few trials and some refactoring later it was clear that I will use a behavior tree to implement the much more advanced logic for this camera-window and project focus logic.

Sequence<CameraBlackBoard> checkStillMovingX = new Sequence<>();
checkStillMovingX.addChild(new Invert<>(new DetectPlayerMoving(Axis.X, 0.1f)));
checkStillMovingX.addChild(new SetCamFollowing(Axis.X, false));

Selector<CameraBlackBoard> shouldFollowX = new Selector<>();
shouldFollowX.addChild(new Invert<>(new DetectPlayerCamRange(Axis.X, 4)));
shouldFollowX.addChild(new DetectCamFollowing(Axis.X));

Sequence<CameraBlackBoard> followX = new Sequence<>();
followX.addChild(shouldFollowX);
followX.addChild(new SetCamFollowing(Axis.X, true));
followX.addChild(new CamFollow(Axis.X, 0.75f));

Parallel<CameraBlackBoard> control = new Parallel<>(Parallel.Policy.Sequence);
control.addChild(new AlwaysSucceed<>(followX));
control.addChild(new AlwaysSucceed<>(new CamFollow(Axis.Y, 0.5f)));
control.addChild(new AlwaysSucceed<>(checkStillMovingX));

bh = new BehaviorTree<>(control, bb);

I had to distinguish between horizontal and vertical movements as this is decoupled. I applied the window logic only for the horizontal movement (the x-axis) which was even with a behavior tree implementation a bit tricky to achieve. The vertical movement is in parallel to the horizontal movement but strictly following. The last sequence checks if the character stopped so we can stop following. We following a character if he touches the window border and keeps following until the character stops. The behavior tree forces us to break the code up into simple tasks which makes it a piece of cake to implement and test.

This reduces the movement of the camera while the character walks back and forth within a specified window. I still have to figure out and fine-tune what is the best window size. It also looks ahead in the direction we move.

There is an underlying camera that does lerp-smoothing to make it softer on direction changes and stops. The underlying camera also does have a kickback function for the camera kickbacks if we fire a weapon to give the weapon more boom. I use the kickback vertically as well for hard landings which gives it a bigger impact.

Zoom

I added a simple “Zoom” property and extended my cameras with a setProperties to set any kind of property and every camera implementation can get whatever property it understands and apply it. The zoom is simply the distance to the character as I decided to use a perspective camera instead of an orthogonal camera. The game also looks a bit more interesting with a perspective camera.

Camera Switch

I implemented as well a simple camera switch logic. With tilded I have an object group where I define the camera area, the name of the camera type, and the camera properties like “Zoom”.

        cameraContainer.stream()
                .filter(cam -> !cam.entered)
                .filter(cam -> isPlayerInCam(cam))
                .findFirst()
                .ifPresent(cam -> {
                    cam.entered = true;
                    myCamera = (MyCamera) feather.instance(cam.type);
                    myCamera.setProperties(cam.properties);
                });
        
        cameraContainer.stream()
                .filter(cam -> cam.entered)
                .filter(cam -> !isPlayerInCam(cam))
                .findFirst()
                .ifPresent(cam -> {
                    cam.entered = false;
                });

That’s it so far. I enjoyed the journey 🙂


I get a small commissions for purchases made through the following links, I only have books in this section which I bought myself and which I love. No bullshit.

Behavior Trees for Weapons

If it comes down to behavior trees many developers think of AI, robotics, or NPCs. I started to use behavior trees to make my stupid soldier NPCs patrol and shoot. They are still very stupid and sometimes shoot each other. I realized that I can use behavior trees for more than just my NPCs.

For example for my weapons. It’s actually so much fun to design and model weapons, not only from the visual representation but also in how it behaves and how it feels to use it.

Note that…

this blog article doesn’t want to explain behavior trees in all lengths and depths. There are many blogs and articles available which do a great job explaining behavior trees. Furthermore, you will probably also need to read the documentation for your behavior tree implementation at hand.

Here a small selection of links:

It starts always simple

When I first introduced my weapons to my platformer "All Fucked Up", I did it the classical way by simply programing them. It started simple with a weapon which eject a shell, launch a bullet, place a flash light and make a boom sound. Even when I modeled a submachine gun it was simple. I thougt well how messy can that possible go. And then a double barrel shot gun came along the way and everything started to get messy.

After two shots the shotgun should eject two shells, not imediately but with a small delay. As well I wanted to limit the rounds per minute. I managed to do this and ended up with the following code:

   public void shoot(double tpf, Vec3d location, Quatd orientation) {
        time += tpf;
        if (pull) {
            if (cartridge > 0 && time > 0.25) {
                cartridge -= 1;
                time = 0;
                launchProjectile(orientation, location);
                if (cartridge <= 0) {
                    eject = true;
                    ejectTime = 0;
                }
            }
            if (time > 2.5) {
                cartridge = 2;
            }
        }
        if (eject && ejectTime < 0.6) {
            ejectTime += tpf;
        } else if (eject) {
            eject = false;
            ejectShell(orientation, location);
        }
    }

It doesn’t look too bad to be honest, if you carefully read it you will get your head around it. But you probably also see the problem with this approach. Just think of extending it with a limited amount of ammo you collected. A force-reload, where you want to reload the weapon even if you didn’t shoot all ammo. It was no fun to try to extend it.

There must be an alternative to this messy code, which is hard to maintain, extend, or adapt. I tried to refactor it in breaking it down to smaller and simpler pieces. The main problem remained, it was still hard to extend or adapt.*

The weapons do have behavior and why not use a behavior tree implementation as a way out. It was one of the best decision I made for this game.

A very short story about behavior trees

I use the behavior tree implementation from libgdx-ai.
We have tasks, sequences, selectors, and parallels. In this article, I don’t need parallels.

I just describe very short what the single parts do.

Task

A task can either fail, succeed or stay running. A task in the state running, will be reentered on the next execution of the behavior tree.

Sequence

A sequence is a special task that runs each of its child tasks in turn. It will fail immediately when one of its children fails. As long as its children succeed, it will keep going. If it runs out of children, it will succeed.

Selector

A selector is a special task that runs each of its child tasks in turn. It will succeed when one of its children succeed. As long as its children are failing, it will keep on trying the next child. If it runs out of children completely, it will fail.

Ok then let us use this behavior tree

On the top level of the behavior tree we have a sequence with two tasks, the first one will reload the gun if needed and if possible, the second task will shoot if the trigger is pulled and there is at least one round chambered.

Let’s look at how we model the "need reload" task. Probably not the best naming. The reload task consist of a selector with two child tasks. The first task checks if the ammo count of the gun is bigger than zero. The second task does a delayed reload of the gun and fails if the player does not have any shotgun rounds left else it succeeds. The check if it really can reload is done in the reload task.

Shooting is basically a sequence of tasks. First, we check if the trigger is pulled and fail if not. If the trigger is pulled we launch the projectiles, this task does always succeed. After that, we wait until the trigger gets released to have a controlled shooting. After we release the trigger we check if there is still life ammo chambered and fail if so. In the case there is no life ammo anymore we wait a fraction of a second and eject two shells and succeed.

As you can see the shoot task also returns with fail or succeed this is important and has to be considered if you want to proceed after the shooting task with an additional task whatever this might be…

And here the complete behavior tree

Looks cool. The code of this behavior tree looks like this

        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);

And in the update loop you call something like

bh.next()

It is so much easier to change the behavior or to adapt it. And a new weapon is done literally in no time. Even if I don’t use a graphical editor. It’s so much fun to model weapons with this approach.

Tasks and reusable code

Because we have capsulated the stuff into tasks, we can reuse these tasks for new weapons. Like DetectPullTrigger(), LaunchProjectile(), AmmoCount() and EjectShell(). And also very generic tasks and decorators like Wait(seconds), WaitUnitlSucceed(task), AlwaysSucceed(task) and Invert(task).

For our own tasks, we can handover a weapon blackboard where we keep together everything we need. The weapon itself implements an interface with the basic actions, like launch bullets, eject shells and reloading as this is very weapon specific.

So let’s conclude

The main message of this blog article is not the solution for the double-barrel shotgun. The main message is that you can use behavior trees beyond NPC AI. Whatever has a behavior can be modeled with a behavior tree even if it is not too obvious in the beginning. If you have a graphical behavior tree editor your game designers can model that part of the game directly and have instant feedback if their ideas work out or not.

I also model my controller and the level switching logic with a behavior tree approach. These parts are much easier to adapt and to extend now. It’s also very handy as you simply can try out all your weird ideas without a lot of coding. What makes it really really fun.

The end

That’s it, I hope you enjoyed it.