多人状态同步 知识和问题回顾

我想实现自由视角,虚拟摇杆和多人位置同步的功能。将这三个功能放一块展示。看了下网盘日期,是去年6月份写的。 虚拟摇杆和移动的同步 角色移动是往前的,所以只要确认角色forward向量的世界坐标,移动问题就能解决。 键盘上的wasd操作只有vertical[0,1]和horizontal[0,1] 。它只有8个方向。

我想实现自由视角,虚拟摇杆和多人位置同步的功能。将这三个功能放一块展示。看了下网盘日期,是去年6月份写的。

虚拟摇杆和移动的同步

角色移动是往前的,所以只要确认角色forward向量的世界坐标,移动问题就能解决。

键盘上的wasd操作只有vertical[0,1]和horizontal[0,1]。它只有8个方向。通常键盘操作的游戏改变方向,按下方向键持续旋转角度来实现的。

虚拟摇杆不同于键盘,它可以确定任意一个方向。

protected override void Start()
	{
		base.Start();
		mRadius = (transform as RectTransform).sizeDelta.x * 0.5f;
	}


	public override void OnDrag (UnityEngine.EventSystems.PointerEventData eventData)
	{
		base.OnDrag (eventData);
		var contentPostion = this.content.anchoredPosition;
		if (contentPostion.magnitude > mRadius){
			contentPostion = contentPostion.normalized * mRadius ;
			SetContentAnchoredPosition(contentPostion);
		}
}


利用UGUI的ScrollRect,分为摇杆底盘和摇杆,摇杆对底盘的相对坐标向量就是了。


摇杆往前推,角色是相对谁往前的方向。角色理当是往摄像机的前方移动,无论角色目前面向何处。

Vector3 Joy = new Vector3 (contentPostion.x, 0, contentPostion.y).normalized;
Vector3 Cam = Vector3.Scale(Camera.main.transform.forward, new Vector3(1, 0, 1)).normalized;
JoyAngle = Joy.x > 0?Vector2.Angle (new Vector2 (0, 1), new Vector2 (Joy.x, Joy.z)):360-Vector2.Angle (new Vector2 (0, 1), new Vector2 (Joy.x, Joy.z));
CamAngle = Cam.x > 0?Vector2.Angle (new Vector2(0,1),new Vector2(Cam.x,Cam.z)):360-Vector2.Angle (new Vector2(0,1),new Vector2(Cam.x,Cam.z));
sumAngle = JoyAngle+CamAngle;

接受到摇杆输入的方向contentPostion后,在为角色重新确定前方需要考虑到摄像机的方向。sumAngle是角色相对世界旋转角度。

dragLevel = System.Math.Abs(contentPostion.x)/mRadius > 0.6f || System.Math.Abs(contentPostion.y)/mRadius > 0.6f? 2f : 1f;

根据拖放摇杆的位置和底盘半径比得出强度。做走和跑。


状态传输

public override void OnEndDrag (UnityEngine.EventSystems.PointerEventData eventData)
	{
		base.OnEndDrag (eventData);
		dragLevel = 0;
		MessageHandle.sendDragState (sumAngle, dragLevel);
	}


只在摇杆变动时传输摇杆状态,静止时则不需要频繁的传输位置或拖放信息,节省资源。


同样接收到的摇杆状态,但服务器或其他客户端不能和自己客户端完全一样地模拟出行为,时间久差距变拉大。

所以还要定时更新位置信息。

	void Update(){
		if (Time.time > time) {
			time = Time.time + 5f;
			if(GameObject.Find(StatePool.localEp)!=null)
			sendMyTransform ();
		}
	}



服务器

采用socket异步通信方式,没什么好说的,看官方文档。

方便起见,我只将从客户端收到的信息再进行分发而已。

public class SocketClient : MonoBehaviour {
	static byte[] inbytes = new byte[1024];
	static byte[] outbytes = new byte[1024];
	public Socket socket;
	void Awake()
	{
		socket = new Socket (AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
		socket.BeginConnect ("127.0.0.1", 200, connectTarget, socket);
	}

	void connectTarget(IAsyncResult ar)
	{
		socket.EndConnect (ar);
		StatePool.localEp = socket.LocalEndPoint.ToString();
		StatePool.RemoteEp = socket.RemoteEndPoint.ToString();
		Initialization.initPlayers();
		socket.BeginReceive (inbytes, 0, inbytes.Length, SocketFlags.None, receiveTarget, socket);
	}
	public void sendMsg(string msgSend)
	{
		outbytes = Encoding.UTF8.GetBytes (msgSend);
		socket.BeginSend(outbytes, 0, outbytes.Length, SocketFlags.None, sendTarget, socket);
	}
	void sendTarget(IAsyncResult ar)
	{
	}
	void receiveTarget(IAsyncResult ar)
	{
		int size = socket.EndReceive (ar);
		if (size > 0)
		{
			MessageHandle.readMsg(Encoding.UTF8.GetString (inbytes, 0, size));
		}
		socket.BeginReceive(inbytes, 0, 1024, SocketFlags.None, receiveTarget, socket);
	}

}



客户端消息接收

public class SocketClient : MonoBehaviour {
	static byte[] inbytes = new byte[1024];
	static byte[] outbytes = new byte[1024];
	public Socket socket;
	void Awake()
	{
		socket = new Socket (AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
		socket.BeginConnect ("127.0.0.1", 200, connectTarget, socket);
	}

	void connectTarget(IAsyncResult ar)
	{
		socket.EndConnect (ar);
		StatePool.localEp = socket.LocalEndPoint.ToString();
		StatePool.RemoteEp = socket.RemoteEndPoint.ToString();
		Initialization.initPlayers();
		socket.BeginReceive (inbytes, 0, inbytes.Length, SocketFlags.None, receiveTarget, socket);
	}
	public void sendMsg(string msgSend)
	{
		outbytes = Encoding.UTF8.GetBytes (msgSend);
		socket.BeginSend(outbytes, 0, outbytes.Length, SocketFlags.None, sendTarget, socket);
	}
	void sendTarget(IAsyncResult ar)
	{
	}
	void receiveTarget(IAsyncResult ar)
	{
		int size = socket.EndReceive (ar);
		if (size > 0)
		{
			MessageHandle.readMsg(Encoding.UTF8.GetString (inbytes, 0, size));
		}
		socket.BeginReceive(inbytes, 0, 1024, SocketFlags.None, receiveTarget, socket);
	}

}


消息处理


客户端收到信息后,调用MessageHandle处理。

public static void readMsg(string str) {
		Loom.RunAsync(()=>{
			Loom.QueueOnMainThread(()=>{
				print(str);
				string[] msgList = str.Split('$');
				string jsonData = msgList[1];
				switch (msgList[0].Split('/')[1])
				{
				case "03":
					lb.addLog ("accept 请求: 我的位置");
					if (StatePool.player){
						sendMyTransform();
					}else{
						sendMyCreate();
					}
					break;
				case "19":
					transformInfo t = JsonUtility.FromJson<transformInfo>(jsonData);
					newCharacter (character, t.Ep, t.postion, t.eulerAngles);
					break;
				case "18":
					lb.addLog ("accept 收到: 位置角度信息,更新或创建");
					transformInfo t1 = JsonUtility.FromJson<transformInfo>(jsonData);
					if(GameObject.Find(t1.Ep)!=null){
						reTransform(GameObject.Find(t1.Ep), t1.postion, t1.eulerAngles);
						
					}else{
						newCharacter(GameObject.Find(StatePool.localEp), t1.Ep, t1.postion, t1.eulerAngles);
						
					}
					break;
				case "28":
					dragState d = JsonUtility.FromJson<dragState>(jsonData);
					if(GameObject.Find(d.Ep)){
						GameObject.Find(d.Ep).GetComponent<move>().setDragState(d.sumAngle, d.dragLevel);
					}
					break;
				}
					
			});
		});
	}


socket异步通信是多线程的,通过void receiveTarget(IAsyncResult ar)

收到消息后主动去处理,这不在主线程之内。

UnityEngine不能在主线程之外调用。这里使用了一段代码使其在主线程中跑,当然这不合适。

如果将收到的信息储存,另在主线程中去读取,这就被动了,没有实时性可言,也不该这么做。


数据传输格式

socket的传输类型是bytes[]。消息层次是多层的,如数据类型,是否要求回复...当然这些也能用9_5_2_7代替。

我将消息当作string处理,用符号分割成数组。主体数据则用unity自带的JsonUtility,转换成对象。



知秋君
上一篇 2024-07-19 17:02
下一篇 2024-07-19 16:36

相关推荐