unity网络编程学习

前言

最近在学习unity自身封装的网络功能,在游戏物体上挂上Network组件,使其成为网络中的一份子,就像这里所说的,Unity网络协议本身是一个比较高层的网络协议,他并不是独立存在的,而是与引擎的游戏对象结合在一起,所以在Unity中不存在单纯的“发送给服务器”或“发送给客户端的”网络消息,不同的游戏对象之间的通信是独立的,就好像每个游戏对象都有一个独立的通信管道。

在游戏中,对象经常发生变化,需要进行状态同步,状态同步的方向只能是isMine到非isMine。比如一个玩家角色对象hp属性,如果需要其他玩家能够看到,就可以使用状态同步来完成。当isMine的玩家角色的hp变化后,会自动更新这个属性给其他的主机上的同一个玩家角色对象。状态同步还支持保证和非保证模式。

要进行状态同步就需要RPC,在其他机子上对同一游戏物体进行一个远程函数调用,RPC一般用来通过网络通知一次性的游戏事件,RPC的传输方向非常自由,可以在一个主机向任何其他主机,包括自己,发送RPC调用。

在unity网络中用到的相关类和方法
RPC 可以传递的参数
int
float
string
NetworkPlayer
NetworkViewID
Vector3
Quaternion
BitStream可以传递的参数
void Serialize (bool value)
void Serialize (char value) - only one byte [0, 255]
void Serialize (short value)
void Serialize (int value)
void Serialize (float value, float maxDelta = 0.00001f);
void Serialize (Quaternion value, float maxDelta = 0.00001f);
void Serialize (Vector3 value, float maxDelta = 0.00001f);
void Serialize (NetworkPlayer value)
void Serialize (NetworkViewID viewID)
RPCMode:
Server - only server
Others - send to everyone except the sender
OthersBuffered - Send to everyone except sender and adds to the buffer
All - send to everyone
AllBuffered - Sends to everyone and adds to the buffer
Network:
Network.AllocateViewID - 自动生成一个可用的viewid
Network.CloseConnection - 关闭当前连接
Network.Connect - 连接到指定端口或者域名
Network.connections -当前网络的连接数
Network.connectionTesterIP - 测试连接者的IP连接到服务器
Network.connectionTesterPort - 测试连接者的端口到服务器 (Debug)
Network.Destroy - 销毁跟当前viewID有关的网络连接
Network.DestroyPlayerObjects - 销毁跟当前Viewid有关的玩家物体
Network.Disconnect - 关闭所有已经连接好的连接,关闭网络网络接口
Network.GetAveragePing - ping 指定连接的玩家
Network.GetLastPing - 获取最后ping通的玩家
Network.HavePublicAddress - 检查当前机器是否有一个公共IP地址
Network.incomingPassword - 设置服务器的连接密码
Network.InitializeSecurity - 设置服务器加密,自动加密处理
Network.InitializeServer - 初始化服务器
Network.Instantiate - 实例化一个已经存在的prefab
Network.isClient - 判断当前的运行是否为客户端
Network.isMessageQueueRunning - 是否取消RPC访问或者取消网络数据同步
Network.isServer - 判断当前的运行是否为服务器
Network.logLevel - 设置当前网络信息的日志信息等级(sys)
Network.macConnections - 设置服务器可以连接的最大数或者游戏玩家数目
如果设置成0表示只有已经一个连接存在,设置成-1表示最大的连接数与当前
连接数相等,如果是这样,如果玩家失去连接,则服务器的位置将会为玩家
继续保留下来
Network.mininumAllocatableViewIDs - 设置最小从服务器获取的viewid数量*
Network.natFacilitatorIP - 使用网络穿透的IP地址
Network.natFacilitatorPort - 设置网络穿透的端口号
Network.OnConnectedToServer - [client]当连接服务器成功时调用
Network.OnDisconnectedFromServer - [client&svr]当服务器断开连接时调用
Network.OnFailedToConnect - [client]当客户端连接失败时
Network.OnNetworkInstantiate - [client|svr]当网络实例化一个对象调用
Network.OnPlayerConnected - [svr]当有一个新的玩家连接到服务器时
Network.OnPlayerDisconnected - [svr]当有玩家离开游戏时
Network.OnSerializeNetworkView - [client|svr]通过network view同步变量*
[void OnSerializeNetworkView(BitStream stream, NetworkMessageInfo info)
Network.OnServerInitialized - [svr]当服务器初始化成功时
Network.peerType - 网络对等状态
Network.player - 得到当前的NetworkPlayer实例引用
Network.proxyIP - 代理服务器的IP地址
Network.proxyPassword - 代理服务器的密码
Network.proxyPort - 代理服务器的端口号
Network.RemoveRPCs - 从服务器那里移除所有跟当前玩家相关的RPC方法
Network.RemoveRPCsInGroup - 从服务器那里移除跟当前组有关RPC方法
Network.sendRate - 网络同步的发送频率
Network.SetLevelPrefix - 设置网络前辍,将应用到所有viewid
Network.SetReceivingEnabled - 设置指定玩家的分组信息是否可以接收
Network.SetSendingEnable - 设置指定玩家的分组信息是否可以发送
Network.TestConnection - 测试当前主机的连接状态*
Network.TestConnectionNAT - 强制测试当前主机的连接状态
Network.time - 得到当前网络的时间,单位秒
Network.useProxy - 使用代理服务器

NetworkView:
NetworkView.Find - 通过netview id来查找一个NetworkView
NetworkView.group - 当前NetworkView所在的组
NetworkView.isMine - 当前物体是否有NetworkView控制功能
NetworkView.observed - 当前NetworkView是否在观察中
NetworkView.owner - 当前NetworkView是归谁所拥有
NetworkView.RPC - 调用当前所有已经连接了服务器的RPC方法
NetworkView.SetScope - 设置NetworkView的关联NetworkPlayer信息
NetworkView.stateSynchronization - 网络数据同步发送安全性*
NetworkView.viewID - 当前NetworkView的NetworkViewID

NetworkViewID:
NetworkViewID.isMine - 是否是自己实例化的物体
NetworkViewID.operator!= - 如果两个NetworkView不相等
NetworkViewID.operator== - 如果两个NetworkView相等
NetworkViewID.owner(NetworkView) - 哪个NetworkPlayer拥有的NetworkViewID
NetworkViewID.ToString - 返回已经格式化好的NetworkViewID信息
NetworkViewID.unassigned - 表示当前networkview id无效

NetworkPlayer:
NetworkPlayer.externalIP - 获取当前NetworkPlayer对外开放的IP地址
NetworkPlayer.externalPort - 获取当前NetworkPlayer对开开放的端口号
NetworkPlayer.guid - 当前NetworkPlayer的GUID,当使用NAT punchthrough时
NetworkPlayer.ipAddress - 当前NetworkPlayer的IP地址
NetworkPlayer.port - 当前NetworkPlayer的端口号
NetworkPlayer.operator!= - 是否两个NetworkPlayer不相等
NetworkPlayer.operator== - 是否两个NetworkPlayer相等
NetworkPlayer.ToString - 返回当前NetworkPlayer的索引

HostData:
HostData.comment - 主机描述信息
HostData.connectedPlayers - 当前的玩家连接数
HostData.gameName - 当前主机对应的游戏名称
HostData.gameType - 当前主机对应的游戏类型
HostData.guid - NAT punchthrough时
HostData.ip - 主机的IP地址
HostData.passwordProtected - 主机是否有密码保护
HostData.playerLimit - 主机最大能承受的玩家数量
HostData.port - 当前主机的端口号
HostData.useNat - 当前主机是否已经有Nat穿透功能
MasterServer:
MasterServer.ClearHostList - 清除通过从MasterServer.PollHostList获取的主机列表
MasterServer.dedicatedServer - 当前机器是否有专有服务器
MasterServer.ipAddress - 主服务器的IP地址
MasterServer.OnFailedToConnectToMasterServer - [svr|client]当连接主服务器失败时回调
MasterServer.OnMasterServerEvent - 当主服务器有事件通知[MasterServerEvent]
MasterServer.PollHostList - 通过MasterServer.RequestHostList检查最新的主机列表
MasterServer.port - 主服务器的端口号
MasterServer.RegisterHost - 注册当前服务器到主服务器
MasterServer.RequestHostList - 人主服务器那里请求主机列表
MasterServer.UnregisterHost - 从主服务器那里取消注册当前服务器
MasterServer.updateRate - 设置主服务器的最小更新频率,如果为0,则主服务器不进行更新,
默认更新频率为60s


小练习
首先搭建一个场景,人物,在人物身上挂上Network,人物控制脚本等。
创建服务器(同时也是客户端),将脚本挂在主摄像机上
using UnityEngine;
using System.Collections;

public class AuthoritativeNetworkServer : MonoBehaviour {

	private string ip="127.0.0.1";
	private int port=10001;
	
void OnGUI(){
		
		
	switch(Network.peerType){
		
		case NetworkPeerType.Disconnected:
			StartCreat();
			
			break;
		case NetworkPeerType.Server:
			OnServer();
			
			break;
		case NetworkPeerType.Client:
			OnClient();
		
			break;
		case NetworkPeerType.Connecting:
			GUILayout.Label("连接中···");
			break;
		}
		
		
	}
	
	void StartCreat(){
		
			GUILayout.BeginVertical();
			if(GUILayout.Button("新建服务器")){
				
				NetworkConnectionError error=Network.InitializeServer(30,port);
				Debug.Log (error);
				
			}
			if(GUILayout.Button("连接服务器")){
				
				NetworkConnectionError error=Network.Connect(ip,port);
				Debug.Log(error);
				
			}
			GUILayout.EndVertical();
		
		
		}
	void OnServer(){
		GUILayout.Label(" 服务端创建成功,请等待连接");
        int length=Network.connections.Length;
		for(int i=0;i<length;i++){
			
			GUILayout.Label("客户端连接ip:"+Network.connections[i].ipAddress);
            GUILayout.Label("客户端连接端口:"+Network.connections[i].port);
			
		}
				
		if(GUILayout.Button("断开服务器")){ 
			Network.Disconnect();
				
		}
		
	}
	void OnClient(){
		
		GUILayout.Label("连接成功");
		if(GUILayout.Button("断开连接")){
		
			Network.Disconnect();
			
		}
		
		
	}
	
}
设置一个人物生成点,挂上 人物创建脚本,当创建服务器或者连接服务器成功时,生成一个游戏人物。可以在地面上通过鼠标点击进行行走,当客户端断开连接时,销毁对应的游戏对象。这里要注意的几点:
1 unity的网络机制是在联网时,在各个机器上创建游戏对象实例,所以要保证照相机发出的射线只能控制当前游戏对象,不能控制其他机器的游戏对象,我的做法是将当前player和network.player(本地实例)比较,相同说明就是当前角色,可以进行射线操作
	//服务端新建完成后运行
	void OnServerInitialized(){
		
		MovePlayer(Network.player);
		
	}
    //玩家连接后运行
	void OnPlayerConnected(NetworkPlayer player){
		
		MovePlayer(player);
       	
	}
	void MovePlayer(NetworkPlayer player){
	//获取玩家id值
	int playerID=int.Parse(player.ToString());
	//初始化目标对象
        Transform playerTrans=(Transform)Network.Instantiate(playerPrefab,transform.position, transform.rotation, playerID);	
        NetworkView playerObjNetworkview=playerTrans.networkView;
        //添加HeroController组件至集合中 用于断开连接时删除对象
        list.Add(playerTrans.GetComponent("HeroController"));
        //调用RPC,函数名为SetPlayer
	playerObjNetworkview.RPC("SetPlayer",RPCMode.AllBuffered,player);
	}
看看setPlayer方法写了什么:
    //调用客户端(这里包括服务端)的SetPlayer函数
    [RPC]
    void SetPlayer(NetworkPlayer player)
    {
         ownerPlayer = Network.player;
        if (player == Network.player)
        {
            //当前的客户端能对其操作,否则不能
            canDo = true;
        }
    }
2 角色的状态需要同步,比如人物的座标,要用到onSerializeNetworkView方法,用来在一个由网络视图监控的脚本中自定义变量同步。它自动决定被序列化的变量是否应该发送或接收,取决于谁拥有该物体,即所有者发送,其他所有人接收。
    void OnSerializeNetworkView(BitStream stream, NetworkMessageInfo info)
    {
        Vector3 pos = Vector3.zero;
        if (stream.isWriting)
        {
            pos = this.transform.position;
            stream.Serialize(ref pos);
        }
        else
        {
            stream.Serialize(ref pos);
            this.transform.position = pos;
        }
    }
这样人物的位置信息就会传达到各个客户端了,但是动画等状态不能通过这个方法传递,仍需要调用RPC函数
	//玩家断开连接时调用
    void OnPlayerDisconnected(NetworkPlayer player) {
        //遍历集合对象上的PlayerControl组件
	    foreach(PlayerControl script in list){
		    if(player==script.ownerPlayer){
                //移除所有属于这个id的rpc函数
			    Network.RemoveRPCs(script.gameObject.networkView.viewID);
                //销毁物体
			    Network.Destroy(script.gameObject);
			    list.Remove(script);
			    break;
		    }
	    }
	    int playerNumber= int.Parse(player+"");
	    Network.RemoveRPCs(Network.player, playerNumber);
	    Network.RemoveRPCs(player);
	    Network.DestroyPlayerObjects(player);
    }

    //客户端断开连接时调用
    void OnDisconnectedFromServer(NetworkDisconnection info) {
	
	    Application.LoadLevel(Application.loadedLevel);	
    }

最后大概实现了,创建服务器,生成玩家,连接服务器,生成玩家,点击地面对玩家进行操作,玩家位置相互同步。





發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章