unity热更方案 java script binding中使用protobuff(二)

来源:互联网 发布:python 日期加减 月份 编辑:程序博客网 时间:2024/05/16 19:52

在上一篇文章中,分析了夜莺的protobuff的使用方案

复习下结论

1.C#类本身,如例子中的ExampleMessage,成员名字与.proto文件中定义的要一致

2.协议字符串,即protobuff中的通用的.proto中的内容,需要注意,不支持import,所以需要把依赖的内容写到一起 

3.协议的唯一标识,函数GetMessageName中返回的字符串

我们知道,protobuff-net已经生成了用于序列化的C#类了,我尝试过把生成的类直接转为JS,出现若干问题,但是通过把相关的类

导出到JS的方式能够解决转换失败的问题,但是还有运行时的问题,最要命的几点是

  [global::System.Serializable, global::ProtoBuf.ProtoContract(Name=@"msgVector3")]  public partial class msgVector3 : global::ProtoBuf.IExtensible  {    public msgVector3() {}        private long _x = default(long);    [global::ProtoBuf.ProtoMember(1, IsRequired = false, Name=@"x", DataFormat = global::ProtoBuf.DataFormat.TwosComplement)]    [global::System.ComponentModel.DefaultValue(default(long))]    public long x    {      get { return _x; }      set { _x = value; }    }    private long _y = default(long);    [global::ProtoBuf.ProtoMember(2, IsRequired = false, Name=@"y", DataFormat = global::ProtoBuf.DataFormat.TwosComplement)]    [global::System.ComponentModel.DefaultValue(default(long))]    public long y    {      get { return _y; }      set { _y = value; }    }    private long _z = default(long);    [global::ProtoBuf.ProtoMember(3, IsRequired = false, Name=@"z", DataFormat = global::ProtoBuf.DataFormat.TwosComplement)]    [global::System.ComponentModel.DefaultValue(default(long))]    public long z    {      get { return _z; }      set { _z = value; }    }    private global::ProtoBuf.IExtension extensionObject;    global::ProtoBuf.IExtension global::ProtoBuf.IExtensible.GetExtensionObject(bool createIfMissing)      { return global::ProtoBuf.Extensible.GetExtensionObject(ref extensionObject, createIfMissing); }  }
这是用protobuff-net生成的一个最基础的向量类,有三个分量

//基础3维向量message msgVector3{//xoptional int64 x = 1;//yoptional int64 y = 2;//zoptional int64 z = 3;}

我们看到,.proto文件中定义的成员为xyz,但是在生成的类中,变成了_x _y _z,而在JS运行中,序列化使用的是field功能,名字认的就是成员名字

而不是C#中的property的名字,直接导致序列化失败,需要写个后处理工具,把对应名字改下,生成.cs后再进行处理

还一个问题是

private global::ProtoBuf.IExtension extensionObject;    global::ProtoBuf.IExtension global::ProtoBuf.IExtensible.GetExtensionObject(bool createIfMissing)      { return global::ProtoBuf.Extensible.GetExtensionObject(ref extensionObject, createIfMissing); }
这里有个成员 extensionObject,是用来实现protobuff的继承功能,这么强大(鸡肋)的功能我还真没有用过,但是由于 extensionObject也是一个

成员,而且在没有父类的情况下,为null,直接导致序列化失败,悲剧,不过也是有解决办法的,经过分析,假设我们不使用继承功能的前提下,

extensionObject一定是为null的,而且C#运行情况下,需要的是GetExtensionObject这个函数,而不是extensionObject这个变量,所以我们尝试

把extensionObject这个变量删除,然后把函数GetExtensionObject的实现直接改为 return null,OK,妥了,可以序列化了

幸运的是,我做了实验,如果把整个.proto文件作为protoString传入给序列化函数,是可以正确的序列化的

现在,C#类和protoString都已经有了,就差一个protoName了,做法就是,在生成的C#类里加个函数,直接返回protoName,所以改变后的文件长

这样

[global::System.Serializable, global::ProtoBuf.ProtoContract(Name=@"Vector3Msg")]  public partial class Vector3Msg : global::ProtoBuf.IExtensible  {    public Vector3Msg() {}        public int  x = default(int);    [global::ProtoBuf.ProtoMember(1, IsRequired = false, Name=@"_x", DataFormat = global::ProtoBuf.DataFormat.TwosComplement)]    [global::System.ComponentModel.DefaultValue(default(int))]    private int _x    {      get { return  x; }      set {  x = value; }    }    public int  y = default(int);    [global::ProtoBuf.ProtoMember(2, IsRequired = false, Name=@"_y", DataFormat = global::ProtoBuf.DataFormat.TwosComplement)]    [global::System.ComponentModel.DefaultValue(default(int))]    private int _y    {      get { return  y; }      set {  y = value; }    }    public int  z = default(int);    [global::ProtoBuf.ProtoMember(3, IsRequired = false, Name=@"_z", DataFormat = global::ProtoBuf.DataFormat.TwosComplement)]    [global::System.ComponentModel.DefaultValue(default(int))]    private int _z    {      get { return  z; }      set {  z = value; }    }    global::ProtoBuf.IExtension global::ProtoBuf.IExtensible.GetExtensionObject(bool createIfMissing)      { return null; }    public static string GetProtoName()    {       return "Vector3Msg";    }  }

仔细看发现msgVector3变成了Vector3msg,不要在意这些细节,因为我是从不同的工程中获得的资料,然后封装一个类,可以这么写

public class GameSocketInterface{    public static void sendMessage<T>(short cmd, T param, string fileName = null, string protuBufname = null)    {        if (VersionControl.useJS)        {            Network.MessageWrap msgWrap = new Network.MessageWrap();            msgWrap.protuBufname = "protocol.msg." + protuBufname;            msgWrap.fileName = fileName;            GameSocket.Instance.sendStringMessage(cmd, msgWrap.Encode(param));        }        else        {            GameSocket.Instance.sendMessage(cmd, param);        }    }    public static T deserialize<T>(NetMsgParam data, string fileName = null, string protuBufname = null)    {        if (VersionControl.useJS)        {            Network.MessageWrap msgWrap = new Network.MessageWrap();            msgWrap.protuBufname = "protocol.msg." + protuBufname;            msgWrap.fileName = fileName;            return (T) msgWrap.Decode(data.Get64String());        }        else        {            return GameSocket.Instance.deserialize<T>(data.GetBytes());        }    }}
关于 MessageWrap代码

public class MessageWrap : MessageParent    {        public string fileName;        public string protuBufname;        public override string GetProToString()        {            TextAsset proto = (TextAsset)Resources.Load("@Protos/" + fileName);            return proto.ToString();        }        public override string GetMessageName()        {            return protuBufname;        }    }

关于NetMsgParam的代码

public struct NetMsgParam    {        private byte[] m_data;        public void SetData(byte[] data)        {            m_data = data;        }         public string Get64String()        {            return Convert.ToBase64String(m_data);        }        public byte[] GetBytes()        {            return m_data;        }    }
为什么要封装下呢,因为byte[]是从C#传入,然后转到JS的,但是sharpkit不支持直接传入byte[]的转换

序列化使用示例

TeleporterMsg msg = new TeleporterMsg();msg.teleporterTid = teleportId;GameSocketInterface.sendMessage((short)PacketProtocolType.CG_SCENE_SWITCH, msg, "scene", TeleporterMsg.GetProtoName());
反序列化示例

private void OnGetAOIInfoMsg(NetMsgParam param)        {            //根据信息生成角色            SceneAOIInfoMsg msg = GameSocketInterface.deserialize<SceneAOIInfoMsg>(param, "scene", SceneAOIInfoMsg.GetProtoName());            List<SceneObjInfoMsg> objList = msg.aoiObjects;            for (int i = 0; i < objList.Count; i++)            {                SceneObjInfoMsg info = objList[i];                if (info.facadeType == AOIObjFacadeType.PLAYER)                {                    CreateOtherHero(info);                }                else if (info.facadeType == AOIObjFacadeType.MONSTER)                {                    CreateMoster(info);                }            }        }


我们可以高兴的看到,在使用者层面,已经跟直接用protobuff-net版本写C#代码十分接近了,只是目前还需要传入proto文件的名字,从而获得

proto文件内的字符串获得protoString,而通过设置开关,就可以实现传说级的终极目标:C#写代码,调试,JS发布,热更妥妥的


已经出现太长不看的风险了,再啰嗦几句,其实如果你够勤奋并且有耐心,到这里已经可以实现在JSB使用protobuff了,实际还不够,因为改协议生

成的C#文件太痛苦了,好在我们已经实现了终极解决方案,直接改了protobuff-net的代码生成规则,直接生成符合JS使用的代码,而且兼容C#模式

运行,后续会给大家分享出源代码和原理分析,今天就到这里了~






0 0