This post got too long for the forum limit!

So I'm splitting it up! Here's the rest.

Controlling the cameraThis will mostly be left to you to implement as you see fit depending on your input method(s), but I will briefly explain what values you need to transfer from your inputs and how. You'll want to get your motion input on the x axis and on the y axis separately, whether it comes from the mouse or a joystick or the arrows on the keyboard and you'll want these values to be in the range from negative to positive but they need not necessarily be from -1 to 1 as higher values can be suitable for higher sensitivity, especially for mouse movement. Then calculating the camera movement is as easy as this:

_angle += input.x * deltatime;

_pitch += input.y * deltatime;

You may need to invert one or both of these values to get the desired result. I'm personally dealing with this on a lower level in my code where the input gets processed based on user settings in the first place and so my camera code does not need to deal with this.

**Remember to always allow your users to manually configure camera axis inversion** because people are used to different configurations and might not be able to play comfortably at all with one or the other—

**also differentiate between third and first person inversion** as people may have different preferences for each of those too in case you're planning on having a first person mode in your game as well!

Note that we are updating the desired angle and pitch values and not updating the current pitch and angle values directly since these will get lerped toward the desired values in the update code that we just wrote.

Handling collisionThere are many ways to fine-tune this, but we will be going for a very simple but effective solution to preventing the camera from clipping through walls that's a solid foundation to work from, if not even enough for many games.

All this requires is a way to cast a line or ray from the target character's origin to the camera's position and see if it intersects with any geometry, and at what point the first (closest to the character) intersection happens and calculating a new camera position and distance based on that, bringing the camera closer to the character as necessary.

In some instances this means the camera will actually clip through the character instead—you can either try to tack on more failsafes to prevent this, or you can do something as simple as just fading the character out, which is done even in AAA games such as The Legend of Zelda: Breath of the Wild:

Footage from ~3:26:34 into this gameplay video on YT from channel RabidRetrospectGames.I've not yet done this in my own game, as you can see in the GIF at the beginning of this post, but I plan on doing so!

Line or ray castSo the basic premise is this:

We cast a line from the character to the camera. Not the other way around. This is important. We do this so that the first thing that the line intersects with will be the closest to the character, so that we know for sure that if we move the camera there, that's where the character will be visible. If the camera were all the way behind the house and we did it the other way around we would instead have found the wall on the other side of the building, and moving the camera there wouldn't have brought the character back into view!

So for this I'm assuming that you already have some sort of library in place that you're already using to check collisions between the player and the level geometry, and that this library provides a line or ray cast function. The only difference between the two is API: a line cast specifies a start and an end point whereas a ray cast merely specifies the starting point and a direction as well as an optional length. Since we want to be checking between the character's origin and the camera's position I'll be using a line cast between the two.

Hitinfo info = linecast(origin, _position);

The origin here is the vector we calculated earlier by adding the offset to the target's position. I'm assuming here that your line cast function returns some kind of struct with information about the result, perhaps like so:

struct Hitinfo

{

bool hit; // did the line cast actually intersect with anything?

vec3 point; // the world position where the intersection happened

}

There might also be things like a normal direction in there, but these are the only two pieces of information we are interested in: did we hit anything at all, and if so where? Then we can simply calculate a new distance for the camera from the origin based on the distance between the origin and this hit point. The naïve calculation would look like so:

float dist = length(origin - info.point);

However we want to bear some things in mind. First, we probably want to have some offset from the obstacle so that the camera isn't licking the wall and risking clipping on the sides. An additional factor to bear in mind here is the value of your camera's near clip plane as well, but that's outside the scope of this codebase. Let's add another constant:

const float _hitoffset = 0.25;

Fiddle with this number as you wish. Now we need the directional vector of the line between the hit point or camera and the character's origin so that we can offset in the right direction.

vec3 dir = vec3(0.0, 0.0, 1.0); // forward vector; you might have a library built-in

dir = _rotation * dir; // align with the character's facing direction

dir = normalise(dir);

That's it! Multiplying the camera's rotation quaternion by the forward vector turns that vector into a directional vector matching the quaternion. Now we can use this to calculate the actual point:

vec3 point = hit.point - (dir * _hitoffset);

Then we can update the previous calculation like so:

float dist = length(origin - point);

Here we are subtracting the hit point position with the added offset from the origin position and getting the length, also known as magnitude depending on your maths library, of the resulting vector.

Finally we also want to clamp the distance to make sure it's between zero and the permitted maximum as defined by our constant:

dist = clamp(dist, 0.0, _maxdist);

Then we can recalculate the position using this distance instead of the maximum distance we defined earlier:

_position = calculatePosition(origin, dist);

Since we drew the line between the origin and the camera we know that the direction of the vector is equal to the one that the camera is already pointing, and so what this code effectively does is to just kind of slide the camera in its own forward direction along this line closer toward the character.

Smoothing the movementNow, this might get choppy if the camera just warps from one distance to the other and we might want to lerp this as well. I've found that it's best to warp directly if the new distance is shorter than the old one because otherwise we risk seeing the camera clipped through the obstacle for a moment, whereas I certainly do prefer a lerp when the new distance is longer since that means the camera now has room to comfortably back up again with nothing in the way.

This means that the we, much like we differentiate between current and desired angle or pitch, now need to also have a variable for the current distance that can be lerped toward the desired one:

float _currdist = _maxdist;

The old position calculation (not the one we just performed based on the distance to the line cast hit point, but the one at the beginning of this tutorial) thus needs to be updated to use the current instead of the maximum distance:

_position = calculatePosition(origin, _currdist);

And then of course we need to actually do the lerping somewhere. If we've detected a hit we want to lerp toward the new distance if it is greater than the old one, but otherwise warp directly to it. If we did not detect a hit at all we want to lerp toward the maximum distance.

Putting it all togetherSo all in all the position calculation logic, taking into account line casting and lerping, would look something like this:

Hitinfo info = linecast(origin, _position);

if (info.hit)

{

vec3 dir = vec3(0.0, 0.0, 1.0);

dir = _rotation * dir;

dir = normalise(dir);

vec3 point = hit.point - (dir * _hitoffset);

float dist = length(origin - point);

if (dist < _currdist)

_currdist = dist;

else

_currdist = lerp(_currdist, dist, deltatime * _movespeed);

}

else

_currdist = lerp(_currdist, _maxdist, deltatime * _movespeed);

_position = calculatePosition(origin, _currdist);

I've introduced yet another constant here for the speed of this movement along the line between the character and the camera that you can fiddle with to get your desired results. Of course you're probably also going to want to filter somehow what geometry the line should actually detect so the camera doesn't try to avoid minor things that aren't actually really in the way, but that's very API-specific, so I left this out here.

Resetting the cameraIf you've ever played a 3D Zelda game, you'll know that they let you use something like a shoulder button (or the Z trigger on a Nintendo 64 controller) to reset the camera behind Link. This is also done in my game as evidenced by the GIF at the top. In fact, this is the only way to control the camera in some of these games, as many of them happen to be on consoles that lack a secondary stick. Even when you have one, I think it's a nice feature to have, so let's look at this too!

Footage from ~13:43 into this gameplay video on YT from channel TheRealSonicFan.Calculating the angleThis is fairly easy. We need to calculate the angle that the camera should have around the character according to their current facing direction in order to end up behind their back. We also need to reset the pitch to whatever default value you decided on earlier. Let's whip up a function for this, like we did for calculating position. This time we want to pass in the rotation of the character as a quaternion.

float calculateAngle(quat targetrot)

{

vec3 dir = vec3(0.0, 0.0, 1.0);

dir = targetrot * dir;

dir.y = 0.0; // we do not want to use this axis

dir = normalise(dir);

return atan2(dir.x, dir.z); // turn direction vector into a radian angle

}

The calculation is much the same as for calculating the direction for the hit point offset before, but this time we don't care about the y axis, and then using atan2 on the resulting vector gives us the angle. We need to use a 3D vector initially since the quaternion is 3D, but since we're really only calculating an angle for a 2D top view rotation, we end up only using the x and z axes after that, effectively a 2D vector.

Now all you need to do, when the user gives you the input you want to assign to this action, is to set the desired angle (not the current angle—we still want to make use of our nice clerp from before to make the current angle interpolate nicely to the new angle, not just warp there) to the result of this calculation, and likewise reset the desired (not current) pitch to the default value.

_angle = calculateAngle(_targetrot);

_pitch = _startpitch;

Free and locked modeOnce again I shall bring up an example from the Zelda series. You may or may not want this functionality!

While Breath of the Wild relies entirely on the player to manually control the camera using the secondary stick and games like Ocarina of Time rely on a combination of resetting and also automatically following the turning motions of character, The Wind Waker offers both: if the player manually takes control, it works like BotW, but if they reset the camera it starts working like OoT instead, somewhat following the motion of the character.

I personally like this a lot, so it's what I've done for my game as you can see in the GIF at the top—let's look at this as well as the grand finale!

Footage from ~1:26 into this gameplay video on YT from channel GuffyTheWeird.Here the camera is in locked or automatic mode, and as you can see it follows Link as he turns to an extent, but not if he does a full 180 and starts running toward the camera. I've achieved something similar in my game, so let's have a peek at the code!

Swapping statesSo we already have two important mechanisms in place by now: free camera control, and camera resetting. What we want now is a variable that controls whether the camera is in free mode or not, and to enter free mode when the player turns the camera and exit free mode when they reset it. So we add a boolean for this, starting out in locked mode:

bool _free = false;

After updating our input management code to modify this variable, we can add to our update function a section to make the camera follow the player's heading to the desired extent with some more maths.

if (!_free)

{

// … calculations go in here

}

First we need the heading direction of the camera as well as that of the character. We calculate these just like we did before in the angle calculation function but stop once we've got to the directional vectors since we don't need the radian angles for this:

// camera direction

vec3 dir = vec3(0.0, 0.0, 1.0);

dir = _rotation * dir;

dir.y = 0.0;

dir = normalise(dir);

// character direction

vec3 targetdir = vec3(0.0, 0.0, 1.0);

targetdir = _targetrot * targetdir;

targetdir.y = 0.0;

targetdir = normalise(targetdir);

Now we want the dot product between these two directional vectors to calculate how parallel or perpendicular they are to each other. If you're completely clueless on dot products,

this page provides an excellent visual guide to how it works. Basically, if they are more or less parallel, we know the camera is looking at the character's back or their face and we don't want to turn the camera, but if they're more perpendicular (forming a right angle) we are looking at the character's side, so they are turning to the left or the right relative to the camera.

Parallel:

Nearly perpendicular:

float dp = dot(targetdir, dir);

Now comes some magic number fiddling again. We need to check whether the resulting dot product is within a certain range of acceptable (non-)perpendicularity where the camera will actually turn with the character and only do so if this is the case. The dot product ranges from -1.0 to 1.0 and for my purposes I found the range -1.0 to 0.25 to be suitable. Adjusted for sign, this means that 1.25 is the size of the total range. I chose to do the maths like so:

float range = 1.25;

if (dp < (range - 1.0))

_angle -= eulervec(inverse(_rotation) * _targetrot).y * deltatime;

The Euler vector function turns a quaternion into a 3D vector containing the x, y and z angles separately as radians and is something I expect you to have in your maths library.

The quaternion in question is the result of multiplying the inverse rotation quaternion of the camera by the rotation of the character in order to adjust for the camera's rotation relative to the character and get a more absolute rotation for the character. After this we are only interested in the y axis rotation angle which will be a negative or positive number depending on which way (their left or right) the character is turning relative to the camera.

You might want to normalise this into an absolute sign that's either -1.0 or 1.0 but having the angle scale with the intensity of the rotation seemed suitable to me, so I didn't.

Finally we multiply this by the deltatime for smooth goodness and update our desired angle by subtracting the result of this whole expression.

Detecting motionBut wait! We forgot something in the last if statement. It gets very annoying if the camera always keeps turning even if the player isn't doing anything, so we want to make it only do so if the character is actually moving. We do this simply by keeping track of their position from the last frame and comparing the magnitude of their difference on the x and z axes (like so many times before, we don't care about the y axis here). So we need a persistent variable for this:

vec3 _targetposOld;

Then at the very end of each update step we need to refresh it with the current position:

_targetposOld = _targetpos;

Now we can calculate the difference like so:

vec3 a = _targetpos;

vec3 b = _targetposOld;

a.y = 0.0;

b.y = 0.0;

float diff = length(a - b);

And then update our if statement something like the following:

if (dp < (range - 1.0) && diff > (epsilon * deltatime))

Epsilon is a magic small number of your choosing to determine when the character's movement is no longer significant; I went for 0.01. Multiplied by deltatime to make it consistent regardless of frame rate.