3D Tutorial 4: Camera Movement

Welcome to the last of the 3D project tutorials!

In this tutorial, we'll be looking at creating a controlled camera in a few different styles. We'll begin very simple, as always. Let's just get some camera movement going.

You know the drill, type or copy and paste the following project into the FUZE4 Nintendo Switch code editor.

  1. obj = placeObject( cube, { 0, 5, 0 }, { 1.5, 4, 0.5 } )
  2. flr = placeObject( cube, { 0, 0, 0 }, { 10, 0.1, 10 } )
  3. 
  4. setObjectMaterial( obj, grey, 0, 1 )
  5. setObjectMaterial( flr, bisque, 0, 1 )
  6. 
  7. pointShadowLight( { 0, 7, 3 }, white, 10, 1024 )
  8.
  9. loop
 10.     clear()
 11. 
 12.     j = controls( 0 )   
 13.    
 14.     camPos = { j.lx * 20, j.ly * 20, 20 }
 15.     setCamera( camPos, { 0, 0, 0 } )
 16.    
 17.     drawObjects() 
 18.     update()
 19. repeat     

Run the program to see our rather strange scene. Move the left Joy-Con control stick around to change the camera angle.

As you can see, at the start of the program we're creating a simple 3D world using two objects. We have two cubes with unequal dimensions.

  1. obj = placeObject( cube, { 0 , 5, 0 }, { 1.5, 4, 0.5 } )
  2. flr = placeObject( cube, { 0, 0, 0 }, { 10, 0.1, 10 } )

Our first cube (the floating one) is stored in the variable called obj and the flat cube object is stored in the variable called flr.

The material we use for both objects is the same as we have used previously, so no need to go into detail there!

On line 7 we have our pointShadowLight() function to cast some lovely shadows on the floor. Our light is positioned at {0, 7, 3}, which is 7 metres above the centre point on the y axis, and 3 metres towards the camera on the z axis.

Let's take a look at those camera controls.

First of all, since we'll be using the Joy-Con controls, we'll need to call the controls() function and assign it to a variable:

 12.     j = controls( 0 )  

In this example, we've called that variable j. Nice and simple! Alright, on to the camera:

 14.     camPos = { j.lx * 10, j.ly * 10, 20 }
 15.     setCamera( camPos, { 0, 0, 0 } )

This time around we are calling the setCamera() function within our main loop. This means our program will reset the camera position every frame.

In order to change the camera position during the loop, we must store the camera position in a variable. On line 14, we create a variable called camPos which stores a position vector. Notice that in the x and y elements of the vector we have used our controls variable to access the left Joy-Con control stick x and y positions. We multiply the result of this by 10 to give an increased effect.

This means our left stick now controls the x and y axis of the camera position!

You might also notice that in the setCamera() function, our second argument for the camera direction is always { 0, 0, 0 }. The effect of this is that no matter where we move the camera, it will always be pointing at the centre of our 3D world space.

As always, try changing some of these values to see the effect it has on the program!

Making a First-Person Camera

Let's go through the process of creating a real first person camera control. We need to be able to move freely, turning the camera to point at whatever we want.

This is actually more complicated than it might sound, so get ready for some serious code!

The end result of this project will be usable in any game you might want to create, so feel free to take it for your own projects!

Our first task will be to make proper use of the right Joy-Con control stick so that we can look around freely.

Here's the full program below. Type or copy and paste it into the FUZE4 Nintendo Switch code editor.

  1. obj = placeObject( cube, { 0, 5, 0 }, { 1.5, 4, 0.5 } )
  2. flr = placeObject( cube, { 0, 0, 0 }, { 10, 0.1, 10 } )
  3.
  4. setObjectMaterial( obj, grey, 0, 1 )
  5. setObjectMaterial( flr, bisque, 0, 1 )
  6.
  7. pointShadowLight( { 0, 8, 2 }, white, 10, 1024 )
  8. 
  9. camPos = { 0, 5, 20 }
 10. angle = -90
 11. 
 12. loop
 13.     clear()
 14.     j = controls( 0 )
 15.     
 16.     angle += j.rx / 4
 17.     fwd = { cos( angle ), 0, sin( angle ) }
 18.     target = camPos + fwd
 19.     setCamera( camPos, target )
 20.     
 21.     drawObjects()
 22.     update()
 23. repeat     

As you can see, our first section of code is exactly the same as the previous one. We are creating our little 3D world of two cubes with the same material. We have our same light placement too.

  9. camPos = { 0, 5, 20 }

Our camera position begins at the vector stored in the camPos variable.

Let's talk about the new addition to the first section, the angle variable.

 10. angle = -90

Our angle variable will be used to calculate which direction we are looking. We begin at -90. This number is important because of the way we will use the sin() and cos() functions shortly.

Let's jump ahead a little to line 16 in our main loop.

 16.     angle += j.rx

This is where we modify the angle variable. We add the current value of the the right control stick (j.rx) to the variable. Notice that we are only using the x axis of the right Joystick for now. We will begin by looking left and right, up and down can come later!

Now, what are we doing with that variable?

 17.    fwd = { cos( angle ), 0, sin( angle ) }

This complex looking bit of code needs some explaining.

We are creating something called a forward vector. This is a special type of directional vector which tells us which direction is forwards, hence the name!

We are using the sin() and cos() functions to calculate the direction we want the camera to point based on the value of the angle variable.

As we change the position of the right Joy-Con control stick, the value of the angle variable changes, and the calculation on line 17 gives us a different result.

Notice that the y element of our forward vector is at 0 for now. This will change when we add the ability to look up and down.

Still with us? Well done!

 19.     target = camPos + fwd
 20.     setCamera( camPos, target )

Here we create a new variable called target. This variable is used in the setCamera() function on line 19. Rather than having a fixed target like before (which was { 0, 0, 0 } if you remember), we are constantly recalculating the target by adding the forward vector to the current camera position and setting it as the new target.

Once we've done that, we call the drawObjects() and update() functions as usual, then close the loop.

Run the program! Your right control stick will now move the camera direction left and right. If that's all working nicely, let's move on to vertical movement!

Vertical Camera Movement

In order to add vertical camera movement to our project, we actually only need a couple of lines. Take a look at the lines below and add them at the designated line numbers.

First we'll need a variable to store the vertical angle of the camera. We'll call this lookHeight. It must be defined outside of the main loop:

 11. lookHeight = 0

Now we need to modify this variable inside the main loop using the right control stick:

 18.     lookHeight += j.ry / 50

We have divided the result of the right control stick by 50 to give a more manageable movement speed.

Lastly, we must use this variable in the calculation of our foward vector:

 19.     fwd = { cos( angle ), lookHeight, sin( angle ) }

Alright! Let's take a look at the program in full so far:

  1. obj = placeObject( cube, { 0, 5, 0 }, { 1.5, 4, 0.5 } )
  2. flr = placeObject( cube, { 0, 0, 0 }, { 10, 0.1, 10 } )
  3.
  4. setObjectMaterial( obj, grey, 0, 1 )
  5. setObjectMaterial( flr, bisque, 0, 1 )
  6.
  7. pointShadowLight( { 0, 7, 3 }, white, 30, 1024 )
  8.
  9. camPos = { 0, 5, 20 }
 10. angle = -90
 11. lookHeight = 0
 12. 
 13. loop
 14.     clear()
 15.     j = controls( 0 )
 16.    
 17.     angle += j.rx
 18.     lookHeight += j.ry / 50
 19.     fwd = { cos( angle ), lookHeight, sin( angle ) }
 20.     target = camPos + fwd
 21.     setCamera( camPos, target )
 22.
 23.     drawObjects()
 24.     update()
 25. repeat

Run the program and use the right control stick to look around freely!

Adding Walking Movement

Make sure our projects are matching and we'll add the last section of code. We need to be able to walk around!

To be able to walk around with the left control stick, we only need to add three lines of code. They must be placed between the fwd = {cos( angle ), lookHeight, sin( angle )} and target += camPos + fwd lines. Let's go through them:

 20.    side = cross( fwd, { 0, 1, 0 } )
 21.    camPos += side * j.lx / 4

These two lines of code allow us to move left and right using the left control stick.

We create a variable called side which stores the result of a calculation called the cross product. The cross() function takes two vectors and gives an angle perpendicular to both. Notice we are using our forward vector and a vector of {0, 1, 0} which is directly upward. The angle perpendicular to these is horizontally across the x axis!

We then add the result of this calculation to our camPos variable multiplied by the left control stick x axis value, allowing us to move freely along the x axis, always keeping our camera pointed towards where we want.

Let's add the line to allow us to move along the z axis:

 22. camPos += normalize( { fwd.x, 0, fwd.z } ) * j.ly / 4

Since we only want to move along the ground rather than flying, we must normalize the vector and remove the y component. We then increase the camPos variable by this result, multiplied by our left control stick y axis value. Again, we divide by 4 to create a more manageable movement speed.

Let's take a last look at the whole project including these changes for reference:

  1. obj = placeObject( cube, { 0, 5, 0 }, { 1.5, 4, 0.5 } )
  2. flr = placeObject( cube, { 0, 0, 0 }, { 10, 0.1, 10 } )
  3.
  4. setObjectMaterial( obj, grey, 0, 1 )
  5. setObjectMaterial( flr, bisque, 0, 1 )
  6.
  7. pointShadowLight( { 0, 7, 3 }, white, 30, 1024 )
  8.
  9. camPos = { 0, 5, 20 }
 10. angle = -90
 11. lookHeight = 0
 12. 
 13. loop
 14.     clear()
 15.     j = controls( 0 )
 16.    
 17.     angle += j.rx
 18.     lookHeight += j.ry / 50
 19.     fwd = { cos( angle ), lookHeight, sin( angle ) }
 20.     side = cross( fwd, { 0, 1, 0 } )
 21.     camPos += side * j.lx / 4
 22.     camPos += normalize( { fwd.x, 0, fwd.z } ) * j.ly / 4 
 23.     target = camPos + fwd
 24.     setCamera( camPos, target )
 25.
 26.     drawObjects()
 27.     update()
 28. repeat

If you'd like to fly around in your 3D scene, it's actually a little simpler!

 22. camPos += fwd * j.ly / 4

Simple as that! We do not remove the y component of the vector and therefore do not need to normalize. Simply add the whole fwd vector to the camera position.

Run the program and take a walk around! Congratulations, you've programmed a first-person camera!

Why not build the scene into something more exciting? Use more placeObject() functions just as we did at the start to create more objects, then the drawObjects() function will take care of the rest!

Well done. You've complete the 3D Tutorials!

Functions and Keywords Used in this Tutorial

clear(), cos(), cross(), drawObjects(), loop, normalize(), placeObject(), pointShadowLight(), repeat, setCamera(), setObjectMaterial(), sin(), update()