我想实现自由视角,虚拟摇杆和多人位置同步的功能。将这三个功能放一块展示。看了下网盘日期,是去年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,转换成对象。