使用THREE.js製作一款3D遊戲
本文是基於某位大神使用three.js設計遊戲的學習心得與知識分享 The Making of “The Aviator”: Animating a Basic 3D Scene with Three.js
前言
這個學期選修了一門計算機圖形學的課程,課程選用的教材是基於WebGL。在此之前我對計算機圖形學是沒有任何概念的,只知道如果想要設計一款遊戲具有圖形學的知識是很重要的。我從來就有一個念想就是製作一款遊戲,因此我對這方面是很有興趣的。 老師推薦了一本入門教材《WebGL程式設計指南》,這本教材講的很詳細很適合入門使用,我花了大約一週的時間將這本書看完,對webgl的程式設計有了個大致的瞭解,當然真的只是粗粗入門。學習過程中又瞭解到three.js這個基於webgl的第三方3d圖形庫,看到了很多酷炫的3d遊戲都使用了這個庫,我決定下一步就是學習這個庫的使用。 我大概看了半本的《THREE.js開發指南》,這本書很系統的講了這個第三方庫,但是難免很枯燥,於是就找到了現在這個使用three.js設計遊戲的這麼一個小專案。
程式碼結構
主函式由各種構件場景的函式組成,十分簡潔
function init(event){
createScene();
createLights();
createPlane();
createSea();
createSky();
document.addEventListener('mousemove', handleMouseMove, false);
loop();//迴圈函式,用於最後每一個幀的重繪,實現動畫效果
}
複製程式碼
使用dat.gui圖形介面動態的調整資料
dat.gui是一個第三方的圖形庫,通過這個圖形介面來調整資料真的很方便
//通過dat.gui來調整環境光
var controls = new function () {//宣告一個控制物件
this.ambientLightColor = "#dc8874";
}
//環境光的值可以是16進位制的數值,如"#ffffff",每次通過gui調整了color值都會觸發下面的匿名函式從而調整環境光的顏色,環境光加入到場景中後每次渲染場景時都會使用最新的環境光顏色值,從而實現了使用gui調整環境光顏色的功能
var gui = new dat.GUI;//建立gui物件
gui.addColor(controls,'ambientLightColor').onChange(function (e) {
ambientLight.color = new THREE.Color(e);//
});
複製程式碼
當然可以新增更多的資料來進行動態調整,比如照相機的位置,各種顏色數值,等等。這是一個超級使用的功能。
一、搭建一個場景
使用thre.js繪製3d圖形最基本的就是需要一個場景,場景像一個容器,至少需要包括燈光,照相機和渲染器
function createScene(){
HEIGHT = window.innerHeight;
WIDTH = window.innerWidth;
scene = new THREE.Scene();//建立場景
scene.fog = new THREE.Fog(0xf7d9aa, 100,950);//使用霧化的效果
var axes = new THREE.AxisHelper(200);//場景中新增一個三維座標系,便於觀察圖形的位置
scene.add(axes);
aspectRatio = WIDTH / HEIGHT;//寬高比設定為視窗大小,避免圖案的變形
fieldOfView = 50;
nearPlane = 0.1;
farPlane = 10000;
camera = new THREE.PerspectiveCamera(fieldOfView,aspectRatio,nearPlane,farPlane);//使用一個透視相機使物體具有3d的效果
camera.position.x = 0;//相機的位置和視點將影響觀察到的物體
camera.position.z = 200;
camera.position.y = 100;//待優化
renderer = new THREE.WebGLRenderer({ alpha: true, antialias: true });//宣告一個webgl的渲染器,這個渲染器就如同html中的canvas
renderer.setSize(WIDTH,HEIGHT);
renderer.shadowMap.enabled = true;
container = document.getElementById('world');
container.appendChild(renderer.domElement);//將這個渲染器加到html當中
複製程式碼
二、新增燈光
在three.js中新增燈光十分的簡單,不同的燈光有不同的作用,比如環境光,點光源,聚光燈等等。這裡用到了半球光,半球光其中設定的兩個引數天空和地面的顏色可以使場景更加的真實。
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;
//每次設定完燈光都需要把他新增到場景中
scene.add(hemisphereLight);
scene.add(shadowLight);
scene.add(ambientLight);
}
複製程式碼
三、創造一片大海
這裡的大海是通過一個倒置的圓柱體來實現的,通過調整照相機的位置,並且配合旋轉的動畫,在半球光的照射下就如同一片汪洋。
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);
}
複製程式碼
四、簡單又精緻的天空
我們使用幾塊大小不一的方塊隨機的堆疊在一起,它們就像雲朵一樣,很抽象派是吧!如果再把他們的位置隨機的擺放,配合轉動的動畫,是不是更像了呢!
//構造一個雲朵物件
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++){
//實現位置隨機,大小隨機
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);
}
複製程式碼
五、設計一架超酷的飛機
使用五個矩形打造一款飛機!這似乎聽起來有點困難,但是這真的很有意思,配合不同的顏色,在螺旋槳的轉動下,這款飛機真的很逼真!
var AirPlane = function() {
this.mesh = new THREE.Object3D();
// 這裡要做的是一個駕駛艙
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);
// 還要有引擎蓋
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);
// 做個尾巴吧
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);
// 機翼當然少不了,用長長的矩形穿過機身,多麼美妙!
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);
// 飛機前端旋轉的螺旋槳
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;
// 螺旋槳
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);
}
複製程式碼
好了,夥計!現在我們的場景中有了燈光,大海,天空還有飛機,但是,似乎還少了什麼。對的,我們要操控這個飛機!
六、控制我們的飛機
飛機能跟隨滑鼠移動的軌跡,為了做到更完美,當飛機上升和下降時,應該要有旋轉的感覺!
function handleMouseMove(event) {
// 我們要把滑鼠的座標值轉換成webgl系統中規格化的數值,從-1到1
// 這種轉換很簡單的夥計!tx = (x-width/2)/(width/2)
var tx = -1 + (event.clientX / WIDTH)*2;
// y軸在視窗座標系和webg座標系的方向是相反的,因此我們把他逆一下就可以
var ty = 1 - (event.clientY / HEIGHT)*2;
mousePos = {x:tx, y:ty};
}
function updatePlane(){
var targetY = 100+mousePos.y*75;//控制飛機在y軸25到175的位置
var targetX = mousePos.x*195;//控制飛機在x軸-195到195的位置
// 每一幀移動飛機移動的距離,使飛機最終到達滑鼠的位置,這樣製造出飛機緩緩飛向指定位置的效果,而不會顯得很突兀。
airplane.mesh.position.y += (targetY-airplane.mesh.position.y)*0.1;
airplane.mesh.position.x += (targetX-airplane.mesh.position.x)*0.5;
// 通過剩餘距離的長度來計算旋轉地幅度,這樣飛機如果一次性移動的距離很多相應的旋轉幅度就越大,與真實的情況也符合,使動畫更加真實。
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;
}
複製程式碼
七、讓畫面動起來、
實現動畫的本質就是每一幀改變相應的引數,不斷的渲染,使人眼感覺畫面在運動
function loop(){
airplane.propeller.rotation.x += 0.3;
sea.mesh.rotation.z += .005;
sky.mesh.rotation.z += .01;
updatePlane();
airplane.pilot.updateHairs();
// 渲染
renderer.render(scene, camera);
// 再次呼叫
requestAnimationFrame(loop);
}
複製程式碼
最後
看看我們的demo吧!
當然還有更多的創意還可以實現,不是嗎?