大家好!我是程序员小菜。最近突然想尝试做微信小游戏。考虑到自己只是一个普通程序员,要学游戏开发代价实在是太大了,而且游戏开发工具中创建复杂的游戏场景咱也用不上,小游戏开发本身就是靠创意和玩法简单。作为一名程序员,小游戏应该尽量能依赖代码实现的最好,这样遇到技术难题也好自己解决。因为自己工作也做前端,最终考虑使用pixi.js来做图像渲染代码实现游戏逻辑。
但是微信小游戏是一个不同于浏览器的 JavaScript 运行环境,没有 BOM 和 DOM API。然而pixi.js是用JavaScript 结合其他 HTML5 技术来显示媒体,创建动画或管理交互式图像。是依赖浏览器提供的 BOM 和 DOM API 的。所以不能直接微信小游戏中使用pixi.js。好在小游戏提供了对大部分 Canvas 2d 和 WebGL 1.0 特性的支持,支持情况参见 RenderingContext,pixi.js它能自动侦测使用WebGL还是Canvas来创建图形
小游戏的运行环境在 iOS 上是 JavaScriptCore,在 Android 上是 V8,都是没有 BOM 和 DOM 的运行环境,没有全局的 document 和 window 对象。因此当你希望使用 DOM API 来创建 Canvas 和 Image 等元素的时候,会引发错误。所以要想使用pixi.js我们就需要在微信小游戏宿主环境模拟实现浏览器环境,对基于浏览器环境的游戏引擎在小游戏运行环境下的一层适配层Adapte,使游戏引擎在调用 DOM API 和访问 DOM 属性时不会产生错误。Adapter 是一个抽象的代码层,并不特指某一个适配小游戏的第三方库,每位开发者都可以根据自己的项目需要实现相应的 Adapter。好在官方实现了一个 Adapter 名为 weapp-adapter, 并提供了完整的源码,供开发者使用和参考。
**Adapter 下载地址 [weapp-adapter.zip](https://res.wx.qq.com/wxdoc/dist/assets/media/weapp-adapter.9568fddf.zip)**但是官方提供的weapp-adapter 并不能完全兼容pixi.js。在实际使用过程中我遇到了很多问题,有些问题通过百度和Google的没有找到很好就解决办法,通过自己的几个晚上的努力终于解决了。所以我整理最新pixie.js7.1.4开发微信小游戏遇到的问题和解决方案方便大家。
## 搭建pixi.js微信小游戏过程中我主要遇到三个问题: ###pixi/unsafe-eval
###纹理加载问题
###TouchEvent
我先从创建微信小游戏开始,使用微信开发者工具创建小游戏不要创建小程序。开始我考虑我是使用pixi.js代码实现小游戏用不到微信小游戏提供的游戏引擎和模板代码,所以就直接创建了小程序。但是创建完小程序之后就发现对canvas支持有问题就直接果断放弃了改创建小游戏。在创建微信小游戏时没有找到创建空白项目就直接选用了飞机大战。然后删掉多余文件,不删也可以我们测试就直接修改game.js
引入小游戏如适配文件weapp-adapter.js和symbol.js。 weapp-adapter.js直接使用上面Adapter.zip包下dist目录打包好的文件。
引入[pixi.js](https://github.com/pixijs/pixijs/releases),我们选择稳定版的最新版v7.1.4,不要选新版本的v7.2.0。因为微信小游戏规定单个js文件不能超过500k,所以我们只能选用压缩过的开发者版本。但是之后讲解问题的原因时会使用测试版本讲解方便大家理解。
pixi.js每个版本的都不太一样,所以看别人示例或者官方示例一定要注意看版本。如果出现ReferenceError: xxx is not defined 时候不要惊慌,可能是这个版本本来就没有这个属性或方法。可以断点看一下它有那些属性和方法。
目录结构和game.js代码如下图:
我们遇到了第一个问题pixi/unsafe-eval报错:
Error: Current environment does not allow unsafe-eval, please use @pixi/unsafe-eval module to enable support.
直接在开发者版js搜索报错:
systemCheck() {
if (!unsafeEvalSupported()) {
throw new Error("Current environment does not allow unsafe-eval, please use @pixi/unsafe-eval module to enable support.");
}
}
function unsafeEvalSupported() {
if (typeof unsafeEval === "boolean") {
return unsafeEval;
}
try {
const func = new Function("param1", "param2", "param3", "return param1[param2] === param3;");
unsafeEval = func({ a: "b" }, "a", "b") === true;
} catch (e) {
unsafeEval = false;
}
return unsafeEval;
}
再找到unsafeEvalSupported方法,我们发现了new Function("param1", "param2", "param3", "return param1[param2] === param3;")。这种将字符串编译成代码方式存在安全问题导致了这个报错,pixi官方也提供解决方法[@pixi/unsafe-eval](https://api.pixijs.io/@pixi/unsafe-eval.html)。我们选择第二种。下载unsafe-eval.min.js
``` https://cdn.jsdelivr.net/npm/@pixi/unsafe-eval@7.x/dist/unsafe-eval.min.js ```
``` https://unpkg.com/@pixi/unsafe-eval@7.x/dist/unsafe-eval.min.js ```
因为this对象是undefined,所以休要把的unsafe-eval.min.js里的三个this.PIXI换成GameGlobal.PIXI。
解决了第一个问题我们就开心的开始撸代码了,直接上game.js代码。
require('./js/libs/weapp-adapter')
import './js/libs/symbol'
import * as PIXI from "./js/libs/pixi/pixi.min";
GameGlobal.PIXI = PIXI
require("./js/libs/pixi/unsafe-eval.min")
// 获取适配屏幕大小
const {pixelRatio, windowWidth, windowHeight} = wx.getSystemInfoSync();
let app = new PIXI.Application({
width: windowWidth * pixelRatio,
height: windowHeight * pixelRatio,
background: '#1099bb',
view: canvas
});
const graphics = new PIXI.Graphics();
graphics.beginFill(0xDE3249);
graphics.drawRect(50, 50, 100, 100);
graphics.endFill();
app.stage.addChild(graphics)
代码在执行到new PIXI.Graphics()报错了:Error: Unrecognized source type to auto-detect Resource,直接找到对应代码。
function autoDetectResource(source, options) {
// ...
for (let i = INSTALLED.length - 1; i >= 0; --i) {
const ResourcePlugin = INSTALLED[i];
// ResourcePlugin.test判断是不是支持的类型
if (ResourcePlugin.test && ResourcePlugin.test(source, extension)) {
return new ResourcePlugin(source, options);
}
}
throw new Error("Unrecognized source type to auto-detect Resource");
}
// 支持的所以类型
INSTALLED.push(ImageBitmapResource, ImageResource, CanvasResource, VideoResource, SVGResource, BufferResource, CubeResource, ArrayResource);
const _BaseTexture = class extends eventemitter3 {
// 忽略其他代码代码
if (resource && !(resource instanceof Resource)) {
resource = autoDetectResource(resource, resourceOptions);
resource.internal = true;
}
}
现在我们来结合代码分析报错的原因,pixie.js画图对象Graphics会继承纹理基类_BaseTexture,_BaseTexture会调用autoDetectResource来确定类型。从INSTALLED中可以看出一共支持8种,分别是ImageBitmapResource, ImageResource, CanvasResource, VideoResource, SVGResource, BufferResource, CubeResource, ArrayResource。autoDetectResource方法通过for循环依次去调用对应类型的ResourcePlugin.test来适配。所有类型的都没有匹配到所以抛出了throw new Error("Unrecognized source type to auto-detect Resource")错误。
同样的代码我们放在浏览器环境执行发现,适配到的类型是CanvasResource。代码如下:
class CanvasResource extends BaseImageResource {
static test(source) {
// ...
return globalThis.HTMLCanvasElement && source instanceof HTMLCanvasElement;
}
}
判断是否是HTMLCanvasElement类型source instanceof HTMLCanvasElement;。浏览器环境是true。小程序环境则不对,那我们一起来看一下小程序下source是什么HTMLElement类型。
我们继续看一下pixi.js的Texture的官方源码。
static get WHITE() {
if (!Texture._WHITE) {
const canvas = settings.ADAPTER.createCanvas(16, 16);
const context = canvas.getContext("2d");
canvas.width = 16;
canvas.height = 16;
context.fillStyle = "white";
context.fillRect(0, 0, 16, 16);
Texture._WHITE = new Texture(BaseTexture.from(canvas));
removeAllHandlers(Texture._WHITE);
removeAllHandlers(Texture._WHITE.baseTexture);
}
return Texture._WHITE;
}
这段代码第一个重要的地方就是const canvas = settings.ADAPTER.createCanvas(16, 16), 这里生成cancas就是前面用来做类型推断的(source)。再来一起看一下settings.ADAPTER.createCanvas方法。控制台中打印的如下,确实就是调用document.createElement("canvas")创建了一个canvas实例并且这个canvas的constructor也是HTMLCanvasElement。
现在问题已经很明确了,我们看一下微信的adapter中document.createElement方法在创建canvas和canvas的实现。
// 微信adapter中document.createElement方法的实现
createElement(tagName) {
if (tagName === 'canvas') {
return new Canvas()
} else if (tagName === 'audio') {
return new Audio()
} else if (tagName === 'img') {
return new Image()
}
return new HTMLElement(tagName)
}
// 微信adapter中Canvas的实现
export default function Canvas() {
const canvas = wx.createCanvas()
canvas.type = 'canvas'
canvas.__proto__.__proto__ = new HTMLElement('canvas')
const _getContext = canvas.getContext
canvas.getBoundingClientRect = () => {
const ret = {
top: 0,
left: 0,
width: window.innerWidth,
height: window.innerHeight
}
return ret
}
return canvas
}
微信weapp-adapter原代码中canvas.__proto__.__proto__ = new HTMLElement('canvas')这句代码可以看出来继承错了,canvas原型的原型应该继承HTMLCanvasElement。把代码修改为canvas.__proto__.__proto__ = new HTMLCanvasElement('canvas')。再重新打包weapp-adapter,把新生成weapp-adapter.js替换微信小游戏项目中的文件就可以了。
终于可以正常的显示图像了,我们点击图像的时候突然又报了一个错。TouchEvent is not defined.这个问题相对简单,TouchEvent变量没有赋值给全局对象。修改一下weapp-adapter的TouchEvent.js ,再要把window.TouchEvent暴露出来即可。
// TouchEvent.js
export class TouchEvent {}
// 修改改为
export default class TouchEvent {}
// 再window.js文件加上这句
export TouchEvent from './EventIniter/TouchEvent'
这样就解决了pixi.js在微信小游戏环境下的运行问题。我来一个拖动小飞机的例子结束本教程。下一篇文章我会展示一些复杂的例子.
require('./js/libs/weapp-adapter')
import './js/libs/symbol'
import * as PIXI from "./js/libs/pixi/pixi.min";
GameGlobal.PIXI = PIXI
require("./js/libs/pixi/unsafe-eval.min")
// 获取适配屏幕大小
const {pixelRatio, windowWidth, windowHeight} = wx.getSystemInfoSync();
let app = new PIXI.Application({
width: windowWidth * pixelRatio,
height: windowHeight * pixelRatio,
background: '#1099bb',
view: canvas
});
const texture = PIXI.Texture.from('./images/hero.png')
texture.baseTexture.scaleMode = PIXI.SCALE_MODES.NEAREST
const bunny = new PIXI.Sprite(texture)
bunny.anchor.set(0.5)
bunny.x = app.screen.width / 2
bunny.y = app.screen.height / 2
bunny.on('pointerdown', onDragStart, bunny)
bunny.interactive = true;
let dragTarget = null;
app.stage.interactive = true
app.stage.hitArea = app.screen
app.stage.on('pointerup', onDragEnd)
app.stage.on('pointerupoutside', onDragEnd)
app.stage.addChild(bunny)
function onDragMove(event) {
if (dragTarget) {
dragTarget.parent.toLocal(event.global, null, dragTarget.position)
}
}
function onDragStart() {
this.alpha = 0.5
dragTarget = this
app.stage.on('pointermove', onDragMove)
}
function onDragEnd () {
if(dragTarget) {
app.stage.off('pointermove', onDragMove)
dragTarget.alpha = 1
dragTarget = null
}
}