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

在上一篇文章中,分析了夜莺的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
{
	//x
	optional int64 x = 1;
	//y
	optional int64 y = 2;
	//z
	optional 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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值