cocos lua游戏过程记录(复盘)

来源:互联网 发布:淘宝也有货到付款的吗 编辑:程序博客网 时间:2024/04/29 12:27

一、复盘原理

我们都知道所谓回放游戏过程,只不过把游戏消息存储下来,在执行一遍。

我的复盘原理是这样实现(麻将):

1)游戏结束,服务器会发送这局执行的所有消息。

2)客户端接受到消息,通过使用lua绑定C++方法读出数据(这也是第二点要讲解的)

3)lua读写文件,把读出的数据以二进制形式存储到本地

4)从本地读出二进制文件,解析内容,得到消息和数据,从而实现回放。

二、读取数据

对于一般的网游公司,都有自己的一套网络通讯代码。

1)一般都是服务器定义一个结构体

2)发送给客户端

3)客户端接受数据,解析数据执行,并返回结果给服务器。

4)循环1-3。。。

因为从服务器接受过来的数据是占据连续的一片内存空间,这有什么用呢?

用处就是:我们可以通过按字节移位的方式来读取数据了(按一字节对齐)。

举个列子:

struct AAA{DWORD                   a1;WORDa2;};
服务器发来的结果体是这样的,那我们应该如何读取a1,a2中的数据呢?

DWORD MsgClass::readDWORD(){    WORD value = 0;    if (m_Pos + 4 <= m_Len)    {        WORD t = 0;memcpy(&t,(const BYTE*)m_Buffer + m_Pos,4);        value = t;    }    m_Pos += 4;    return value;}

WORD MsgClass::readWORD(){    WORD value = 0;    if (m_Pos + 2 <= m_Len)    {        WORD t = 0;memcpy(&t,(const BYTE*)m_Buffer + m_Pos,2);        value = t;    }    m_Pos += 2;    return value;}
按照上面写的代码,绑定解析类MsgClass来解析结构体,这就是按字节移位的方式来读取数据了。

我们就能在lua中使用绑定方法来解析数据:

local a1 = readDWORD() 

local a2 = readWORD() 

三、存储文件

从第二点可以知道,不管传过来的数据如何,都是可以解析出来的。

那下面有2种形式来存储了:table 和 二进制存储

1、table 存储:顾名思义把传过来的数据以table形式存储

网上有存储脚本,就不列出了,最后存储的形式:(大致相同)

require "SaveTableToFile"local ex = {}ex.year = 2017ex.month = 1ex.day = 4ex.name = "msdb1989"table.save(ex, "file.dat")----------------------------------------return {-- Table: {1}{   ["day"]=4,   ["year"]=2017,   ["month"]=1,   ["name"]="msdb1989",},}
存储形式是个表, 读出来也是一个表。

存储方法的优点:一目了然,存储的数据很明确。

存储方法的缺点:增加了存储文件的大小,io操作效率低,对于大型数据不适合。

2、二进制存储:这个其实是我自己定义的(不好意思),就是服务器传来的数据,不做任何处理,直接存储字节流(换句话说就是来什么存什么)。

例如:

struct CMD_S_Task{DWORDdwUserID;WORDwKindID;WORD    wServerID;............charszNickName[MAX_LEN];};
对于这样很大的结果体,或者数据很多时,用table来存储性能就很低了。

做复盘的时候,有个复盘头和复盘数据。复盘头是记录玩家信息,复盘数据则是记录玩家操作,而我一开始用table来实现,发现复盘数据很多的情况下,根本来不及存储,所以我才改为二级制存储。
1)保存文件

那怎么做到把发来的数据一下全部存储呢。

/// 读取一个void*数据char* MsgClass::readvoids(int length) {if (length < 0) {return "";}char* value = new char[length + 1];memset(value, 0, sizeof(char)*(length + 1));if (m_Pos + 1 <= m_Len) {memcpy(value, (const char*)m_Buffer + m_Pos, length);}m_Pos += length;return value;}
要读取这个数据包内容,self.m_AryReplayBuffer = msgclass:readvoids(512) 这样就可以了,512表示字节为这个数据包大小,目前最大支持20K,当然还能更大。

这样存下来就是二进制文件了。

2)读取文件

//写入voidvoid MsgClass::WritevoidToBuffers(const char* ch, int len) {memcpy((char*)&m_socketBuffers[m_len], ch, len);m_len += len;}
file = io.open("123.dat", "rb")if file == nil then    returnendResetBuffers()WritevoidToBuffers(file:read(512), 512)SendForMessage("主消息", "子消息")
这样就能读取保存在123.dat里的512字节,然后把消息派发下去,就能模拟服务器发来的消息,这样就能实现复盘了。

四、后续工作

当然还有很多工作没有处理

比如:回退,前进,最开始,结束等等。

这些其实,只不过是发不同的消息而已。

例如:上面例子512位头数据,那么512之后就是游戏开始消息了,开始消息发完就是第二个游戏消息了,既然知道所有数据,那前后,开始结束也就知道了。







1 0
原创粉丝点击