学习笔记适合新手,如有错误请指正。?号处也请各位指点下,谢谢。
Network View是Unity封装的一套快速实现多人联机游戏的功能
接口介绍
Network View的接口主要包括属性和函数两个部分,属性主要用于获取当前的状态信息,函数主要用于通信
1属性
isMine:Network View是否是由“我”即本机创建的
observed:指定被观察及同步的组件或脚本
stateSynchronization:Network View类型Off为没有数据将被同步,ReliableDeltaCompressed为当数据发生变化时才发送,Unreliable为强制发送数据
viewID:NetworkViewID类型的ID
2函数
RPc(Remote Procedure Call,远程过程调用),在Network系统中可以理解为向其他机器发送消息,如果有同名函数将被调用
networkView.RPC(string funName,RPCMode mode,params object[] args);
参数funName为函数名,参数mode指定发送的范围,可选范围包括全部连接的机器包括本机,例如,All是发送给所有机器,Server是发送给服务器
args是传递的参数,可传可不传,支持类型包括int、float、string、NetworkPlayer、NetworkViewID、Vector3、Quaternion
只要有Network View,那么如何游戏对象上的脚本的函数体前用【RPC】标注,就可以通过RPC来通知调用,但是函数名必须唯一,否则其中只有一个会被调用
[RPC]
void funName()
{
........
}
实例——聊天室
新建场景新建空游戏对象并命名为Manager,并为其添加代码
代码中STATE枚举,idle是为创建或连接服务器时的空闲状态,server是作为服务器的状态,client是作为客户端的状态
using UnityEngine; using System.Collections; using System.Collections.Generic; public class C_16_2_2 : MonoBehaviour { private STATE state = STATE.idle; private string IP = "10.234.41.123"; private int Port = 1000; private NetworkView networkView; private string myName=""; //聊天消息记录 private string textRecord=""; //将要发送的消息 private string textToSend=""; //显示聊天记录的ScrollView的滚动位置 private Vector2 scrollPos; //显示聊天室当前人员的ScrollView的滚动位置 private Vector2 scrollPos_manlist; //储存NetworkViewID以及名字的字典 private Dictionary<string,string> manList = new Dictionary<string, string>(); //整个区域的宽度 private float thewidth = 500f; //整个区域的高度 private float theheight = 500f; //聊天室当前人员名字的链表 private List<string> names = new List<string>(); void Awake() { networkView = GetComponent<NetworkView>(); } void OnGUI() { GUILayout.BeginVertical(GUILayout.Width(thewidth),GUILayout.Height(theheight)); switch(state) { case STATE.idle: OnIdle(); break; case STATE.server: Format_CutOffLine(); Title(); Format_CutOffLine(); OnChat(); Format_CutOffLine(); OnServer(); break; case STATE.client: Format_CutOffLine(); Title(); Format_CutOffLine(); OnChat(); Format_CutOffLine(); OnClient(); break; } GUILayout.EndVertical(); } //显示标题 void Title() { GUILayout.BeginHorizontal(); GUILayout.Space(thewidth*0.5f -10f); GUILayout.Label("聊天室"); GUILayout.EndHorizontal(); } //idle状态 void OnIdle() { Format_CutOffLine(); GUILayout.BeginHorizontal(); GUILayout.Label("我的昵称:",GUILayout.Width(100)); myName = GUILayout.TextField(myName,GUILayout.Width(thewidth-100)); GUILayout.EndHorizontal(); Format_CutOffLine(); Format_Space(); Format_CutOffLine(); if(GUILayout.Button("建立聊天室",GUILayout.Width(thewidth))) { NetworkConnectionError error = Network.InitializeServer(10,Port,false); if(error == NetworkConnectionError.NoError) { manList.Add(Network.player.ToString(),myName); Server_UpdateNameList(); state = STATE.server; } } Format_CutOffLine(); Format_Space(); Format_CutOffLine(); GUILayout.BeginHorizontal(); GUILayout.Label("服务器IP地址:"); IP = GUILayout.TextField(IP,GUILayout.Width(thewidth-100)); GUILayout.EndHorizontal(); if(GUILayout.Button("连接服务器",GUILayout.Width(thewidth))) { NetworkConnectionError error = Network.Connect(IP,Port); if(error == NetworkConnectionError.NoError) { state = STATE.client; } } Format_CutOffLine(); } //连接服务器 void OnConnectedToServer() { Debug.Log("Connected to server"); networkView.RPC("Server_Join", RPCMode.Server,myName); } //当有客户端断开服务器时被调用的函数 void OnPlayerDisconnected(NetworkPlayer player) { string id = player.ToString(); Debug.Log("Leaved:"+id); if(manList.ContainsKey(id)) { networkView.RPC("SendText", RPCMode.All,manList[id]+"离开了聊天室"); manList.Remove(id); } Server_UpdateNameList(); } //server状态 void OnServer() { GUILayout.Label("当前连接数量:"+ Network.connections.Length); if(GUILayout.Button("关闭聊天室",GUILayout.Width(thewidth))) { Network.Disconnect(); state = STATE.idle; } } //client状态 void OnClient() { if(GUILayout.Button("离开聊天室",GUILayout.Width(thewidth))) { Network.Disconnect(); state = STATE.idle; } } //共用的处理聊天的函数 void OnChat() { GUILayout.BeginHorizontal(); GUILayout.BeginVertical(); scrollPos = GUILayout.BeginScrollView(scrollPos,GUILayout.Width(thewidth-100),GUILayout.Height(300)); GUILayout.Label(textRecord); GUILayout.EndScrollView(); GUILayout.EndVertical(); GUILayout.BeginVertical(); GUILayout.Label("~聊天室成员~"); scrollPos_manlist = GUILayout.BeginScrollView(scrollPos_manlist,GUILayout.Width(100),GUILayout.Height(300)); string txt =""; foreach(var s in names) { txt += s +"\n"; } GUILayout.Label(txt); GUILayout.EndScrollView(); GUILayout.EndVertical(); GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(); textToSend = GUILayout.TextField(textToSend,GUILayout.Width(thewidth-50)); if(GUILayout.Button("发送",GUILayout.Width(50))) { networkView.RPC("SendText", RPCMode.All,myName+":"+textToSend); textToSend =""; } GUILayout.EndHorizontal(); } //服务器向客户端发送人员名单及聊天记录,供客户端更新显示 void Server_UpdateNameList() { string textToSend=""; foreach(var item in manList) { textToSend += item.Value+"|"; } networkView.RPC("RPC_GetNameList", RPCMode.All,textToSend); networkView.RPC("RPC_ChatRecordUpdate", RPCMode.All,textRecord); } //不同栏间的填充 void Format_CutOffLine() { GUILayout.Label("==============================================================="); } //标准间隔 void Format_Space() { GUILayout.Space(50); } //当客户端连接成功时向服务器发送的消息 [RPC] public void Server_Join(string client_name, NetworkMessageInfo info) { string id = info.sender.ToString(); Debug.Log("Joined:"+id); networkView.RPC("SendText", RPCMode.All,client_name+"加入了聊天室"); manList.Add(id,client_name); Server_UpdateNameList(); } //发送文本 [RPC] public void SendText(string content, NetworkMessageInfo info) { textRecord += content+"\n"; } //人员名单 [RPC] public void RPC_GetNameList(string content, NetworkMessageInfo info) { string[] ss = content.Split(‘|‘); names.Clear(); names.AddRange(ss); } //聊天记录 [RPC] public void RPC_ChatRecordUpdate(string content) { textRecord = content; } }
实例——碰碰乐
本例中僬侥实现的是一个小游戏,多个玩家在平台上格子控制一个球体,球体相互碰撞,球体被撞落下平台的玩家为输
新建一个Plane作为平台,调整摄像机视角,新建球体,创建物理材质并赋予sphere Collider,添加刚体组件和Network View组件
最后添加脚本,该代码主要让Network View监听刚体,并使其在玩家之间同步数据
using UnityEngine; using System.Collections; public class SphereSerializer : MonoBehaviour { private Rigidbody rigidbody; private NetworkView networkView; void Awake() { rigidbody = GetComponent<Rigidbody>(); networkView = GetComponent<NetworkView>(); //设置networkView的监听对象为刚体 networkView.observed = rigidbody; } }
再创建控游戏对象并命名Manager,为其添加脚本
using UnityEngine; using System.Collections; public enum BallGameState { lobby, idle, gaming, lose } public class C_16_2_3 : MonoBehaviour { public GameObject prefab_ball; private BallGameState state = BallGameState.lobby; private string IP = "10.234.41.123"; private int Port = 1000; private Rigidbody myBall; void OnGUI() { switch(state) { case BallGameState.lobby: OnLobby(); break; case BallGameState.idle: OnIdle(); break; case BallGameState.gaming: OnGaming(); break; case BallGameState.lose: OnLose(); break; } } //大厅状态逻辑 void OnLobby() { if(GUILayout.Button("建立服务器")) { NetworkConnectionError error = Network.InitializeServer(10,Port,false); if(error == NetworkConnectionError.NoError) state = BallGameState.idle; } GUILayout.Label("======================================"); GUILayout.BeginHorizontal(); GUILayout.Label("服务器IP地址:"); IP = GUILayout.TextField(IP); GUILayout.EndHorizontal(); if(GUILayout.Button("连接服务器")) { NetworkConnectionError error = Network.Connect(IP,Port); if(error == NetworkConnectionError.NoError) state = BallGameState.idle; } } //空闲状态逻辑 void OnIdle() { if(GUILayout.Button("Start!")) { StartGaming(); } } //游戏状态逻辑 void OnGaming() { //控制自己的球体 float x = Input.GetAxis("Horizontal"); float y = Input.GetAxis("Vertical"); myBall.AddForce(new Vector3(x,0,y)); //出界则失败 if(myBall.transform.position.y <-5) { Network.Destroy(myBall.GetComponent<NetworkView>().viewID); state = BallGameState.lose; } } //失败状态逻辑 void OnLose() { GUILayout.Label("You Lose!"); if(GUILayout.Button("Start Again!")) { StartGaming(); } } //开始游戏 void StartGaming() { state = BallGameState.gaming; GameObject obj = Network.Instantiate(prefab_ball,new Vector3(0,1,0),Quaternion.identity,0) as GameObject; myBall = obj.GetComponent<Rigidbody>(); } }
在Build Settings窗口打包本场景,在编辑器及设备上同时运行
Network View准许指定Transform、rigdbody等组件作为被观察和同步的对象,也准许指定自定义作为被观察和同步的对象,因为脚本继承自MonoBehavior,MonoBehavior继承子Behaviour,Behaviour继承自Component,在脚本中,我们需要实现OnSerializeNetworkView函数同步数据
将脚本上的脚本SphereSerializer替换为SphereSerializer2
using UnityEngine; using System.Collections; public class SphereSerializer2 : MonoBehaviour { private Rigidbody rigidbody; private Renderer renderer; private NetworkView networkView; bool firstSerialize = true; void Awake() { rigidbody = GetComponent<Rigidbody>(); renderer = GetComponent<Renderer>(); networkView = GetComponent<NetworkView>(); networkView.observed = this; } //同步数据 void OnSerializeNetworkView(BitStream stream, NetworkMessageInfo info) { if(firstSerialize) { firstSerialize = false; Vector3 color = Vector3.zero; if (stream.isWriting) { if(networkView.isMine) color = new Vector3(1,0,0); else color = new Vector3(0,1,0); stream.Serialize(ref color); } else { stream.Serialize(ref color); } renderer.material.color = new Color(color.x,color.y,color.z); } Vector3 pos= Vector3.zero; Quaternion rotation= Quaternion.identity; Vector3 velocity = Vector3.zero; if (stream.isWriting) { pos = rigidbody.position; stream.Serialize(ref pos); rotation = rigidbody.rotation; stream.Serialize(ref rotation); velocity = rigidbody.velocity; stream.Serialize(ref velocity); } else { stream.Serialize(ref pos); transform.position = pos; stream.Serialize(ref rotation); transform.rotation = rotation; stream.Serialize(ref velocity); rigidbody.velocity = velocity; } } }