常见问题

以下是开发者在使用 Tiny.js 做游戏开发过程遇到的一些常见问题,如何解决及规避的总结。 首先,请确保使用最新版本的 Tiny.js。

一、渲染内核

1-1 关于渲染模式选择

Tiny.js 支持 WebGL 和 Canvas 两种模式来渲染。但 WebGL 渲染会导致以下问题:

  • Android 4.x、5.x 设备有一定概率渲染异常,Android 6.0.x 系统会有一定概率出现闪屏及黑屏,部分华为低端设备(黑名单统计中)闪退数量增加,因此可以:
    • Android 6.1 系统版本以下的设备使用 Canvas 渲染模式
    • Android 其他系统版本及设备使用自动识别渲染模式(即不传 renderType 或传入 Tiny.RENDERER_TYPE.UNKNOWN

1-2 iOS 设备请使用 WKWebview

iOS 设备使用 UIWebview 进行渲染时,Canvas 区域会有小概率出现蓝屏问题,需要在可以使用 WKWebview 的设备上尽量开启这一特性。

二、绘制性能

2-1. Canvas 渲染模式下避免使用 Tiny.Graphics

Tiny.Graphics 底层使用的是 Canvas API 来绘制图形,每一帧进行贴图都会进行计算并绘制。如非必要,请尽量使用图片材质

2-2. 帧绘制时无需清理 canvas

如果绘制区域的背景非透明,则可以设置 renderOptions.transparent: false 这一启动参数,这样在每一帧绘制时渲染引擎无需清理 canvas,可以节省一定的 CPU 开销。具体配置请参考启动参数

2-3. 合理选择帧率来平衡性能与体验

Tiny.js 引擎在 1.1.7 版本增加了帧率设置启动参数。在不明显影响体验的基础上,可以通过降低帧率,即设置 fps 启动参数,来有效减少 CPU 性能开销。具体配置请参考启动参数

2-4. 使用 ParticleContainer 来优化绘制性能

对于一些重复精灵、大量重复运动的场景,可以使用 ParticleContaier 来优化渲染性能。具体可参考:ParticleContainer

2-5. 帧动画可能会导致渲染帧率恢复到 60FPS

在 1.1.7、1.1.8、1.2.0、1.2.1 版本的 Tiny.js 下,如果游戏设置 60 以下的 FPS 帧率,当帧动画(AnimatedSprite 并且 loop: false)播放完成时,游戏主场景帧率会自动恢复到 60 FPS,增加 CPU 性能开销

三、内存优化

3-1. 清理 actions

如果 Tiny 对象重复或循环执行一些特定的 action,那么在执行动画动作前,需要调用 Tiny.Action.cleanup,来清理 Tiny 对象上残留的 Tween 实例引用,避免造成内存泄漏。

// noteLeft 是一个 Tiny.Sprite 实例
const flyLeft = Tiny.MoveTo(3000, Tiny.point(-10, -10));
const fadeOutLeft = Tiny.FadeOut(3000);
Tiny.Action.cleanup(noteLeft);
noteLeft.runAction(flyLeft, fadeOutLeft);

3-2. 清理骨骼显示对象

如果使用了骨骼动画,那么在移除骨骼动画时,请调用骨骼显示对象的 dispose 方法来销毁骨骼。

armatureDisplay.dispose();

3-3. 开发粒子特效注意内存回收

在手动开发粒子特性动画时,注意循环利用或者在不用时销毁动画对象,来避免造成内存无限增长。

particle () {
  // 每个例子都会执行该方法来绘制运动轨迹
  const snowflake = new Tiny.Sprite(Tiny.TextureCache['snow']);
  snowflake.setScale(random(0.8, 1.2));
  snowflake.setOpacity(random(0.8, 1));
  const fallAction = Tiny.MoveTo(1e3, Tiny.point(0, 100));

  fallAction.onComplete = () => {
    container.removeChild(snowflake); // 运动结束后移除 snowflake 对象
  };

  container.addChild(snowflake);
  snowflake.runAction(fallAction);
}

3-4. 减少加载 JSON 文件的 xhr 请求

可以通过将材质数据以 JSON 对象的形式内联进 js,而不是构建成单个 JSON 文件。这样可以直接使用 Tiny.loaders.Loader 加载 JSON 对象(需传入 metadata.JSONObject),来有效减少加载 JSON 文件产生的 xhr 请求。

四、图片拼叠

4-1. 需注意 TilingSprite 平铺图片的像素

使用 TilingSprite 绘制纯色或者渐变背景时,如果重复的图片宽度(或者高度)大于 1,在某些特定的像素值上,在部分 iOS 设备上,会导致 webview 卡死。

4-2. 不要使用 TilingSprite 平铺雪碧图的图片

使用 TilingSprite 平铺图片时,请确保使用的是单张图片,不要将图片合成到雪碧图中。否则会造成平铺的颜色产出半透明的渐变。

4-3. NinePatch 九宫格渲染间隙规避

使用 NinePatch 绘制九宫格时,通过 canvasPadding: 1 来规避 canvas 渲染间隙问题。(tinyjs-plugin-ui 默认已经规避了这一问题,请确保不要手动改动这一参数)。

4-4. Mesh 网格的渲染间隙规避

使用 canvas 渲染模式,需要设置网格(tinyjs-plugin-meshtinyjs-plugin-drangonbones)的 canvasDrawTimes 为一个合理值(1到10),来来规避 canvas 渲染间隙

五、文字相关

5-1. 避免重复创建 Tiny.Text 实例

每次使用 Tiny.Text 创建文字实例时,Tiny.js 引擎都会创建一个离屏 canvas 的 DOM 节点,来进行一些文字测量的相关计算。因此要注意不要循环重复创建实例,要尽量重复利用已创建 Tiny.Text 实例。否则会引发内存泄漏。

5-2. canvas 无法支持特定字号的 emoji 字符

Tiny.Text 实例中的文字如果含有 emoji 表情,特定 fontSize 下,可能会无法展示 emoji 字符,这是由于 canvas 的底层绘制引起,无法解决,但可以通过尝试调整到特定字号(一般是减少字号)来规避。

5-3. 正确截断含有 emoji 字符的文字

如果在 emoji 字符的中间位置进行截断,可能会导致文字中的截断位置以前的其他字符被截掉,影响展现。所以需要在 emoji 字符开始或者结束的位置进行截断。可以通过 encodeURIComponent 方法来判断是否在正确的位置进行了截断:

try {
  encodeURIComponent(text); // 截断不完整的 emoji 会报错
} catch (e) {}

六、其他

6-1. 避免使用客户端时间

涉及时间获取的逻辑请尽量使用服务端时间。避免使用 new Date()Date.now() 来获取时间。也可以 Tiny.getTime() 方法来获取时间,但在 iOS 8.X 系统上底层还是依赖于 Date 对象实现,需要特殊处理。