赞
踩
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项,在弹出的对话框中选择要发布的平台,添加当前的场景后发布。
最后运行游戏,在客户端或者服务器端移动游戏主角时,可以看到服务器端和客户端的同步显示
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。