3D微信小遊戲 輪盤控制模塊 three.js


在這裏插入圖片描述

一、場景構造

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。

靜止的場景

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);
  }
}

在這裏插入圖片描述

動畫渲染

main.js

修改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部分

main.js

輪盤控制,分數信息,技能按鈕這些部分和遊戲主體分開,都創建在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);

  // 創建指向標
}

在這裏插入圖片描述

輪盤控制

main.js

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 翻譯

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章