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

本文介绍了一种麻将游戏的复盘技术实现方案,包括游戏消息的读取、数据的存储方式(table和二进制存储)、复盘原理及后续可能的工作。通过详细的技术流程和示例代码展示了如何有效地进行游戏过程的记录与回放。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、复盘原理

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

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

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

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

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

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

二、读取数据

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

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

2)发送给客户端

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

4)循环1-3。。。

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

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

举个列子:

struct AAA
{
	DWORD                   a1;
	WORD			a2;
};
服务器发来的结果体是这样的,那我们应该如何读取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 = 2017
ex.month = 1
ex.day = 4
ex.name = "msdb1989"

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

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

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

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

例如:

struct CMD_S_Task
{
	DWORD							dwUserID;
	WORD							wKindID;
	WORD						    	wServerID;
	....
	....
	....
	char							szNickName[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)读取文件

//写入void
void 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
    return
end

ResetBuffers()
WritevoidToBuffers(file:read(512), 512)
SendForMessage("主消息", "子消息")
这样就能读取保存在123.dat里的512字节,然后把消息派发下去,就能模拟服务器发来的消息,这样就能实现复盘了。

四、后续工作

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

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

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

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







评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值