Unity&网络--HLAPI(1):服务器客户端变量同步,脏标识以及定制特性SyncVar

服务器客户端变量同步

在网络游戏中,大部分事务都是在服务器内处理的,例如NPC被玩家攻击后减HP,服务器需要将变化的HP值传到各个客户端,客户端更新此HP值后,客户端才会显示减血后的NPC血量,这种同步变量是网络游戏中最基本也是最频繁的操作。

这里写图片描述

那么如何实现这种变量同步?最暴力的方法可以是服务器每次同步通讯都将此HP的最新值发给客户端。例如在Unity中可以选择每帧都发送:

	void Update () {
		byte[] hp_Byte=BitConverter.GetBytes(hp);//1,将变量序列化为二进制格式
		myServer.SendToAll(hp_Byte,msgType_hpNPC1);//2,伪代码。将变量传给所有客户端,并通知它们此消息为NPC1的hp值。
	}

脏标识

但就算是一个小游戏,需要同步的变量也会很多,这种方法显然在性能和带宽上是无法接受的。更合理的思路是只同步发生变化的变量,这里可以用到脏标识,dirty bit。
声明一个脏标:
int m_DirtyBit=0;
它的二进制表示00000000 00000000 00000000 00000000,可以单独使用每一位记录一个变量是否在此帧内被更改过,因此一个32位int可以共追踪32个变量的变化情况。脏标通过与2的0~31次方进行位运算以单独的更改每一位。

例如我们可以这样实现:

	private long dirty_bit=0;

	private int _npc1_hp=100;

	public int npc1_hp{
		get{
			return _npc1_hp;
		}
		set{
			if(value!=_npc1_hp){
				dirty_bit|=0x01;//一旦此变量发生变化,则将dirty_bit的某位变为1
				_npc1_hp=value;
			}
		}
	}

	private int _npc2_hp=100;

	public int npc2_hp{
		get{
			return _npc2_hp;
		}
		set{
			if(value!=_npc2_hp){
				dirty_bit|=0x02;//以此类推
				_npc2_hp=value;
			}
		}
	}

	void Update () {
		SyncVars();
	}

	private void SyncVars(){//然后在同步变量方法内通过检查脏位的各个位来判断哪些变量发生了变化并发送给各个客户端。
		if(dirty_bit&0x01){//如果脏位第一个bit为1则为true
			myServer.SendToAll(BitConverter.GetBytes(_npc1_hp,msgType.npc1HP));
		}
		if(dirty_bit&0x02){
			myServer.SendToAll(BitConverter.GetBytes(_npc2_hp,msgType.npc2HP));
		}
		dirty_bit=0;
	}

定制特性SyncVar

那么在Unity中,Unet模块通过一个[Syncvar]特性,使用代码注入完成了上面的所有内容,如果在需要同步的变量上方加上一个[Syncvar]特性,那么一旦此变量发生变化,服务器就会将此变量发送给所有客户端。开发者不再需要自己实现序列化,脏标识判断以及send方法。

	[SyncVar]
	private int _npc1_hp=100;

通过官方文档介绍,每个继承自NetworkBehaviour的类最多只可以有32个标为SyncVar特性的变量,这说明了每个NetworkBehaviour都只有一个32位int的胀标。

        [EditorBrowsable(EditorBrowsableState.Never)]
        protected void SetSyncVar<T>(T value, ref T fieldValue, uint dirtyBit)
        {
            bool changed = false;
            if (value == null)
            {
                if (fieldValue != null)
                    changed = true;
            }
            else
            {
                changed = !value.Equals(fieldValue);
            }
            if (changed)
            {
                if (LogFilter.logDev) { Debug.Log("SetSyncVar " + GetType().Name + " bit [" + dirtyBit + "] " + fieldValue + "->" + value); }
                SetDirtyBit(dirtyBit);
                fieldValue = value;
            }
        }

NetworkBehaviour源码中有这么一段代码,changed = !value.Equals(fieldValue); , 说明只有赋予SyncVar的值与前一个值不同时变量才会变脏。

SyncVar还支持一个hook特性,当SyncVar值在客户端发生变化时会带调用一个钩子方法,方便开发者插入逻辑。例如在下面的方法On_npc1_hp中可让UI更新NPC1的血条等操作,OnScale更新物体大小:

	[SyncVar(hook="On_npc1_hp"]
	private int _npc1_hp=100;
	
	void On_npc1_hp(int newValue){
		//变量被访问之后会执行此方法,newValue为变量被赋的值,类型要与SyncVar的变量类型一致。
	}
	[SyncVar(hook="OnScale"]
	public int scale_SV=1;
	
	void OnScale(int newScale){
		scale_SV=newScale;
		transform.localScale=newScale;
	}

经测试发现,hook函数调用完成后SyncVar才会完成同步。

SyncVar的实现结合了C#定制特性和Mono Cecil技术。简单来说,这些技术允许编译器在编译阶段插入IL代码,或对现有的IL代码进行修改。Unity的策略是在编译阶段,寻找所有带有SyncVar特性的变量,并插入上文中类似的属性访问器或一个warpper包装方法,在方法内对变量修改并进行脏位运算,然后将项目中所有标注SyncVar的变量的引用地址改为对应的wrapper方法或属性设置器处。

————————————————
参考:
https://blogs.unity3d.com/cn/2014/05/29/unet-syncvar/

————————————————
维护日志:
2017-5-5:更新
2017-5-8:更新
2017-5-10:更新
2017-5-12:更改
2017-8-22:更改标题;文章内增加段落小标题
2020-8-14:review

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值