一、场景构造
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 翻译