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.