微信小游戏 - Canvas/WebGL Demo 移植

这是个人关于微信小游戏系列文章的第一篇,在这系列文章里会描述 ——

  • 如何把一些 Canvas/WebGL Demo 移植到小游戏环境并支持双端运行;
  • 对小游戏在 Android 平台的运行时架构进行分析;
  • 通过对移植的 Canvas/WebGL Demo 在小游戏和 Chrome for Android 浏览器上做 Benchmarking,对 H5 游戏 vs 小游戏的渲染性能进行对比和分析;

Canvas/WebGL Demo

为了做性能对比,一共移植了四个 Demo,这些 Demo 常用于浏览器自身的 Canvas/WebGL 性能测试,包括 GUIMark3 和 WebGL Aquarium。

移植的代码已经上传到 GitHub,读者可以自行下载运行测试。

上图从左至右分别是:

  1. Canvas Bitmap,修改自 GUIMark3 Bitmap,类似雷电的小游戏,多个小位图的重复绘制,主要测试 Canvas.drawImage 的性能,跟微信开发工具自带的样例游戏类似;
  2. Canvas Compute,修改自 GUIMark3 Compute,模拟鸟群的运动,包含大量的物理运动计算,实际上是测试 JavaScript 的计算性能;
  3. WebGL Compute,Canvas Compute 的 WebGL 版本,用 WebGL 绘制点取代 Canvas 绘制短线段;
  4. WebGL Aqua,修改自 WebGL Aquarium,绘制的场景有一定的复杂度,包含了约 30 个模型;

代码移植过程

总的来说移植并不算太困难,WebGL Aquarium 本身包含了大量的 DOM 元素和利用 DOM 来加载脚本和图片,所以花的时间比较长,其它页面只有很少 DOM 元素和 DOM 操作的,移植起来还是很简单,当然这里只包括主体内容的渲染部分,其它如 HUD,输入事件处理,音频播放等并没有包括在内,如果原来的页面是使用 DOM 做 HUD 的,可能会比较麻烦。另外,如果先开发小游戏再移植到浏览器上运行,理论上应该会更简单。

移植过程中自己写了一些方便双端运行的适配代码(wxhelper.js),示例如下。

检查是否是小游戏运行环境

let WX_GAME_ENV = typeof wx !== 'undefined';
let WX_GAME_DEVTOOLS = false;
let SystemInfo = null;

if (WX_GAME_ENV) {
  SystemInfo = wx.getSystemInfoSync();
  if (SystemInfo.platform == "devtools")
    WX_GAME_DEVTOOLS = true;
}

Performance.now()

function Now() {
  if (WX_GAME_ENV) {
    if (WX_GAME_DEVTOOLS)
      return wx.getPerformance().now();
    else
      return wx.getPerformance().now() / 1000;
  } else {
    return performance.now();
  }
}

注意规范里面的 performance.now() 是返回微秒精度,但是以毫秒为单位的浮点数值,而小游戏的 API 定义是返回微秒为单位,并且实机环境跟 DevTools 环境还不一样,DevTools 环境估计就是调用浏览器本身的 API,返回的是毫秒为单位的值,这算是踩到的第一个坑,上面的适配代码统一返回毫秒单位。

创建 Image 对象

function CreateImage() {
  if (WX_GAME_ENV) {
    return wx.createImage();
  } else {
    return new Image();
  }
}

获取主 Canvas 的引用

let MainCanvas = null;

function GetMainCanvas(domId) {
  function GetMainCanvasImpl(domId) {
    if (WX_GAME_ENV) {
      if (window != null && window.canvas != null)
        return window.canvas;
      else
        return wx.createCanvas();
    } else {
      return document.getElementById(domId);
    }
  }

  if (MainCanvas != null)
    return MainCanvas;

  MainCanvas = GetMainCanvasImpl(domId);
  return MainCanvas;
}

在小游戏环境中,只允许有一个占据全屏幕的主 Canvas,如果使用微信提供的 Adapter,这个 Canvas 会事先创建并通过 window.canvas 引用。

全局对象

在浏览器环境里面,window 是全局对象,而在小游戏环境里面 GameGlobal 才是全局对象,下面的代码演示了如何在小游戏环境里面模拟 window 全局对象,我们可以在 GameGlobal 下创建一个 window 属性并让它引用自身。

if (typeof window !== 'undefined') {
  window.wxhelper = wxhelper;
} else if (typeof GameGlobal !== 'undefined') {
  GameGlobal.wxhelper = wxhelper;
  GameGlobal.window = GameGlobal;
  window.top = GameGlobal.parent = window;
} else {
  console.log("Cannot find any global object!");
}

因为在小游戏环境里面,每个 JavaScript 文件都是一个模块,里面包含的方法和变量不会自动包括在全局对象下面,如果我们需要跟浏览器保存一致的行为,就需要将对外的 API 显式地导出到全局变量。比如:

// Vector3D.js
window.Vector3D = function Vector3D(x, y, z){
    this.x = x || 0;
    this.y = y || 0;
    this.z = z || 0;
}

// Boid.js
Boid.ZERO = new Vector3D(0, 0, 0);

上面的示例代码可以把 Vector3D 在浏览器和小游戏环境都导出到全局对象下面,虽然在浏览器环境下是多余的。当浏览器对 ES6 Module 的支持完善后,理论上我们可以使用统一的模块管理方式来处理浏览器和小游戏环境的模块 API 的导出/导入的问题。

你可能感兴趣的:(小游戏)