第一次开发小游戏, 用的是 Hilo 框架. 由于项目开发时间比较紧张, 对游戏和 CANVAS 都没有了解过. 代码虽然写的很烂, 但是还是记录下踩过的坑吧! 本文为碎碎念模式, 并不深入, 写错的地方希望多多指点.
一, CANVAS 横屏适配处理
游戏是微信内的一款横屏游戏. 如果强制横屏, 提示用户去控制横竖屏开关并不友好.
解决方案, 游戏场景做成如下图紫色部分结构, 游戏宽高和手机屏幕调换. 如果手机为竖屏, 那么将游戏旋转 90° 即可.
注: 所述 [横屏] 为用户打开了允许横屏的开关并横屏, 真正的横屏.
代码如下所示:
- let width = document.documentElement.clientWidth;
- let height = document.documentElement.clientHeight;
- let box = document.getElementsByTagName('canvas');
- let style = '';
- // 竖屏
- if (width <height) {
- style += `width:${height}px;`;
- style += `height:${width}px;`;
- style += '-webkit-transform: rotate(90deg); transform: rotate(90deg);';
- // 注意旋转中点的处理
- style += `-webkit-transform-origin: ${width / 2}px ${width / 2}px;`;
- style += `transform-origin: ${width / 2}px ${width / 2}px;`;
- }
- if (box.length) {
- box[0].style.CSSText = style;
- }
当用户开启了横屏开关, 如果用户横屏, 那就将游戏场景旋转 0° 即可, 也就是恢复最初的样子. 如下:
- // 横屏
- if (width> height) {
- style += `width:${width}px;`; // 注意旋转后的宽高切换
- style += `height:${height}px;`;
- style += '-webkit-transform: rotate(0); transform: rotate(0);';
- style += '-webkit-transform-origin: 0 0;';
- style += 'transform-origin: 0 0;';
- }
- if (box.length) {
- box[0].style.cssText = style;
- }
横屏没想象那么顺利, 我们的游戏是在微信场景. 当用户开启横屏开关并横屏后, 微信内置浏览器头也会占一大部分区域. 这样我们的游戏场景旋转后明显是显示不全的.
解决方案就是利用 Hilo 的 api resize 下舞台
- // 解决微信横屏浏览器头部 导致高度变化的问题
- this.stage.resize(height, width, true);
最后有几个注意点:
1, 注意旋转过程中的宽高切换
2, 注意单位适配问题
3, 注意微信浏览器头, 就因为这个头的变化. 整个游戏都需要处理, 所以还是尽量不要自己处理...
参考文章
二, 点击事件失效
如下图所示, 游戏结束场景的 2 个按钮.
左侧旋转 90° 后变成右侧横屏, 但是竖屏下的横屏 (也就是旋转 90° 得来的)[再来一次] 按钮点击事件会失效, 但是点击红色区域 (没有按钮, 大致绘制并不精准) 部分这个时间会被触发.
而用户横屏 (开启了横屏开关, 自然横屏)[再来一次] 按钮点击事件不会失效.
绘制时坐标以游戏场景左上角为(0,0), 而旋转 90° 后坐标以游戏场景左下角为(0,0). 因为旋转 90° 后游戏场景左下角变成了视觉上的左上角. 因此竖屏下的横屏点击红色区域生效就是因为, Hilo 的点击事件是绑定在元素绘制时坐标区域上(猜测, 没有看源码). 旋转后, 按钮的点击事件生效区间就变成了根据绘制的 x,y 也就是红色区域.
那么如何解决这个问题, 如上图所示, 旋转后的 x,y 如图中蓝色字所示. 可以算出
- // 再玩一次按钮
- const start = this.gameOverScene.getChildById('start');
- // 再玩一次按钮 新的 x = 游戏画布宽度 - 绘制的 y - 按钮的高度
- const startNewX = this.width - start.y - start.height ;
- // 再玩一次按钮 新的 y = 绘制的 x
- const startNewY = start.x;
- // 监听舞台点击事件
- this.stage.on(Hilo.event.POINTER_START, (e) => {
- // 利用新的 x,y 和按钮自身的高度和宽度 判断是否点击在按钮区域
- if ((e.stageX> startNewX && e.stageX <startNewX + start.height) &&
- (e.stageY> startNewY && e.stageY <startNewY + start.width)) {
- // 在玩一次逻辑处理
- }
- )};
[再玩一次]按钮点击事件解决了, 但是事情没有那么简单.
给另一个 [分享] 按钮加上事件, what? 无论横屏还是竖屏点击事件都不生效. 至少 [再玩一次] 按钮事件还是生效的, 只是不准罢了.
原因, 观察上述图.[分享]按钮在初始化的过程中是在游戏画布右侧, 也就是手机屏幕外部. 经过测试发现, 发现绘制时在手机屏幕外的区域点击事件都不会生效. 解决方法如[再玩一次], 无论横屏还是竖屏都计算坐标判断.
三, 音乐播放兼容
Hilo 的 htmlAudio 声音播放模块, 官方文档表示 [使用限制: iOS 平台需用户事件触发才能播放, 很多 Android 浏览器仅能同时播放一个音频.] 但是目前使用来看, 浏览器测试 OK, 绝大部分手机都不能正常播放. 解决方案, 采用 DOM 的 audio, 但是同样 iOS 平台需用户事件触发才能播放. 因此最终的解决方案就是进入游戏之前或者某个合适的环节获取所有的音乐, 先播放再暂停. 用户不会感知, 可以完美解决. 如下:
- // html
- <audio id="audio" src="xxx.mp3" preload="auto"></audio>
- // dom 为获取
- const dom = document.getElementById('audio');
- dom.play();
- dom.pause();
四, 部分机型游戏场景显示不全
游戏中可能有某些元素是经常复用的, 因此会单独切出来. 如下图左侧
如上图右侧所示效果, 最开始的实现方式如下. 在初始化的时候就将公用元素 Y 轴截断展示, 这个效果看似 OK, 但是在测试阶段发现某些 iPhone 手机不能显示这 2 个元素.
- new Hilo.Bitmap({
- // 绘制的图片
- image: 'imgurl',
- // 测试坐标, 非精准坐标
- rect: [0, 100, 50, 300],
- y: 0,
- });
- new Hilo.Bitmap({
- // 绘制的图片
- image: 'imgurl',
- // 测试坐标, 非精准坐标
- rect:[0, 150, 50, 300],
- y: 0,
- });
查看 Hilo 源码绘制图片是用 CanvasRenderingContext2D.drawImage 方法.
CanvasRenderingContext2D.drawImage() 是浏览器原生提供的在 canvas 上绘制图片的方法.
其有以下三种参数形式(详细用法说明及演示可见 MDN):
- ctx.drawImage(image, dx, dy);
- ctx.drawImage(image, dx, dy, dWidth, dHeight);
- ctx.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);
参数 | 意义 |
---|---|
sx, sy | 源图像的选择区域的偏移量 |
sWidth, sHeight | 源图像的选择区域的宽高 |
dx, dy | 目标 canvas 的选择区域的偏移量 |
dWidth, dHeight | 目标 canvas 的选择区域的宽高 |
注:
在 Chrome 和 Firefox 下, 最终的选择区域超出源图像的部分不会被绘制.
在 IE 和 Edge 下, 最终的选择区域超出源图像的部分会用图像的边界像素来填充.
Safari 7.1 额外要求 sx + sWidth 和 sy + sHeight 不超过源图像的宽高, 否则 drawImage() 函数不会绘制任何图像.(未在更高版本的 Safari 上测试)
在支持 canvas 的老版本的 Firefox 上, 有着和 IE 等浏览器类似的对 sx, sy, sWidth, sHeight 的限制, 在新版本中, 这些限制已经被移除.
而我们的元素在部分机型上不能显示就是因为触发了第 3 点坑. 修复代码如下, 通过完整的绘制图片, 然后通过元素的坐标来达到目标样式.
- new Hilo.Bitmap({
- // 绘制的图片
- image: 'imgurl',
- // 测试坐标, 非精准坐标
- rect: [0, 0, 50, 300],
- y: -100,
- });
- new Hilo.Bitmap({
- // 绘制的图片
- image: 'imgurl',
- // 测试坐标, 非精准坐标
- rect:[0, 0, 50, 300],
- y: -150,
- });
参考文章
五, 碰撞检测, 撞击坐标不准确
用 Hilo 最开始开心的一点也是碰撞检测不需要自己写, hitTestObject 检测 object 参数指定的对象是否与其相交. 因此撞击区域可以书写撞击坐标.
- // 给如下图中的一个多边形实行撞击坐标
- new Hilo.Bitmap({
- // 绘制的图片
- image: 'imgurl',
- // 测试坐标, 非精准坐标
- rect: [0, 0, 50, 300],
- y: -100,
- boundsArea: [
- // 测试坐标, 非精准坐标, 图中红点的坐标, 从左到右
- {x: 0, y: 0},
- {x: 0, y: 100},
- {x: 100, y: 100},
- {x: 100, y: 200},
- {x: 200, y: 200},
- {x: 200, y: 100},
- {x: 300, y: 100},
- {x: 300, y: 0},
- ]
- });
理想中应该是如下黄色区域构成的碰撞检测区域.
而实际却是如下黄色区域, 空气墙??? 用户反馈为什么没撞到就死翘翘了.(看了 Hilo 碰撞检测这部分的实现源码, 没太看懂多边形的处理..)
解决办法, 类似雪碧图使用, 恩... 主要是懒得切图
- new Hilo.Bitmap({
- // 绘制的图片
- image: 'imgurl',
- // 测试坐标, 非精准坐标
- rect: [0, 0, 100, 100],
- y: -100,
- boundsArea: [
- // 测试坐标, 非精准坐标, 图中红点的坐标, 从左到右
- {x: 0, y: 0},
- {x: 0, y: 100},
- {x: 100, y: 100},
- {x: 100, y: 0},
- ]
- });
- new Hilo.Bitmap({
- // 绘制的图片
- image: 'imgurl',
- // 测试坐标, 非精准坐标
- rect: [100, 0, 100, 200],
- y: -100,
- boundsArea: [
- // 测试坐标, 非精准坐标, 图中红点的坐标, 从左到右
- {x: 0, y: 0},
- {x: 0, y: 200},
- {x: 100, y: 200},
- {x: 100, y: 0},
- ]
- });
- new Hilo.Bitmap({
- // 绘制的图片
- image: 'imgurl',
- // 测试坐标, 非精准坐标
- rect: [200, 0, 100, 100],
- y: -100,
- boundsArea: [
- // 测试坐标, 非精准坐标, 图中红点的坐标, 从左到右
- {x: 0, y: 0},
- {x: 0, y: 100},
- {x: 100, y: 100},
- {x: 100, y: 0},
- ]
- });
其实就是将 1 张图裁剪成了 3 张图, 裁剪出来的区域撞击坐标都中规中矩. 过于不规则的图形只能尽量写的粗糙一些.
这么轻松就解决了吗??? 当然 NO!!! 回顾下第四点, 部分机型游戏场景显示不全. 上面裁剪的 3 张图里, 最后一张图又触发了 safari 的 bug.o(╥﹏╥)o, 所以还是乖乖切图, 或者雪碧图留一点安全区域吧!
而且后来我才知道雪碧图对于 CANVAS 来说更耗性能, 还不如多切点图呢~
来源: https://juejin.im/post/5ba0dea5f265da0aa3591f8d