【游戏客户端】制作你玩我猜Like玩法
大家好,我是Lampard猿奋~~ “你画我猜”相信大家都不陌生,当初这款小游戏可谓茶余饭后必玩之选,风头一时无二。今天要和大家分享如何实现一个你玩我猜Like的玩法。
我们可以简单的把需求拆成两个个部分:1.绘图功能的实现,2.数据传输方式
本文不贴代码只讲设计思路,希望能帮助到大家~
(一)绘图功能的实现
(1)画布
要作画我们首先需要一个画布,画布我们可以在UI编辑器中用一个image控件来显示。但是需要注意两个地方:
首先就是不要用太深的颜色作为底色(你没见过黑色的画布吧,五彩斑斓的黑当我没说...), 再者就是记得画布的透明度最好设置成255,因为之后的绘制是用目标颜色和画布进行混合渲染,因此如果透明度不为255的话,渲染出来的结果可能和设定的画笔颜色会有出入
(2)rendertexture类
紧接着就是要介绍我们实现绘图逻辑的类:RenderTexture类,怎么去理解这个类呢?大家可ui把这个类当作一张白纸,我们想要实现绘制效果,首先要在白纸上画出背景颜色(画布颜色),然后再在画布颜色的基础上,继续增加各式各样我们想要绘制的图形
此时问题很多的小明就要问了:为啥不直接用UI管理器导入的Image作为真正的画布?还要搞什么白纸的概念呢?
这是因为我们要实现画笔所划过的地方与画布进行混合渲染,而RenderTexture类支持这个功能,而UI编辑器中封装好的UI类Image和Sprite是不支持的
RenderTexture类底层是调用了OpenGL的glBlendFunc的混合绘制方法,关于RenderTexture的研究我们在之前做过了。
function clsWidgetScratch:initRenderTexture()local renderTexture = cc.RenderTexture:create(self.width, self.height)self:addChild(renderTexture)renderTexture:addChild(self.drawNode)renderTexture:addChild(self.rubberImg)return renderTexture
end
如果大家想了解cocos中如何调用RenderTexture来进行混合绘制,可以看看我之前写的这一篇文章:【游戏客户端】实现刮刮乐效果
如果大家想要了解RenderTexture的底层openGL渲染逻辑,可以看看这一篇文章:glBlendFunc颜色混合
(3)画笔DrawNode
方才我们已经创建了一个Rendtexture的对象,然后把画布的颜色渲染了上去,紧接着我们只需要创造出画笔绘制的内容,然后和画布进行混合渲染就可以实现我们的绘图功能
我们知道画画是需要从点到线再到面的,因此我们可以实例化一个带颜色的DrawNode对象用来描绘画笔点下的一个点
而想要绘制出不同的颜色,只需要调整drawSolidCircle接口中的color字段即可
function clsWidgetScratch:initDrawNode()local drawNode = cc.DrawNode:create()drawNode:setVisible(true)drawNode:setBlendFunc(gl.ONE, gl.GL_ONE_MINUS_SRC_ALPHA)drawNode:setLocalZOrder(1000)drawNode:drawSolidCircle(cc.p(self.length / 2, self.length / 2), self.length / 2, 360, 10, 1, 1, self.color)return drawNode
end
(4)调整绘制的位置
有了画布和Drawnode画出来的点,我们下一步还需要做的就是监听玩家绘制的位置
首先我们要创建一个监听用的Layout置于最上层,用onTouchMove的方法监听玩家触摸的位置。紧接着把DrawNode的position设置成玩家落笔的位置,然后调用cocos中renderTexture的混合绘制方法即可
(5)橡皮擦功能
画图需求中带有了橡皮擦功能,其实聪明的朋友已经发现,所谓的橡皮擦功能。我们可以简单的把DrawNode的颜色设置为和画布的一样,就可以把之前绘制的内容给覆盖掉了
好,我们看看目前所实现的效果:
(二)数据传输方式的选择
当看到玩法效果实现出来的时候,我也和大家一样欣喜若狂,以为这个需求快要到尾声了。然鹅,困难的地方还在后面:首先这并不是一个单机玩法,它是多人实时同步的一个游戏。那就意味着你需要把绘制的内容实时同步给所有的玩家,那么问题来了,对于一幅960*640的画布,你要如何进行实时传输呢?
(1)Http协议拉取最新的图片
首先我们可以想到用类似patch的方式,每当绘制成功后就把内容以png格式上传到资源库中,然后其他猜测的玩家则用http协议去拉取最新的资源
这样的做法好处是确保绘制内容的准确性和完整性
坏处是每一次绘制内容的更新,都要去拉取一次资源,我们知道文件的IO是非常耗时的,这样当遇到性能有点卡顿的时候,更新时就会一闪一闪,这样玩家的体验上并不好
(2)RPC协议同步像素内容
那我们可不可以去同步像素的内容呢?虽然项目的每一个像素点用的都是RGBA8888的格式记录,也就是说一个像素需要32位,但我们实际只用到了8个颜色,且透明度都是255,因此我们完全可以用8个bit去映射记录每个像素的信息
但是虽然如此优化了,但每一个像素也需要用到1B的大小去记录,而我们有960*640个像素,960 * 640 / 1024,算下来我们还要用到600KB的数据去传输这个画面。
但由于处于性能压力的监控,服务端很早之前就闲置了,每次使用RPC协议,不能交互超过100KB的数据,因为如果我们需要用到这种做法,我们还得进一步优化:可以把画布分块,像场景地图那样切成小块去分开传输,总的来说也算是一种思路
这种做法好处是确保了画面的准确性,以及省去了IO的时间
坏处是需要分块请求画面内容,比较难确保每次数据传输我每一小块的内容都可以准确的获取数据和显示
(3)RPC协议同步绘制流程
其实第二种的思路已经很接近正解了,但是唯一问题就是不想采用分块的方式,想完完整整的一幅图片同步给玩家。因此我想到了第三种方法:记录玩家的绘制流程,用RPC协议同步玩家的绘制记录。
什么意思呢?首先我们把整一个画布划分成一个3*3,或者4*4像素方块组成的整体。然后我们用一个二维数组去记录这些小方块是否被绘制,绘制的颜色映射是什么。
每当玩家绘制的时候,我们就更新二维数组中该小方块的映射值,然后把这个二维数组的记录信息,以二进制数据的形式拼接在一起上传。同时参与的玩家通过RPC协议获取最新的绘制信息,然后解码成二维数组,重新根据绘制信息重新把内容“画”出来
这样我们就可以把3*3或者4*4的像素信息融合成一份,虽然会有一些锯齿感和多花了时间进行编码和解码,但是成功把数据压缩到了100K以内,且避免了IO的卡顿
local NORMAL_LENGTH = 3local i = math.floor(y / NORMAL_LENGTH)
local j = math.floor(x / NORMAL_LENGTH)
self.nodeList[i*NORMAL_LENGTH][j*NORMAL_LENGTH] = self:getEncodeNodeIndex()
(三)最后看看我们最终的效果
你画我猜玩法视频
至此你画我猜玩法的分享到此结束~~
感谢阅读,点赞,关注!!!