Unity3D 多人网络游戏-客户端于服务器的同步

Unity3D 多人网络游戏-客户端于服务器的同步

第一次发表,非原创,是参照《Unity4.x 从入门到精通》一书做的Demo,mario是我从别的项目上扒下来的

游戏运行效果:客户端和服务器端的同步(运行稍有延迟现象)


游戏场景自行建立,只需要能体现出来即可,如一个地面一个预设物体即可

 

1.      启用Unity3D新建一项目,在场景中新建两个空的GameObject对象,并分别命名为Connect和RobotControl,如图所示,Connect对象负责客户端与服务器端的脚本逻辑.在该场景中服务器也参与游戏的运行.RobotControl对象主要负责控制主角的状态.


2.      在Project视图下建立C#文件,并命名AuthoritativeNetworkServer

3.编辑AuthoritativeNetworkServer脚本文件,代码如下:

using UnityEngine;

usingSystem.Collections;

 

publicclass AuthoritativeNetworkServer : MonoBehaviour {

// Use this for initialization

//定义远程连接IP地址(这里为本地)

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 ("新建服务器")){

                    //初始化服务器端口

                    NetworkConnectionErrorerror=Network.InitializeServer(30,port);

                    Debug.Log(error);

           }

           //客户端是否连接服务器

           if (GUILayout.Button ("连接服务器")){

                    //连接至服务器

                    NetworkConnectionErrorerror=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();            

           }

}

}

3.      该脚本把客户端脚本与服务器脚本写至同一个类文件中,作为服务器还是客户端取决于单击的按钮.如果单击新建服务器按钮,那么该界面就作为服务器运行,以此类推

4.      拖动该脚本文件到Connect对象上,如图

 

5.      单击运行按钮,预览游戏


屏幕左上角出现了两个按钮,新建服务器已经连接服务器,单击新建服务器后提示创建服务器成功如图


6.      返回Unity编辑器中,在Project视图中新建C#脚本文件,并为其命名为PlayerCreat

7.      编辑该脚本,代码如下

usingUnityEngine;

usingSystem.Collections;

 

publicclass PlayerCreat : MonoBehaviour {

 

//新建目标对象

public Transform playerPrefab;

//新建集合,用于存储playerControl脚本组件

private ArrayList list=new ArrayList();

//服务器新建完成后运行

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;

           //添加PlayerControl组件至集合中

           list.Add(playerTrans.GetComponent("PlayerControl"));

           //调用RPC,函数名为SetPlayer

           playerObjNetworkview.RPC("SetPlayer",RPCMode.AllBuffered,player);

}

//玩家断开连接时调用

void OnPlayerDisconnected(NetworkPlayerplayer){

           Debug.Log ("Clean up afterplayer "+player);

           //遍历集合对象上的PlayerControl组件

           foreach (PlayerControl script inlist) {

                    if(player==script.ownerPlayer){

                             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);

}

//客户端断开连接时调用

voidOnDisconnectedFromServer(NetworkDisconnection info){

           Application.LoadLevel(Application.loadedLevel);

}

}

         当服务器初始化完成时会运行OnServerInitialized函数。游戏中通过调用RPC函数来运行服务器或客户端的SetPlayer函数,需要使用RPC函数时,需要在该函数的上方添加@RPC(javasciprt)或者[RPC]C#,并传递N额头workPlayer对象到该函数中

 

10.拖动该脚本到RobotControl上,如图

 

11.在Project视图中Assets文件夹下找到mario预设体,为该预设体添加NetworkView,Rigidbody组件,为了避免该预设体对象发生穿墙等错误结果,需要为该预设体添加Box Collider组件,如图

 

12在Project视图中Assets下创建C#脚本文件,并为其命名为PlayerControl  编辑其脚本

using UnityEngine;

using System.Collections;

 

public class PlayerControl : MonoBehaviour{

 

          //玩家对象

         publicNetworkPlayer ownerPlayer;

         //保存水平方向控制

         privatefloat clientHInput=0;

         //保存垂直方向控制

         privatefloat clientVInput = 0;

         //服务器水平方向控制

         privatefloat serverHInput=0;

         //服务器垂直方向控制

         privatefloat serverVInput=0;

 

         //游戏对象向前移动

         privateint Player_Up=0;

         //游戏对象向右移动

         privateint Player_Right=1;

         //游戏对象向后移动(相对于前)

         privateint Player_Down=2;

         //游戏对象向左移动

         privateint Player_Left=3;

         //游戏对象的旋转值

         privateint PlayerRotate;

         //游戏对象的位置

         privateVector3 playerTransform;

         //游戏对象的当前状态

         privateint playerState=0;

         voidAwake(){

                   //是否运行于服务器

                   if(Network.isClient) {

                            enabled=false;

                   }

         }

 

         voidUpdate(){

                   if(ownerPlayer != null & Network.player == ownerPlayer) {

                            //获取水平控制

                            floatcurrentHInput=Input.GetAxis ("Horizontal");

                            //获取垂直控制

                            floatcurrentVInput=Input.GetAxis("Vertical");

 

                            if(clientHInput!=currentHInput|| clientVInput!=currentVInput){

                                     clientHInput=currentHInput;

                                     clientVInput=currentVInput;

                                     if(Network.isServer){

                                               SendMovementInput(currentHInput,currentVInput);

                                     }elseif(Network.isClient){

                                               //只在服务器运行SendMovementInput函数

                                               networkView.RPC("SendMovementInput",RPCMode.Server,currentHInput,currentVInput);

                                     }

                            }

                   }

                   if(Network.isServer) {

                            //输入状态

                            if(serverVInput==1){

                                     setState(Player_Up);

                            }

                            elseif(serverVInput==-1){

                                     setState(Player_Down);

                            }

                            if(serverHInput==1){

                                     setState(Player_Right);

                            }

                            elseif(serverHInput==-1){

                                     setState(Player_Left);

                            }

                            if(serverHInput==0&& serverVInput==0){

                                     //没有按下反向键时候 播放默认动画

                                     animation.Play();

                                     networkView.RPC("PlayState",RPCMode.OthersBuffered,"walk");

                            }

                   }

         }

 

         voidsetState(int state){

                   PlayerRotate= (state - playerState) * 90;

                   //当按下方向键,播放walk动画

                   animation.Play("walk");

                   networkView.RPC("PlayState",RPCMode.OthersBuffered,"walk");

                   switch(state) {

                            //向前移动

                   case0:

                            playerTransform= Vector3.forward*Time.deltaTime;

                            break;

                   case1:

                            //向右移动

                            playerTransform=Vector3.right*Time.deltaTime;

                            break;

                   case2:

                            //向后移动

                            playerTransform=Vector3.back*Time.deltaTime;

                            break;

                   case3:

                            //向左移动

                            playerTransform=Vector3.left*Time.deltaTime;

                            break;

                   }

                   //移动游戏对象

                   transform.Translate(playerTransform*5,Space.World);

                   //旋转游戏对象

                   transform.Rotate(Vector3.up,PlayerRotate);

                   //存储游戏状态

                   playerState= state;

         }

         [RPC]

         voidPlayState(string state)

         {

                   animation.Play(state);

         }

         [RPC]

         //调用客户端(这里包括服务器)的setPlayer函数

         voidSetPlayer(NetworkPlayer player){

                   ownerPlayer= player;

                   if(player == Network.player) {

                            enabled=true;          

                   }

         }

         [RPC]

         voidSendMovementInput(float currentHInput,float currentVInput){

                   serverHInput= currentHInput;

                   serverVInput= currentVInput;

         }

         voidOnSerializeNetworkView(BitStream stream,NetworkMessageInfo info){

                   //发送状态数据

                   if(stream.isWriting) {

                            Vector3pos = rigidbody.position;

                            Quaternionrot = rigidbody.rotation;

                            Vector3velocity = rigidbody.velocity;

                            Vector3angularVelocity = rigidbody.angularVelocity;

                            stream.Serialize(ref pos);

                            stream.Serialize(ref velocity);

                            stream.Serialize(ref rot);

                                     stream.Serialize(ref angularVelocity);

                   }else {

                            //读取状态数据

                            Vector3pos=Vector3.zero;

                            Vector3velocity=Vector3.zero;

                            Quaternionrot=Quaternion.identity;

                            Vector3angularVelocity=Vector3.zero;

                            stream.Serialize(refpos);

                            stream.Serialize(refvelocity);

                            stream.Serialize(refrot);

                            stream.Serialize(refangularVelocity);

                   }

         }

}

OnSerializeNetworkView函数通过判断是否写入,以序列化主角的位置(谁拥有该游戏对象,谁就能操作isWring属性),如果客户端操控游戏对象时处于写入状态,服务器则处于读取状态,反之亦然

13.点击Play按钮,预览游戏如图

 

退出游戏预览模式。为了使服务器能够运行与后台,一次打开菜单中Edit—ProjectSettings—Player项,在Inspector视图中勾选Per-PlatformSettings面板中的RunInBackground项,如果发布WEB端游戏,也需要勾选 如图

 

14.打开菜单栏的File—BuildSettings项,在弹出的对话框中选择要发布的平台,添加当前的场景后发布。

 

 

最后运行游戏,在客户端或者服务器端移动游戏主角时,可以看到服务器端和客户端的同步显示


没有更多推荐了,返回首页