[前端工坊] 微信小游戏|萌狗冠军之路,纯干货分享!

文章来自微信公众号:前端工坊(fe_workshop),不按期更新有趣、好玩的前端相关原创技术文章。若是喜欢,请关注公众号:前端工坊
版权归公众号全部,转载请注明出处。
做者:毛科 刘麒麟

**咱们作了一个小游戏!快来看一下!纯干货分享!
阅读本文须要5分钟,只需5分钟,你就会跟我同样,爱上这款游戏~**前端

图片描述

策划上

这款游戏具体的玩法是经过点击屏幕左右区域来控制小狗的前进方向进行跳跃,而阶梯是无穷尽的,若遇到障碍物或者踩空、或者小狗脚下的阶梯陨落,游戏失败;这款游戏提供指尖娱乐,考验你们左右手的配合能力,和迅速反应能力,长时间训练,就能得高分。json

技术上

「使用3D图形引擎three.js随机渲染阶梯」canvas

阶梯由一个个方块随机组成「无障碍物的阶梯」和「有障碍物的阶梯」,无障碍物的阶梯随机组成一条畅通无阻的路径,有障碍物的阶梯随机生成世界杯32强旗子。小程序

方块纹理的绘制:
直接看代码,一个方块由6个面组成
图片描述后端

// 建立纹理
new THREE.MeshBasicMaterial({
    map: assets.getTexture("cubeWallpaper"),
    overdraw: true,
})

// 建立6个面
const materials = [
    new THREE.MeshBasicMaterial({
        map: assets.getTexture("cubeWallpaper"),
        overdraw: true,
    }),
    new THREE.MeshBasicMaterial({
        map: assets.getTexture("cubeWallpaper"),
        overdraw: true,
    }),
    new THREE.MeshBasicMaterial({
        map: assets.getTexture("cube"),
        overdraw: true,
    }),
    new THREE.MeshBasicMaterial({
        map: assets.getTexture("cubeWallpaper"),
        overdraw: true,
    }),
    new THREE.MeshBasicMaterial({
        map: assets.getTexture("cobeWallpaper2"),
        overdraw: true,
    }),
    new THREE.MeshBasicMaterial({
        map: assets.getTexture("cubeWallpaper"),
        overdraw: true,
            }),
]
// 建立盒子
const boxmat = new THREE.MeshFaceMaterial(materials)

「使用canvas绘制2D图形,渲染排行榜」
图片描述微信小程序

排行榜自己的功能实现并不复杂,主要是由于开放数据域的限制,显示麻烦一些。
排行榜主要功能进行拆解后主要涉及如下功能点api

  • 上报用户分数
  • 获取用户好友分数
  • 对好友分数进行排序
  • 获取用户好友头像
  • 绘制排行榜
  • 增长前端的分页功能
  • 获取当前用户本身的信息

微信的开放数据域
在微信小程序当中,当咱们须要获取微信好友的关系链时,会受到一些限制。微信为了保护本身的用户关系链不被恶意获取盗用,同时又想要创建开放的小程序生态,巩固自身地位。在对信息的开放与封闭之间,微信的解决办法是这样的,经过创建一个开放的数据域,全部和微信好友关系相关的API一概被限制,只能在开放数据域当中使用。且获取的数据,禁止以任何方式直接传递给主域,只能将数据绘制在一个离屏的shareCanvas,而主域经过将这个离屏的shareCavans绘制到上屏canvas上,从而达到展现微信好友关系链的数据。promise

在绘制排行榜的过程当中遇到的坑和解决方法:
主域能够经过wx.postMessage向开放数据域发送消息通讯,开放数据域经过监听主域的消息来决定什么时机调用相关API并绘制相关数据。但由于实际的调用API以及绘制页面和加载微信用户头像等等操做都是彻底异步的,而主域并不知道何时开放数据域完成了接口的调用,和图像的绘制。缓存

解决办法:当用户触发了 点击排行榜按钮以后,主域先绘制一个全透明的蒙层阻止用户继续操做交互。而后经过postMessage通知开放数据域子域,开放数据域子域接收到消息后,先判断这是个什么消息。好比是绘制排行榜ShowRankingList仍是游戏结束GameOver,上报用户分数。
若是是绘制排行榜,则先异步加载排行榜的素材资源,而后将黑色背景与排行榜素材先绘制到canvas上,而与此同时主域则每隔0.5秒就获取一次shareCanvas将纹理绘制到主屏上。在开放数据域绘制完素材以后,会当即经过getFriendCloudStorage获取微信好友数据,而后经过getTopDataList对数据进行解析处理,由于拿到的数据是没有排序的,此时还会进行按分数进行排序。代码以下微信

topDataList = Lodash.sortBy(topDataList, (item) => {
    return -item.score
})

获得数据以后,先克隆一份,而后用slice切割为6个元素传递给displayTopDataList,这个是上屏显示用的6个好友数据。而后经过promiseTopDataList,该方法会将这6个元素的头像进行异步的获取。

这里有几个坑,一次并行请求3个以上的微信头像时,网络上很容易出现562错误,此时会致使头像绘制失败,程序上对头像绘制失败进行了容错,若是获取不到用户头像则显示一个空白的头像。但这始终不是个办法,最后的解决办法是按顺序依次加载完头像,而后返回。两种实现代码以下:
异步获取全部用户的头像

const taskList = []
for (const item of topDataList) {
    taskList.push(this.promiseTopData(item))
}
return Promise.all(taskList)

按步列依次获取用户的头像

return new Promise((resolve, reject) => {
    let index = 0
    let taskList = []
    let task = (index) => {
        const item = topDataList[index]
        this.promiseTopData(item).then((itemReady) => {
            taskList.push(itemReady)
            if (index < topDataList.length - 1) {
                index++
                task(index)
            } else {
                resolve(taskList)
            }
        }).catch((error) => {
            reject(error)
        })
    }
    task(index)
})

当资源准备好以后,调用drawImage方法开始排行榜数据绘制,这个就是普通的2d绘制了,没有什么太多的技巧,主要就是有一个初始的initTop值,元素的位置会相对该Top值偏移以此来进行定位。关于头像画圆,主要是经过建立一个cavnas,而后把头像画上去,再用arc剪裁,而后再把头像纹理画到shareCanvas上。

// 头像剪裁成圆
const avatarCanvas = wx.createCanvas()
avatarCanvas.width = 80
avatarCanvas.height = 80
const avatarContext = avatarCanvas.getContext('2d')

avatarContext.save();
avatarContext.arc(40, 40, 40, 0, Math.PI * 2);
// 从画布上裁剪出这个圆形
avatarContext.clip();
avatarContext.drawImage(topItem.avatarDOM, 0, 0, 80, 80)
avatarContext.restore();

// 实际的画
shareContext.drawImage(avatarCanvas, 130, (initTop + (i * 105)))

有什么方法能够把开放数据域的数据带出来?

目前尝试有,给shareCanvas附加属性,innerHTML,appendChild,data属性,均没有用,在开放数据域是个受限的环境,几乎绝大部分api都没法使用,包括localStorage等,也不能经过canvas的getDataURL把数据转换成Image,之因此这样的限制,是由于为了防止部分人经过开放数据域获取用户信息以后,再用getDataURL拿到绘制的用户数据,传递给后端API,作OCR图像识别解析微信用户关系,因此微信限制shareCanvas输出成图像,且限制,必须只能画在主屏上面。而后再经过人工审核机制,防范恶意小程序偷数据。

「生成并保存战绩页」
图片描述

把用户玩游戏的成果,包装成一个专属的宣传卡片,是诱导并激发用户分享意愿的一个强有力的方法。咱们使用canvas结合微信小游戏开发生态,保存当前用户昵称和当场游戏结果生成不一样的文案和图片,完成了从画布到截屏和存入相册一系列动做。

微信小游戏开发生态已经为你打通了画布到截屏到存入相册这一系列动做。

在本游戏中使用了 toTempFilePathsaveImageToPhotosAlbum 方法。这二者方法都挂载在wx对象下。

toTempFilePath, 将当前 Canvas 保存为一个临时文件,并生成相应的临时文件路径。

saveImageToPhotosAlbum,保存图片到系统相册。
核心代码:

//  loadCanvas 为咱们专门为渲染战绩卡片而初始化的一个 2d canvas。

loadCanvas.toTempFilePath({
    fileType: 'jpg',
    success: function(res) {
        wx.saveImageToPhotosAlbum({
            filePath: res.tempFilePath,
            success: function() {
            }
        })
    }
})

注意事项:

saveImageToPhotosAlbum会弹出相册受权弹框,记得处理失败回调哦。

若是里面要引用用户相关信息,记得也要处理用户不受权的状况。

关于生成到相册的图片清晰度问题,经验是,再用canvas绘制背景图的时候,别用高清图,都会被wx压缩的。这个本身去压缩纹理素材,这样既不会触发微信的压缩,又比微信压缩的效果好。

「游戏声音开关的控制」

音频建立

let audio = new Audio(`${path}`);
    //path 为音频文件地址

音频播放

audio.play();

音频中止

audio.pause();

小游戏有背景音乐/小狗撞击障碍物失败音乐/小狗踩空音乐/阶梯陨落音乐/游戏升级音乐5种不一样类型的音频文件。咱们的作法是统一将音频文件集中预加载,并缓存到一个音频库的对象中,后续按需调用。

音频库对象中,咱们定义了, begin, change,off,on等方法,在全局须要的地方作统一调度,这样可使得,全部音频管理来自于一个music center,不会由于逻辑而紊乱。

在music center中,change,on,off均作了变量锁,使得了全域的操控触发,惟一相应。

for (let t of soundassets) {
    let audio = new Audio(assetpath + t.url);

    audio.autoplay = t.setting.autoplay;
    audio.loop = t.setting.loop;

    audio.addEventListener("load", () => {
        soundonload(audio, t);
        loaded++;
        onProgress(t.url, loaded, itesmtotal);
        if (loaded === itesmtotal) {
            onLoad();
        }
    });
    audio.addEventListener("error", (e) => {
        onError(assetpath + t.url);
    });
    itesmtotal++;
}

「游戏难度方面」

游戏的初版本,咱们的游戏难度是每一层阶梯以1.3s的速度陨落,每完成50s加大一倍难度的策略;小伙伴们反应游戏可贵分低以后,咱们迅速的调整游戏策略,把阶梯陨落速度下降,而且改成每100s加大一倍难度的玩法。

BitmapFont游戏数字的绘制
图片描述

在游戏当中常常有须要绘制一些图形的文字数字等,这个时候可使用BitmapFont,也就是准备好文字与数字的纹理,并用TexturePack组成一张大图及记录图片尺寸位置信息的font.json。在代码当中加载这两个资源,而后就能直接使用了。

在咱们的游戏当中也涉及到须要绘制数字图形分数的部分,首先是制做了一个用于处理分数的Score类,该类负责对分数进行记录,清零以及管理和数字图形的渲染。

当玩家分数变化时,咱们会调用render进行分数的绘制,先经过string pad,将数字型的分数补零为5位数字字符串

_pad(num, n) {
    var len = num.toString().length;
    while (len < n) {
        num = "0" + num;
        len++;
    }
    return num;
}

打散字符串,并进行循环,更新对应位数的纹理

// 获得当前数字的纹理
const texture =this.resources.getTexture(texturePath);
// 替换对应位置的material的纹理为分数的纹理
this.score[`n${i}`].material.map = texture;

其它一些方法,reset用于分数清零,init负责对分数部件的及初始位数初始化

reset() {
    this.render(0)
}

游戏体验,请扫小程序码:
若是你有更有趣的想法,欢迎留言区讨论~
图片描述

更多小游戏,请关注微信公众号:前端工坊
后续,咱们会推出更多纯干货技术分享
图片描述

相关文章
相关标签/搜索