Three.js series: write a first / third person perspective game

Posted by phlow on Wed, 02 Mar 2022 06:22:55 +0100

Hello, everyone. I'm Qiufeng. I'm here Last I talked about three The goal of JS series and Baoke dream game, let's pass three JS to talk about the perspective following in the game. Believe me, readers play more or less games, such as king glory, Jedi survival, baokemeng, Zelda, protogod and so on. So do you know what perspective they are? Do you know the difference between the first person perspective and the third person perspective? How can we achieve this effect through code? If you are curious about the above questions and can't fully answer them. Then please follow me and look down.

Perspective explanation

First, let's look at the concepts of first person perspective and third person perspective. In fact, for us, the first person and the third person are very familiar. The first person tells a thing in his own tone. For example, autobiographies are written in this form, while the third person is a bystander. For example, many novels are expanded by him (xxx), and the audience looks at the whole story from the perspective of God. The corresponding first person perspective and third person perspective are also the same concepts, only visual** So what are their differences** The advantage of the first person perspective is that it can bring the player the maximum sense of immersion. Observing the scene and picture from the first person perspective "I" can make the player feel the details more carefully. The most common are things like Jedi survival and the best flying car.

The first person perspective also has its limitations. The player's field of vision is limited and cannot see a wider field of vision. The other is that the first person perspective will bring "3D vertigo" to players. When the reaction speed is not as fast as the lens speed, it will cause dizziness. What about the third person perspective? His advantage is freedom and wide vision. The movement of characters is separated from the perspective. One is used to manipulate the direction of the characters and the other is used to manipulate the direction of the field of vision.

Its disadvantage is that it can't focus on the part well and is easy to miss the details. But generally speaking, most games currently provide switching between two perspectives to meet different situations. For example, in absolute survival, you usually follow and move with the third person perspective when walking, and you usually use the first person perspective when shooting. Well, so far, we have known the concepts and differences of first person perspective and third person perspective. Then let's take the third person perspective as an example to analyze how we can achieve such an effect? (after the third person is written, it can be changed into the first person with a little modification, so take the more complex third person as an example.) how many steps does it take to put the elephant in the refrigerator? Three steps! Open the refrigerator, put the elephant in the refrigerator and close the refrigerator. Obviously, it's hard to put an elephant in the refrigerator, but from a macro point of view, there are three steps. Therefore, we also divide the realization of the function of third person perspective into three steps:

Step split

The following steps will not contain any code, please rest assured: 1 We all know how the characters move. In the physical real world, we move by our legs and move when we step away. What about this process from a broader perspective? In fact, if we look beyond the earth, from a further point of view, our movement is more like translational changes one by one. Similarly, we use translation to represent motion in computer. We were familiar with the details of translation changes before. If we are not familiar with them now, it doesn't matter. First look at the coordinate axis below. (the side length of the small square is 1)

When the small square moves from position A1 to position A2, it is the translation change. If it is expressed by mathematical expression, it is

What does it mean? That is, let's add 2 to the x value of all the dots in the small box, while the value of y remains unchanged. Let's take some values at random to verify. For example, the small square in A1 position is (0,0) in the lower left corner. Through the above changes, it becomes (2,0). Let's see in A2 that the new position of the small square is (2,0); Then substitute (1,1) in the upper right corner and find that it becomes (3,1), which is the same as the position we actually move to. So there is no problem with the above formula. But later, we felt that the formula like the above was a little less general. As for why it is not general enough here, it will be explained in detail in the later series of articles, because it also involves other changes, such as rotation and scaling, which can be described by a matrix. Therefore, if the translation can also be expressed by a matrix, the whole problem will become simple, That is: motion change = matrix change. Let's see what it looks like to turn the initial formula into a matrix:

You can briefly explain how the matrix on the right comes from

This part in the upper left corner is called the identity matrix, and the following 20 is the translation change we need. As for why it has changed from 2-dimensional to 3-dimensional, it is because the concept of homogeneous matrix is introduced. The same principle, analogy to three-dimensional, we need to use four-dimensional matrix. So, through a series of examples, we finally want to get a conclusion that all motions are matrix changes.

2. The lens faces the characters. We all know that in the real world, the vision of our eyes is limited, and the same is true in computers. Assuming that our field of vision is a 3 * 3 grid in the computer, let's take the previous coordinate axis as an example. The yellow area is the visible area of our field of vision:

Now let's move the small block to the right by 3 units, and then move one unit online.

At this time, we will find that we can't see this small piece in our field of vision. Imagine that we are playing a shooting game. The enemy is moving in front of us. What will we do to find it? Yes, we rotate our heads, exposing the enemy to our view. Like this:

This will lock the enemy, which can always make the characters appear in our field of vision and remain relatively stationary. 3. The lens is at the same distance from the character. It's not enough to have the lens facing the character. We have to keep our lens at the same distance from the character. Why do you say that? First of all, let's take an example of our coordinate axis, but this time we will expand a z axis: then let's look at the plane screenshot under normal conditions

Screenshot:

Now let's move our block 1 unit to - Z:

Screenshot:

At this time, we find that the small block becomes smaller, and as the small block moves in the - z direction more and more, the small block we see will become smaller and smaller. At this time, we obviously didn't change our perspective, but we still couldn't track the small pieces well. Therefore, we need to move the position of our perspective. What will we do when we can't see a distant road sign? Yes, get closer!

Screenshot:

Perfect! Now we will theoretically realize the function of a third person perspective through the explanation in three directions!

Code

Next, we only need to implement the code according to our above theory. The code cannot be implemented in another language. The principle is very simple. 1. Initialize canvas scene

<canvas class="webgl"></canvas>  
...  
<script>  
// Create scene  
const scene = new THREE.Scene()  
// Add camera  
const camera = new THREE.PerspectiveCamera(75, sizes.width / sizes.height);  
camera.position.y = 6;  
camera.position.z = 18;  
const controls = new OrbitControls(camera, canvas)  
controls.enableDamping = true; // To set damping, you need to call in update  
scene.add(camera);  
// Render  
const renderer = new THREE.WebGLRenderer({  
    canvas  
})  
renderer.setSize(sizes.width, sizes.height)  
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))  
renderer.render(scene, camera);  
</script>  

Scenes, cameras and renderers are relatively fixed things. This section does not mainly explain them, but can be understood as some necessary statements during the initialization of our project. At this time, when we open the page, it is a dark piece. For beauty, I add a floor to the whole scene.

// Set floor  
const geometry = new THREE.PlaneGeometry(1000, 1000, 1, 1);  
// Floor map  
const floorTexture = new THREE.ImageUtils.loadTexture( '12.jpeg' );  
floorTexture.wrapS = floorTexture.wrapT = THREE.RepeatWrapping;   
floorTexture.repeat.set( 10, 10 );  
// Floor material  
const floorMaterial = new THREE.MeshBasicMaterial({   
    map: floorTexture,   
    side: THREE.DoubleSide   
});  
  
const floor = new THREE.Mesh(geometry, floorMaterial);  
// Set floor position  
floor.position.y = -1.5;  
floor.rotation.x = - Math.PI / 2;  
  
scene.add(floor);  
`

![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/aa8cea3705594d10a7e01e7225283a78~tplv-k3u1fbpfcp-zoom-1.image)

The picture is good at this time\~2.According to the theory of character movement, we need to add a character. Here, for convenience, we still need to add a small box:

// Small slider  
const boxgeometry = new THREE.BoxGeometry(1, 1, 1);  
const boxMaterials = [];  
for (let i = 0; i < 6; i++) {  
    const boxMaterial = new THREE.MeshBasicMaterial({  
        color: Math.random() * 0xffffff,  
    });  
    boxMaterials.push(boxMaterial);  
}  
// Small pieces  
const box = new THREE.Mesh(boxgeometry, boxMaterials);  
box.position.y = 1;  
box.position.z = 8;  
  
scene.add(box);  
  

In order to look good, I added six different colors to the small piece.

Although it still looks a little crude, as the saying goes, high-end ingredients often only need the most simple cooking methods. Although the small piece is small, it has all kinds of internal organs. Now, after we render the small block, all we need to do is bind the shortcut keys.

Corresponding code:

// Control code  
const keyboard = new THREEx.KeyboardState();  
const clock = new THREE.Clock();  
const tick = () => {  
    const delta = clock.getDelta();  
    const moveDistance = 5 * delta;  
    const rotateAngle = Math.PI / 2 * delta;  
      
    if (keyboard.pressed("down"))  
        box.translateZ(moveDistance);  
    if (keyboard.pressed("up"))  
        box.translateZ(-moveDistance);  
    if (keyboard.pressed("left"))  
        box.translateX(-moveDistance);  
    if (keyboard.pressed("right"))  
        box.translateX(moveDistance);  
  
    if (keyboard.pressed("w"))  
        box.rotateOnAxis( new THREE.Vector3(1,0,0), rotateAngle);  
    if (keyboard.pressed("s"))  
        box.rotateOnAxis( new THREE.Vector3(1,0,0), -rotateAngle);  
    if (keyboard.pressed("a"))  
        box.rotateOnAxis( new THREE.Vector3(0,1,0), rotateAngle);  
    if (keyboard.pressed("d"))  
        box.rotateOnAxis( new THREE.Vector3(0,1,0), -rotateAngle);  
          
    renderer.render(scene, camera)  
    window.requestAnimationFrame(tick)  
}  
tick();  

Here we will explain translateZ and translateX. These two functions literally mean to move to the Z-axis and x-axis. If you want to move forward, move to the - z-axis. If you want to move to the left, move to the - x-axis. clock. What does getDelta () mean? Put it simply The function of the getDelta () method is to obtain the time interval between the first and second execution of the method. For example, we want to move 5 units forward in 1 second, but the direct movement must be stiff, so we want to add animation. We know that in order to achieve smooth animation, it is generally realized through the browser's APIrequestAnimationFrame. The browser will control the rendering frequency. Generally, when the performance is ideal, it will render about 60 times per second. In actual projects, if the scene to be rendered is complex, it will generally be less than 60, that is, the time interval between two frames is greater than 16.67ms. Therefore, in order to move these five units, we split the moving distance of each frame into these 60 renderings. Finally, let's talk about rotateOnAxios, which is mainly used to control the rotation of small boxes.

. rotateonworldaxis (axis: vector3, angle: float): this axis – a standardized vector in world space.
Angle – angle, expressed in radians.

3. Camera and character synchronization review the theoretical part. Our last step is to keep the camera (human eye) and object relatively stationary, that is, the distance remains unchanged.

const tick = () => {  
  ...  
  const relativeCameraOffset = new THREE.Vector3(0, 5, 10);  
    
  const cameraOffset = relativeCameraOffset.applyMatrix4( box.matrixWorld );  
    
  camera.position.x = cameraOffset.x;  
  camera.position.y = cameraOffset.y;  
  camera.position.z = cameraOffset.z;  
  // Always keep the camera looking at the object  
  controls.target = box.position;  
  ...  
}  

The core point here is relativecameraoffset applyMatrix4( box.matrixWorld ); In fact, we talked about this in the theory part, because the underlying principle of our object movement is to make matrix changes, so if we want to keep the distance between the camera (human eye) and the object unchanged, we only need to make the camera (human eye) and the object make the same changes. And in three All the changes of the object in JS are recorded in In the matrix, as long as the external scene does not change, then Matrixworld is equal to matrix . And applymatrix4 means multiplication.

Effect demonstration

In this way, I finally realized the whole function! See you next time!

Source address: https://github.com/hua1995116/Fly-Three.js

epilogue

❤️ Follow + like + collect + comment + forward ❤️, Original is not easy, encourage the author to create better articles

Note the official account of autumn wind, a front end official account focused on front-end interview, engineering and open source.

Topics: Javascript Front-end Web Development webgl