随着微信小游戏的推出,其全面支持以往的H5游戏开发,微信借小游戏的社交方式完全激活小程序。一样的,也算是从新吹起了H5游戏的风口。前端
能够预见的是,借助小游戏的风,前端游戏开发这一分支也会燃起来了。在游戏开发中,最使人难受的也许就是性能优化了吧。本人在整理过往几回游戏开发的经历中,总结了一些常被忽视的优化小措施,与诸君分享。canvas
针对游戏性能优化,首先,咱们要知道咱们优化的目标是什么?每每咱们以为性能优化很难,是由于咱们不肯定优化目标是什么,针对什么进行优化。小程序
在我看来,性能优化的实质,实际上就是尽量的减小等待时间和内存使用。api
有了目标有好办了,接下来,咱们须要知道咱们经过优化哪些目标能够减小代码执行和内存使用。我粗略的分为了3个方面:浏览器
场景:针对须要大量使用且绘图繁复的静态场景性能优化
实现:对象内放置一个私有canvas,初始化时将静态场景绘制完备,须要时直接拷贝内置canvas的图像便可微信
//每一帧重绘时
setInterval(function () {
context.fillRect(x,y,width,height)
context.arc(x,y,r,sA,eA)
context.strokeText('hehe', x, y)
}, 1000/60)
复制代码
设置离屏canvas网络
let background = {
width: 400,
height: 400,
canvas: document.createElement('canvas'),
init: () => {
let self = this
let ctx = self.canvas.getContext('2d')
self.canvas.width = self.width
self.canvas.height = self.height
ctx.fillRect(x,y,width,height)
ctx.arc(x,y,r,sA,eA)
ctx.strokeText('hehe', x, y)
}
}
background.init()
setInterval(() => {
context.drawImage(background.canvas, background.width, background.height, 0, 0);
}, 1000/60)
复制代码
不设置离屏canvas的状况下,每帧绘制会调用3次绘图api;设置离屏canvas后,每帧只用调用一次api。多线程
实质:减小调用api的次数,减小代码执行语句,从而减小每帧渲染时间,从而提升动画流程度。异步
场景:针对须要频繁修改canvas对象的渲染状态 (fillStyle, strokeStyle ...)
实现:按canvas状态分别绘制,而不是按对象进行绘制
混合绘制
for (let i = 0; i < line.length; i++) {
let e = line[i]
context.fillStyle = i % 2 ? '#000': '#fff'
context.fillRect(e.x, e.y, e.width, e.height)
}
复制代码
不一样状态分别绘制:
context.fillStyle = '#000'
for (let i = 0; i < line.length / 2 - 1; i++) {
let e = line[i * 2 + 1]
context.fillRect(e.x, e.y, e.width, e.height)
}
context.fillStyle = '#fff'
for (let i = 0; i < line.length / 2 - 1; i++) {
let e = line[i * 2]
context.fillRect(e.x, e.y, e.width, e.height)
}
复制代码
先后比较看,虽然循环次数没变,但循环内调用的语句变少了,即不在循环内修改canvas状态了。
实质:减小canvas api的调用,不用在每次根据对象属性去修改canvas的状态,而是将具备相同状态的对象提出,批量渲染。
场景:针对场景中大背景变化缓慢,而角色的状态变换频繁
实现:将场景按状态变换快慢进行层次划分,设置不一样的透明度和z-index进行层级叠加。
实质:经过分层,对连续帧中的相同场景不重复渲染,减小渲染所需的canvas api的调用。
但在微信小游戏中,本方法不能使用,由于微信小游戏中有全局惟一canvas,其余canvas都是离屏canvas,不能显示。
这个不存在什么场景,就是一把梭,无脑直接上RAF,别再setInterval了。
简单点说,RAF是浏览器根据页面渲染的状况,自行选择下一帧绘制的时机。
可是有一个tip须要注意,RAF无论理回调函数,即在RAF回调被执行前,若是RAF屡次调用,其回调函数也会屡次调用。因此须要作好防抖节流。否则会致使RAF的回调函数在同一帧中重复调用,形成没必要要的计算和渲染的消耗。
const animation = timestamp => console.log('animation called at', timestamp)
window.requestAnimationFrame(animation)
window.requestAnimationFrame(animation)
复制代码
场景:针对游戏中须要频繁更新和删除 的角色
实现:对象池维护一个装着空闲对象的池子,若是须要对象的时候,不是直接new,而是从对象池中取出,若是对象池中没有空闲对象,则新建一个空闲对象。
const __ = {
poolDic: Symbol('poolDic')
}
/**
* 简易的对象池实现
* 用于对象的存贮和重复使用
* 能够有效减小对象建立开销和避免频繁的垃圾回收
* 提升游戏性能
*/
export default class Pool {
constructor() {
this[__.poolDic] = {}
}
/**
* 根据对象标识符
* 获取对应的对象池
*/
getPoolBySign(name) {
return this[__.poolDic][name] || ( this[__.poolDic][name] = [] )
}
/**
* 根据传入的对象标识符,查询对象池
* 对象池为空建立新的类,不然从对象池中取
*/
getItemByClass(name, className) {
let pool = this.getPoolBySign(name)
let result = ( pool.length
? pool.shift()
: new Object() )
return result
}
/**
* 将对象回收到对象池
* 方便后续继续使用
*/
recover(name, instance) {
this.getPoolBySign(name).push(instance)
}
}
复制代码
实质:减小内存的使用。每次建立一个对象,都须要分配一点内存,而因为浏览器的回收机制,致使会有大量无用的对象的累加,白白消耗大量的内存。
场景:针对须要进行大量计算任务
实现:使用worker单独开启线程进行并行计算,主线程仍执行本身的任务。
实质就是并行计算,避免进程堵塞。任务计算须要的时间是不会减小的,形象点来讲就是从一条腿走路变成两条腿走路
//main.js
//建立worker线程
let worker = new Worker('worker.js')
//监听worker线程的返回事件
worker.onmessage = (e) => {
//e worker线程的返回对象
}
//发送消息
worker.postMessage(obj)
//worker.js
//监听主线程的执行请求
onmessage = (e) => {
//执行对象e
postMessage(result)
}
复制代码
实质:并行计算,能够认为计算任务与主线程工做是异步的,互不干扰。由于是将计算任务所有交给worker,全部计算时间是不会减小的。
对象池不只能够针对对象,还能够针对worker进行线程池的管理,有兴趣的朋友能够试试。
其实除了上述3个方面,还有一个很是重要的优化目标,那就是网络优化,但这也是咱们常说的浏览器性能优化的终点内容,因此关于网络优化,各位就请移步其余大神的文章,我也就再也不卖弄我那一点三脚猫技术了。各位朋友有什么其余的优化措施的,欢迎交流。