Zalo DS Blog

Tuesday, August 08, 2017

Properly doing cameras interpolation through a target (I)

This is the 1st post on the series Properly doing cameras interpolation through a target:
- Properly doing cameras interpolation through a target (I)
- Properly doing cameras interpolation through a target (II)

Not sure everyone knows, but I am the kind of player that one day is enjoying the latest Zelda and the next day can be playing a Nintendo64 game on my favourite emulator. I don't play too many games (not that I ever had, usually when I was a kid I could get 5-6 different games a year, so I am fine with this) but the few ones I do I like to get to at least one of the endings.

Jumping from Banjo Kazooie to Zelda Breath of The Wild I could appreciate an evolution on the cameras Interpolation both games were doing. This is something that I wanted to work on for a long time, so after seeing it every night in Zelda BOTW I decided to implement it once and for all.




When Banjo crosses the bridge there is an interpolation between two cameras, but while it's happening the character moves from side to side of the screen giving a weird sensation

In Zelda Breath Of The Wild a cameras interpolation happens when you use the teleport. The gameplay camera changes to a new one that shows Link from a closest angle. Link always stays in the middle of the screen

By cameras interpolation I refer to the animation that happens when changing between two different cameras, moving from the first one to the second. Bear in mind this is not always necessary, the change could be inmmediate

A very basic Camera Interpolator

Ok, let's start from a simple Camera interpolator. I'll be using Unity this time. Basically we want an script that given two cameras, disables both of them and activates a new one that moves from the first one to the second, and when it finishes it disables this new camera and enables the second one. When moving from the first one to the second we want to interpolate: position, rotation, fov, near clip plane and far clip plane. Something like this will do

Any time that we want to interpolate we should call InterpolateTo passing the two cameras and the time we want it to last. The var interpolationTimeAccum will be incremented during every call to LateUpdate and all the camera parameters will be interpolated until it reaches interpolationTime.

Now in order to test this script let's create a new script that allows us to easily select the cameras we want to select for the interpolation. The first thing that comes to my mind is an object that keeps a list of cameras and every time we press a number in our keyboard it interpolates to the camera in that index. Something like this:

I am also disabling every single camera except the first one because we only want one of them to be active at a time. In order to test these new scripts let's create a new scene with a cube and some cameras around it (on its front, bottom, left, right, front and back, for example). We also need an extra camera that will contain the CameraManager.cs script and an extra GameObject containing the Cameras.cs with all the cameras and the reference to the CameraManager. This is how it should look:

  

This script really interpolates through all the cameras but it has some issues:
- The cube is not on screen all the time even though we are just rotating around it
- The interpolation cannot be interrupted, you need to wait until it finishes before interpolating again
- InterpolateTo doesn't really need the fromCamera, we could assume this camera is the last one we were interpolating to
- All the cameras have AudioListeners and this causes a lot of warnings (this is a Unity related issue but since it is the engine we are using we'll take care of it)

A little improvement

Let's focus for now on fixing the three last issues that are actually not related to the interpolation itself but have more to do with the usability of the script.

The first two issues are easy to solve if instead of storing the fromCamera we keep a record of its position, rotation, fov, near and far. If the interpolation is interrupted then the current values of these variables will be the new interpolation sources.

The AudioListener issue can also be fixed disabling the AudioListeners instead of just the Camera components (this is much better than fully disabling the GameObject since there might be other script that need to continue running). Since we are going to use this in several points of the script, let's create a function fort his purpose.

You might be worried that we are no longer keeping track of the source camera. But actually it is much better if we forget about it, for several reasons:
- It could be destroyed at any time during the interpolation (giving us a NullPointerException that will cause a crash)
- It doesn't look ok to interpolate from a Camera that is still moving, you can test this a little bit with the previous script if you don't trust me
- We are not really interpolating from a camera, same that a moving object moving from A to B could change its target to C any time and won't be interpolating from A or B just a point in the middle.

We also need to update the Cameras.cs script

You can now interrupt the interpolation any time and all the AudioSources issues will no longer be happening, but still when interpolating between some cameras the box gets offscreen, this is easy to see interpolating between the front and back cameras

Interpolating through a target

So what we want is to keep the box inside the boundaries of the screen at all times. Let's start by thinking if this is possible or not. Could we always have the box visible? If it is visible from cameraFrom and it is visible from cameraTo then I can think of an interpolation on screen coordinates were the box moves from the left of the screen to the right at the same time that is interpolating its rotation. Let me help you to visualize it, this is what we are looking for:

If you think on screen coordinates instead of world coordinates it seems a little bit more obvious. Now, how could we implement this?

Maybe you are thinking that you could use the lookAt function from the Quaternion class. You could just interpolate the positions between the two cameras while always looking at the target. This will work in some situations but not in all of them, because the lookAt function always keeps the target in the center of the screen. If you pay attention to the screenshots you'll see that I have on purpose placed the target on the screen sides.

What if we create a camera that allows us to point to a target but not necessarily in the middle of the screen? The next script does exactly this:
If you add this to a camera and assign a target and an offset, then rotating the camera (manually in the editor) will rotate around the target but will not be centered on the screen

How does this script achieve it?
- It keeps the current rotation
- Then it resets its position and is placed on the target position
- From there it applies a (local) translation with the given offset

If we could represent the position of our cameras with a target, a rotation and an offset from it then interpolating the rotations first and then the offsets would give us the intepolation we are looking for.

Given a target position P and a Transform T the offset that T needs to move from its position to P is the position of P relative to T. The inverse of this value is what is needed to move from P to T.position. In Unity this is expressed as
Vector3 offset = -T.worldToLocalMatrix.MultiplyPoint(P);

The reason why we are using P relative to T instead of just P is because we need to Translate the camera in local space. Now putting all the pieces together, we can improve our CameraManager like this:
pay special attention to lines 57 and 88, that is where the magic happens. Also it is important to interpolate the rotation first and then do the translation so this last one happens in the right direction. You might have also noticed that InterpolateTo now has an extra param: the target. If you want to use the interpolation without a target then you should assing cameraTo as target (lines 34,36), and then the interpolation will look like the old one.



Ok, we are gonna leave it here for now. This interpolator is much better than the one we started with. You can play with it a little bit if you want, but bear in mind that is not final. It has a few issues that I will solve in the next post. In the meantime if you find there is something that doesn't make sense or you have a question, please let me know in the comnents ;)

Labels: , ,

0 Comments:

Post a Comment

<< Home