一、場景構造
three.js基礎
如果是剛接觸3D引擎,關於three.js的基礎知識可以參考之前的博客Three.js+tween.js 基礎(一) 中的基本概念篇部分。(場景 、相機 、渲染器 、添加對象)
微信小遊戲中使用three
使用微信開發者工具創建新的小遊戲模板,AppID選擇測試號,得到的是飛機大戰的源文件,在這個基礎上進行修改。
game.js:
import './js/libs/weapp-adapter'
import './js/libs/symbol'
import Main from './js/main'
new Main()
game.json:
{
"deviceOrientation": "landscapeRight"
}
改爲橫屏遊戲。
最後,清空images、audio和js文件夾。添加 libs 和main.js空文件到js文件夾。
最終得到:
代碼部分的修改在main.js。
靜止的場景
代碼主體部分:
// 引入three
import * as THREE from 'libs/three.js'
// 一些全局變量
var Colors = { ... }
var WIDTH = window.innerWidth;
var HEIGHT = window.innerHeight;
var Sea = function () { ... }
var Cloud = function () { ... }
var Sky = function () { ... }
var AirPlane = function () { ... }
var Pilot = function () { ... }
/**
* 遊戲主函數
*/
export default class Main {
constructor() {
// 創建場景,相機和渲染器
this.createScene();
// 添加光源
this.createLights();
this.start()
}
createScene() { ... }
createLights() { ... }
createPlane() { ... }
createSea() { ... }
createSky() { ... }
start() {
// 添加對象
this.createPlane();
this.createSea();
this.createSky();
window.requestAnimationFrame(this.loop.bind(this), canvas);
}
update() {
}
loop() {
this.update()
this.renderer.render(this.scene, this.camera);
window.requestAnimationFrame(this.loop.bind(this), canvas);
}
}
動畫渲染
修改update():
update() {
// 轉動大海和雲
this.sea.mesh.rotation.z += .005;
this.sky.rotation.z += .01;
// 更新每幀的飛機
this.updatePlane();
// 更新每幀的海浪
this.sea.moveWaves();
}
添加 updatePlane(),更新每幀的飛機
updatePlane() {
this.airplane.propeller.rotation.x += 0.3;
this.airplane.pilot.updateHairs();
}
爲 Pilot 添加原型函數 updateHairs() ,讓頭髮飄起來:
Pilot.prototype.updateHairs = function () {
// 獲得頭髮
var hairs = this.hairsTop.children;
// 根據 angleHairs 的角度更新頭髮
var l = hairs.length;
for (var i = 0; i < l; i++) {
var h = hairs[i];
// 每根頭髮將週期性的基礎上原始大小的75%至100%之間作調整。
h.scale.y = .75 + Math.cos(this.angleHairs + i / 3) * .25;
}
// 在下一幀增加角度
this.angleHairs += 0.16;
}
更新每幀的海浪,爲 Sea 添加原型函數 moveWaves() :
Sea.prototype.moveWaves = function () {
// 獲取頂點
var verts = this.mesh.geometry.vertices;
var l = verts.length;
for (var i = 0; i < l; i++) {
var v = verts[i];
// 獲取關聯的值
var vprops = this.waves[i];
// 更新頂點的位置
v.x = vprops.x + Math.cos(vprops.ang) * vprops.amp;
v.y = vprops.y + Math.sin(vprops.ang) * vprops.amp;
// 下一幀自增一個角度
vprops.ang += vprops.speed;
}
// 告訴渲染器代表大海的幾何體發生改變
// 事實上,爲了維持最好的性能
// Three.js 會緩存幾何體和忽略一些修改
// 除非加上這句
this.mesh.geometry.verticesNeedUpdate = true;
this.mesh.rotation.z += .005;
}
二、輪盤控制
創建UI部分
輪盤控制,分數信息,技能按鈕這些部分和遊戲主體分開,都創建在UI部分,使用新的場景和正交相機,需要保證在各個機型位置相對不變。這裏只添加輪盤控制,分數信息,技能按鈕以後再寫。。。。
修改 start() 、loop() :
start() {
// 添加對象
this.createPlane();
this.createSea();
this.createSky();
this.createUI();
window.requestAnimationFrame(this.loop.bind(this), canvas);
}
loop() {
this.update()
this.renderer.render(this.scene, this.camera);
this.ui.render(this.renderer);
window.requestAnimationFrame(this.loop.bind(this), canvas);
}
添加 createUI()
createUI() {
// 利用新建一個場景,並創建一個正交相機
// 把UI元素放在這個場景裏,渲染的時候同時渲染多個場景
this.ui = new UI();
// 創建輪盤
var controller = new Controller();
// console.log(controller);
controller.mesh.position.set(30 + controller.controllerRadius - WIDTH * 0.5, 20 + controller.controllerRadius - HEIGHT * 0.5, 0);
this.controller = controller;
this.ui.add(this.controller.mesh);
}
添加 UI 及其原型函數 add()、render()
var UI = function () {
this.scene = new THREE.Scene();
this.camera = new THREE.OrthographicCamera(WIDTH / -2, WIDTH / 2, HEIGHT / 2, HEIGHT / -2, 0, 10000);
this.camera.position.z = 10000;
}
UI.prototype.add = function (obj) {
this.scene.add(obj);
}
UI.prototype.render = function (renderer) {
renderer.clearDepth();
renderer.render(this.scene, this.camera);
}
添加輪盤 Controller
var Controller = function () {
this.mesh = new THREE.Object3D();
var controllerRadius = HEIGHT * 0.17 > 105 ? 105 : HEIGHT * 0.17;
this.controllerRadius = controllerRadius;
// 創建輪盤
var geometry = new THREE.CircleGeometry(controllerRadius, 32);
var material = new THREE.LineBasicMaterial({ color: 0xf7d9aa });
geometry.vertices.shift();
var circle = new THREE.LineLoop(geometry, material);
this.mesh.add(circle);
var geometry = new THREE.CircleGeometry(controllerRadius, 32);
var material = new THREE.MeshBasicMaterial({
color: 0xffffff,
transparent: true,
opacity: 0.1
});
var bgcircle = new THREE.Mesh(geometry, material);
this.mesh.add(bgcircle);
// 創建當前位置
var wheel = new THREE.Object3D();
wheel.name = "wheel";
var geometry = new THREE.CircleGeometry(controllerRadius * 0.33, 32);
var material = new THREE.MeshBasicMaterial({
color: 0xffffff,
transparent: true,
opacity: 0.1
});
var circle1 = new THREE.Mesh(geometry, material);
wheel.add(circle1);
var geometry = new THREE.CircleGeometry(controllerRadius * 0.33 - 2, 32);
var material = new THREE.MeshBasicMaterial({
color: 0x201D13,
transparent: true,
opacity: 0.1
});
var circle2 = new THREE.Mesh(geometry, material);
wheel.add(circle2);
this.mesh.add(wheel);
// 創建指向標
}
輪盤控制
start()中添加事件監聽,update()中更新每幀的鼠標位置和輪盤
start() {
// 添加對象
this.createPlane();
this.createSea();
this.createSky();
this.createUI();
this.mousePos = { x: 0, y: 0 };
// 初始化事件監聽
this.touchEvent();
window.requestAnimationFrame(this.loop.bind(this), canvas);
}
update() {
// 轉動大海和雲
this.sea.mesh.rotation.z += .005;
this.sky.rotation.z += .01;
// 更新每幀的飛機
this.updatePlane();
// 更新每幀的海浪
this.sea.moveWaves();
// 更新每幀的鼠標位置
this.updatePosition();
}
添加 touchEvent() 事件監聽,實時更新 mousePos
touchEvent() {
canvas.addEventListener('touchstart', ((e) => {
e.preventDefault()
var radius = this.controller.controllerRadius;
var tx = (e.touches[0].clientX - 30 - radius) / radius;
var ty = (HEIGHT -e.touches[0].clientY - 20 - radius) / radius;
if (this.checkIsFingerOnController(tx, ty)) {
this.touched = true;
this.controller.mesh.children[2].children[1].material.opacity = 0.3;
this.mousePos = { x: tx, y: ty };
}
}).bind(this))
canvas.addEventListener('touchmove', ((e) => {
e.preventDefault()
if (this.touched) {
var radius = this.controller.controllerRadius;
var tx = (e.touches[0].clientX - 30 - radius) / radius;
var ty = (HEIGHT-e.touches[0].clientY - 20 - radius) / radius;
if (this.checkIsFingerOnController(tx, ty)){
this.mousePos = { x: tx, y: ty };
}
else{
let k = ty/tx;
if (tx > 0 && ty > 0) {tx = 1 / Math.sqrt(k * k + 1);ty = k * tx;}
else if (tx < 0 && ty > 0) { tx = -1 / Math.sqrt(k * k + 1); ty = k * tx; }
else if (tx > 0 && ty < 0) { tx = 1 / Math.sqrt(k * k + 1); ty = k * tx; }
else { tx = -1 / Math.sqrt(k * k + 1); ty = k * tx; }
this.mousePos = { x: tx, y: ty };
}
}
}).bind(this))
canvas.addEventListener('touchend', ((e) => {
e.preventDefault()
this.touched = false;
this.controller.mesh.children[2].children[1].material.opacity = 0.1;
this.mousePos = { x: 0, y: 0 };
}).bind(this))
}
checkIsFingerOnController(x, y) {
return x*x+y*y<1;
}
添加 updatePosition() ,更新當前輪盤位置
updatePosition() {
var radius = this.controller.controllerRadius;
// 在x軸上-radius至radius之間移動點標
// 根據鼠標的位置在-1與1之間的範圍,使用 normalize 函數實現(如下)
var targetX = this.normalize(this.mousePos.x, -1, 1, (-1 + 0.33) * radius, (1 - 0.33)*radius);
var targetY = this.normalize(this.mousePos.y, -1, 1, (-1 + 0.33) * radius, (1 - 0.33)*radius);
// 在每幀通過添加剩餘距離的一小部分的值移動點標
this.controller.mesh.children[2].position.x += (targetX - this.controller.mesh.children[2].position.x) * 0.25;
this.controller.mesh.children[2].position.y += (targetY - this.controller.mesh.children[2].position.y) * 0.25;
}
normalize(v, vmin, vmax, tmin, tmax) {
var nv = Math.max(Math.min(v, vmax), vmin);
var dv = vmax - vmin;
var pc = (nv - vmin) / dv;
var dt = tmax - tmin;
var tv = tmin + (pc * dt);
return tv;
}
修改 updatePlane() ,更新當前飛機位置
updatePlane() {
// 在x軸上-140至140之間和y軸25至175之間移動飛機
// 根據鼠標的位置在-1與1之間的範圍,使用 normalize 函數實現(如下)
var targetX = this.normalize(this.mousePos.x, -1, 1, -140, 140);
var targetY = this.normalize(this.mousePos.y, -1, 1, 25, 175);
// 更新飛機的位置
//this.airplane.mesh.position.x = targetX;
// 在每幀通過添加剩餘距離的一小部分的值移動飛機
this.airplane.mesh.position.y += (targetY - this.airplane.mesh.position.y) * 0.1;
this.airplane.mesh.position.x += (targetX - this.airplane.mesh.position.x) * 0.05;
// 剩餘的距離按比例轉動飛機
this.airplane.mesh.rotation.z = (targetY - this.airplane.mesh.position.y) * 0.0128;
this.airplane.mesh.rotation.x = (this.airplane.mesh.position.y - targetY) * 0.0064;
this.airplane.propeller.rotation.x += 0.3;
this.airplane.pilot.updateHairs();
}
代碼主體部分:
// 引入three
import * as THREE from 'libs/three.js'
// 一些全局變量
var Colors = { ... }
var WIDTH = window.innerWidth;
var HEIGHT = window.innerHeight;
var Sea = function () { ... }
Sea.prototype.moveWaves = function () { ... }
var Cloud = function () { ... }
var Sky = function () { ... }
var AirPlane = function () { ... }
var Pilot = function () { ... }
Pilot.prototype.updateHairs = function () { ... }
var UI = function () { ... }
UI.prototype.add = function (obj) { ... }
UI.prototype.render = function (renderer) { ... }
var Controller = function () { ... }
/**
* 遊戲主函數
*/
export default class Main {
constructor() {
// 創建場景,相機和渲染器
this.createScene();
// 添加光源
this.createLights();
this.start()
}
createScene() { ... }
createLights() { ... }
createPlane() { ... }
createSea() { ... }
createSky() { ... }
createUI() { ... }
updatePlane() { ... }
touchEvent() { ... }
checkIsFingerOnController(x, y) { ... }
updatePosition() { ... }
normalize(v, vmin, vmax, tmin, tmax) { ... }
start() {
// 添加對象
this.createPlane();
this.createSea();
this.createSky();
this.createUI();
this.mousePos = { x: 0, y: 0 };
// 初始化事件監聽
this.touchEvent();
window.requestAnimationFrame(this.loop.bind(this), canvas);
}
update() {
// 轉動大海和雲
this.sea.mesh.rotation.z += .005;
this.sky.rotation.z += .01;
// 更新每幀的飛機
this.updatePlane();
// 更新每幀的海浪
this.sea.moveWaves();
// 更新每幀的鼠標位置
this.updatePosition();
}
loop() {
this.update()
this.renderer.render(this.scene, this.camera);
window.requestAnimationFrame(this.loop.bind(this), canvas);
}
}
相關鏈接:
Github項目地址
three.js官方文檔
The Making of “The Aviator”: Animating a Basic 3D Scene with Three.js 翻譯