Make a 3D game with three.js

The Making of “The Aviator” : Animating a Basic 3D Scene with three.js

preface

This semester, I took a computer graphics course as an elective course, and the textbook for the course was WebGL. I had no idea about computer graphics until then, except that it was important to have a knowledge of graphics if you wanted to design a game. I’ve always wanted to make a game, so I’m very interested in that. The teacher recommended an introductory textbook “WebGL Programming Guide”, which was very detailed and suitable for beginners. I spent about a week reading this book and had a general understanding of WebGL programming, of course, it was really only a rough introduction. In the learning process, I learned about three.js, a third-party 3D graphics library based on WebGL, and saw that many cool 3D games use this library, so I decided to learn how to use this library in the next step. I read about half a book called The Three. js Development Guide, which covers this library in a very systematic way, but it’s boring, so I came across this little project of designing a game using Three. js.

The code structure

The main function is composed of the functions of various component scenarios and is very concise

function init(event){
    createScene();
    
    createLights();

    createPlane();
    
    createSea();
    
    createSky();
    
    document.addEventListener('mousemove', handleMouseMove, false);
    
    loop();// Loop function, used for the last each frame redraw, achieve animation effect
}
Copy the code

Use dat. GUI to dynamically adjust data

Dat. GUI is a third party graphics library, and it’s really convenient to adjust data through this graphical interface

// Adjust the ambient light through dat. GUI
var controls = new function () {// Declare a control object
    this.ambientLightColor = "#dc8874";
}
// The value of the ambient light can be a hexadecimal value, such as "# FFFFFF ". Each time the color value is adjusted through the GUI, the following anonymous function will be triggered to adjust the color of the ambient light. After the ambient light is added to the scene, the latest ambient light color value will be used every time the scene is rendered
var gui = new dat.GUI;// Create GUI objects
gui.addColor(controls,'ambientLightColor').onChange(function (e) {
	ambientLight.color = new THREE.Color(e);// 
});
Copy the code

Of course, you can add more data for dynamic adjustment, such as camera position, various color values, and so on. This is a super use feature.

1. Build a scene

Drawing 3d graphics with thre.js basically requires a scene. The scene is like a container, including at least lights, camera and renderer

function createScene(){

    HEIGHT = window.innerHeight;
    WIDTH = window.innerWidth;

    scene = new THREE.Scene();// Create a scene
    scene.fog = new THREE.Fog(0xf7d9aa.100.950);// Use the atomizing effect
	var axes = new THREE.AxisHelper(200);// Add a 3d coordinate system to the scene to see the position of the graph
    scene.add(axes);
    aspectRatio = WIDTH / HEIGHT;// Set aspect ratio to window size to avoid distortion of pattern
    fieldOfView = 50;
    nearPlane = 0.1;
    farPlane = 10000;
    camera = new THREE.PerspectiveCamera(fieldOfView,aspectRatio,nearPlane,farPlane);// Use a perspective camera to give the object a 3D effect
    camera.position.x = 0;// The camera's position and viewpoint will affect what objects are observed
    camera.position.z = 200;
    camera.position.y = 100;/ / to be optimized

    renderer = new THREE.WebGLRenderer({ alpha: true.antialias: true });// Declare a WebGL renderer, which is like a canvas in HTML
    renderer.setSize(WIDTH,HEIGHT);
    renderer.shadowMap.enabled = true;
    container = document.getElementById('world');
    container.appendChild(renderer.domElement);// Add the renderer to the HTML

    
Copy the code

Two, add lights

It is very easy to add lights to three.js. Different lights have different functions, such as ambient light, point light, spot light, etc. Hemispheres are used here. The two parameters set in hemispheres are the color of the sky and the ground to make the scene more realistic.

function createLights(){
    hemisphereLight = new THREE.HemisphereLight(0xbbbbbb.0x000000.9.);
    ambientLight = new THREE.AmbientLight(controls.ambientLightColor);
    shadowLight = new THREE.DirectionalLight(0xffffff.9.);
    shadowLight.castShadow = true;
    shadowLight.shadow.camera.left = - 400.;
    shadowLight.shadow.camera.right = 400;
    shadowLight.shadow.camera.top = 400;
    shadowLight.shadow.camera.bottom = - 400.;
    shadowLight.shadow.camera.near = 1;
    shadowLight.shadow.camera.far = 1000;
    shadowLight.shadow.mapSize.width = 2048;
    shadowLight.shadow.mapSize.height = 2048;
// Every time you set up a light, you need to add it to the scene
    scene.add(hemisphereLight);
    scene.add(shadowLight);
    scene.add(ambientLight);
}
Copy the code

Create an ocean

Here, the ocean is realized by an inverted cylinder, which is illuminated by hemispherical light by adjusting the camera’s position and rotating animation.

Sea = function(){
    var geom = new THREE.CylinderGeometry(600.600.800.40.10);
    geom.applyMatrix(new THREE.Matrix4().makeRotationX(-Math.PI/2));
    var mat = new THREE.MeshPhongMaterial({
        color:Colors.blue,
        transparent:true.opacity:6..shading:THREE.FlatShading,
    });
    this.mesh = new THREE.Mesh(geom, mat);
    this.mesh.receiveShadow = true;

}

var sea;
function createSea(){
    sea = new Sea();
    sea.mesh.position.y = - 600.;
    scene.add(sea.mesh);
}
Copy the code

4. Simple and delicate sky

We use blocks of different sizes stacked randomly, like clouds, very abstract, right? If their positions are randomly placed, with the rotation of the animation, is not more like it!

// Construct a cloud object
Cloud = function(){
    this.mesh = new THREE.Object3D();
    var geom = new THREE.BoxGeometry(20.20.20);

    var mat = new THREE.MeshPhongMaterial({
        color:Colors.white,
    });
    var nBlocs = 3+Math.floor(Math.random()*3);

    for(i=0; i<nBlocs; i++){// Implement random position and random size
        var m = new THREE.Mesh(geom, mat);
        m.position.x = i*15;
        m.position.y =Math.random()*10;
        m.position.z = Math.random()*10;
        m.rotation.z = Math.random()*Math.PI*2;
        m.rotation.y = Math.random()*Math.PI*2;
        var s = 1. + Math.random()*9.;
        m.scale.set(s,s,s);

        m.castShadow = true;
        m.receiveShadow = true;

        this.mesh.add(m);
    }
}

Sky = function(){
    this.mesh = new THREE.Object3D();
    this.nClouds = 20;
    var stepAngle = Math.PI*2 / this.nClouds;
    for (var i=0; i<this.nClouds; i++){var c = new Cloud();
        var a = stepAngle*i;
        var h = 750 + Math.random()*200;
        c.mesh.position.y = Math.sin(a)*h;
        c.mesh.position.x = Math.cos(a)*h;
        c.mesh.rotation.z = - Math.PI/2+a;
        c.mesh.position.z = - 50-Math.random()*400;
        var s = 1+Math.random()*2;
        c.mesh.scale.set(s,s,s);
        this.mesh.add(c.mesh); }}var sky;

function createSky(){
    sky = new Sky();
    sky.mesh.position.y = - 600.;
    scene.add(sky.mesh);
}
Copy the code

Design a cool airplane

Use five rectangles to build a plane! This may sound a little difficult, but it is really interesting, with the different colors, in the rotation of the propeller, the plane is really realistic!

var AirPlane = function() {

    this.mesh = new THREE.Object3D();

    // Here is a cockpit
    var geomCockpit = new THREE.BoxGeometry(80.50.50.1.1.1);
    var matCockpit = new THREE.MeshPhongMaterial({color:Colors.red, shading:THREE.FlatShading});
    geomCockpit.vertices[4].y-=10;
    geomCockpit.vertices[4].z+=20;
    geomCockpit.vertices[5].y-=10;
    geomCockpit.vertices[5].z-=20;
    geomCockpit.vertices[6].y+=20;
    geomCockpit.vertices[6].z+=20;
    geomCockpit.vertices[7].y+=20;
    geomCockpit.vertices[7].z-=20;

    var cockpit = new THREE.Mesh(geomCockpit, matCockpit);
    cockpit.castShadow = true;
    cockpit.receiveShadow = true;
    this.mesh.add(cockpit);

    // Also have hood
    var geomEngine = new THREE.BoxGeometry(20.50.50.1.1.1);
    var matEngine = new THREE.MeshPhongMaterial({color:Colors.white, shading:THREE.FlatShading});
    var engine = new THREE.Mesh(geomEngine, matEngine);
    engine.position.x = 40;
    engine.castShadow = true;
    engine.receiveShadow = true;
    this.mesh.add(engine);

    // Make a tail
    var geomTailPlane = new THREE.BoxGeometry(15.20.5.1.1.1);
    var matTailPlane = new THREE.MeshPhongMaterial({color:Colors.red, shading:THREE.FlatShading});
    var tailPlane = new THREE.Mesh(geomTailPlane, matTailPlane);
    tailPlane.position.set(- 35.25.0);
    tailPlane.castShadow = true;
    tailPlane.receiveShadow = true;
    this.mesh.add(tailPlane);

    // Wings, of course, with long rectangles through the fuselage, how wonderful!
    var geomSideWing = new THREE.BoxGeometry(40.8.150.1.1.1);
    var matSideWing = new THREE.MeshPhongMaterial({color:Colors.red, shading:THREE.FlatShading});
    var sideWing = new THREE.Mesh(geomSideWing, matSideWing);
    sideWing.castShadow = true;
    sideWing.receiveShadow = true;
    this.mesh.add(sideWing);

    // The rotating propeller at the front of an airplane
    var geomPropeller = new THREE.BoxGeometry(20.10.10.1.1.1);
    var matPropeller = new THREE.MeshPhongMaterial({color:Colors.brown, shading:THREE.FlatShading});
    this.propeller = new THREE.Mesh(geomPropeller, matPropeller);
    this.propeller.castShadow = true;
    this.propeller.receiveShadow = true;

    / / the propeller
    var geomBlade = new THREE.BoxGeometry(1.100.20.1.1.1);
    var matBlade = new THREE.MeshPhongMaterial({color:Colors.brownDark, shading:THREE.FlatShading});

    var blade = new THREE.Mesh(geomBlade, matBlade);
    blade.position.set(8.0.0);
    blade.castShadow = true;
    blade.receiveShadow = true;
    this.propeller.add(blade);
    this.propeller.position.set(50.0.0);
    this.mesh.add(this.propeller);

};

var airplane;

function createPlane(){
    airplane = new AirPlane();
    airplane.mesh.scale.set(25..25..25.);
    airplane.mesh.position.y = 100;
    scene.add(airplane.mesh);
}
Copy the code

All right, man! Now we have lights, sea, sky and planes in the scene, but there seems to be something missing. That’s right! We’re gonna fly this plane!

Take control of our plane

The plane can follow the movement of the mouse track, to achieve perfect, when the plane up and down, should have a sense of rotation!


function handleMouseMove(event) {

    // We want to convert the mouse coordinate value to the normalized value in webGL system, from -1 to 1
    // This conversion is easy dude! tx = (x-width/2)/(width/2)

    var tx = - 1 + (event.clientX / WIDTH)*2;

    // The Y-axis is in the opposite direction of the window and WebG coordinates, so we can invert it
    

    var ty = 1 - (event.clientY / HEIGHT)*2;
    mousePos = {x:tx, y:ty};

}


function updatePlane(){
    var targetY = 100+mousePos.y*75;// Control the aircraft from Y-axis 25 to 175
    var targetX = mousePos.x*195;// Control the aircraft from X-axis -195 to 195

    // Each frame moves the distance of the aircraft so that the aircraft finally reaches the mouse position. This creates the effect of the aircraft slowly flying towards the specified position without appearing awkward.
    airplane.mesh.position.y += (targetY-airplane.mesh.position.y)*0.1;
    airplane.mesh.position.x += (targetX-airplane.mesh.position.x)*0.5;
    // The rotation amplitude is calculated by the length of the remaining distance, so that if the aircraft moves a large distance at one time, the corresponding rotation amplitude will be larger, which is also consistent with the real situation, making the animation more realistic.
    airplane.mesh.rotation.z = (targetY-airplane.mesh.position.y)*0.0256;
    airplane.mesh.rotation.x = (airplane.mesh.position.y-targetY)*0.0256;

    airplane.propeller.rotation.x += 0.3;

}
Copy the code

Seven, let the picture move,

The essence of animation is to change the corresponding parameters of each frame, constantly render, so that the human eye feels the picture in motion

function loop(){
    airplane.propeller.rotation.x += 0.3;
    sea.mesh.rotation.z += 005.;
    sky.mesh.rotation.z += .01;
    updatePlane();
    airplane.pilot.updateHairs();


    / / rendering
    renderer.render(scene, camera);

    // call it again
    requestAnimationFrame(loop);
}
Copy the code

The last

Check out our demo!

Surely there are more ideas that can be achieved, right?