您的位置:首页 > 移动开发 > Unity3D

PhotonServer MMO游戏开发

2017-10-28 11:57 225 查看

原文地址:blog.liujunliang.com.cn

上篇文章介绍的PhotonServer实现在MySQL登录验证请求本文继续上篇文章开发,通过PhotonServer来实现一个MMO Demo游戏(持续发送数据包)源码下载地址以下是我对网络游戏开发编程的总结:一、将需要在各个客户端一起显示的数据都发送到服务器端上(主角的移动、动作、技能特效等)二、在PhotonServer中,一个客户端对应一个ClientPeer对象(所以可以将一个主角的信息作为ClientPeer一个属性存储起来)三、在服务器端将连接进来的客户端有一个容器保存起来,待方便之后的逻辑处理(网络游戏的主角之间的逻辑部分在服务器端开发)接下来一步步介绍MMO Demo开发流程,通过这么一个小小的案列,相信以后对网络游戏开发有深刻体会(学习下本系列文章前面的内容)!
(一)、将主角的移动位置数据发送到服务器端主角的操作
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayControl : MonoBehaviour
{
[SerializeField]
private GameObject mLocalPlayer;

//上传位置信息请求
private PositionRequest mUpdatePositionRequest;

//创建主角的请求
private AddPlayerRequest mAddPlayerRequest;

//存储上一帧主角位置
private Vector3 mLastPosition = default(Vector3);

//计时器
private float mTimer = 0;
private float mTime = 0.2f;

private void Start()
{
//将本地主角的进行表示(颜色设置为红色)
mLocalPlayer.GetComponent<Renderer>().material.color = Color.red;

mUpdatePositionRequest = GameObject.Find("GameRequest").GetComponent<PositionRequest>();

mAddPlayerRequest = GameObject.Find("GameRequest").GetComponent<AddPlayerRequest>();
mAddPlayerRequest.OnOperationRequest();
}

void Update ()
{
PosControl();
//将主角数据按一定时间上传到服务器
// InvokeRepeating("SendPosition", 5.0f, 2.0f);//不晓得为啥我的延时方法无效 导致大量的发包堵塞了缓存区

mTimer += Time.deltaTime;
if (mTimer >= mTime)
{
mTimer = 0;
SendPosition();
}
}

void PosControl()
{
float horizontal = Input.GetAxis("Horizontal");
float vertical = Input.GetAxis("Vertical");

mLocalPlayer.transform.Translate(new Vector3(vertical, 0, horizontal) * Time.deltaTime * 3.0f);
}

//将主角位置信息发送到服务器
void SendPosition()
{
if (mLastPosition == default(Vector3) || Vector3.Distance(mLocalPlayer.transform.position, mLastPosition) >= 0.1f)
{
mLastPosition = mLocalPlayer.transform.position ;

mUpdatePositionRequest.playerPosition = mLocalPlayer.transform.position;
mUpdatePositionRequest.OnOperationRequest();//向服务器发送数据包请求
}
}
}


在一定时间段内,持续向服务器发送数据包请求
using System.Collections;
using System.Collections.Generic;
using ExitGames.Client.Photon;
using UnityEngine;
using System.Xml.Serialization;
using System.IO;

public class PositionRequest : BaseRequest
{
[HideInInspector]
public Vector3 playerPosition { get; set; }

public override void OnOperationRequest()
{
Collective.Vector3Pos vector3Pos = new Collective.Vector3Pos() { posX = playerPosition.x, posY = playerPosition.y, posZ = playerPosition.z };

string vector3PosStr;
using (StringWriter sw = new StringWriter())
{
XmlSerializer xs = new XmlSerializer(typeof(Collective.Vector3Pos));
xs.Serialize(sw, vector3Pos);

vector3PosStr = sw.ToString();
}

GameContext.GetInstance.peer.OpCustom(
(byte)this.operationMode,
new Dictionary<byte, object> { { (byte)Collective.ParameterMode.VECTOR3POS, vector3PosStr }},
true
);
}

public override void OnOperationResponse(OperationResponse operationResponse)
{
//将位置信息上传请求 无需做出响应
}

public override void Awake()
{
base.Awake();

this.operationMode = Collective.OperationMode.UPDATEPOS;
GameContext.GetInstance.AddRequest(this);
}
}


在服务器端接收请求,并将把位置信息更新存储起来
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Photon.SocketServer;
using System.Xml.Serialization;
using System.IO;

namespace MyGameServer.Request
{
class PositionRequest : BaseRequest
{
public PositionRequest()
{
this.operationMode = Collective.OperationMode.UPDATEPOS;
}

public override void OnOperationRequest(OperationRequest operationRequest, SendParameters sendParameters, ClientPeer peer)
{
MyGameServer.LOG.Info("获得位置数据");
//获取数据
Dictionary<byte, object> parameters = operationRequest.Parameters;
string vector3PosStr = (string)parameters.FirstOrDefault(q => q.Key == (byte)Collective.ParameterMode.VECTOR3POS).Value;

Collective.Vector3Pos vector3Pos;
//反序列化
vector3Pos = Collective.XMLTool.XMLDeserialze<Collective.Vector3Pos>(vector3PosStr);

MyGameServer.LOG.Info("主角位置:" + vector3Pos.posX + " ," + vector3Pos.posY + " ," + vector3Pos.posZ);
//设置主角位置信息
peer.mVector3Pos = vector3Pos;
}
}
}


(二)、主角登录后(在服务器端使用容器将客户端保存),此时发送一个请求,检测除主角外其他游戏客户端检测到在之前有其他游戏客户端连接进服务器将[b]回发给客户端创建响应:告诉客户端有之前有其他玩家登录到了游戏场景,需要在场景创建其他角色;(注:一个场景创建多个角色)[/b]并且将之前的游戏客户端上发送事件:告诉其他客户端,有个游戏主角加入了,需要在场景中创建该角色;(注:多个场景创建一个角色)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Photon.SocketServer;

namespace MyGameServer.Request
{
public class AddPlayerRequest : BaseRequest
{
public AddPlayerRequest()
{
this.operationMode = Collective.OperationMode.ADDPLAYER;
}

public override void OnOperationRequest(OperationRequest operationRequest, SendParameters sendParameters, ClientPeer peer)
{
/*********************将之前已经登录了的玩家查询出来,传给客户端*************************/
List<string> nameList = new List<string>();

//查询全部在线玩家
foreach (var tempPeer in MyGameServer.peerList)
{
if (!string.IsNullOrEmpty(tempPeer.mName) && tempPeer != peer)
{
//添加其他已经登入的玩家名
nameList.Add(tempPeer.mName);
}
}
MyGameServer.LOG.Info("需要添加的玩家数:" + nameList.Count);

//将玩家名字列表序列化
string nameListStr = Collective.XMLTool.XMLSerialize<List<string>>(nameList);

Dictionary<byte, object> parameter = new Dictionary<byte, object> { { (byte)Collective.ParameterMode.NAMELIST, nameListStr } };
OperationResponse response = new OperationResponse((byte)this.operationMode);
response.Parameters = parameter;

peer.SendOperationResponse(response, sendParameters);
/*************************************************************************************/

/************************将之前登录的玩家客户端上添加本Peer***************************/
foreach (var tempPeer in MyGameServer.peerList)
{
if (!string.IsNullOrEmpty(tempPeer.mName) && tempPeer != peer)
{
//其他已经上线了的客户端
EventData ed = new EventData((byte)Collective.EventModel.ADDPLAYER);
ed.Parameters = new Dictionary<byte, object> { { (byte)Collective.ParameterMode.PLAYNAME, peer.mName } };

tempPeer.SendEvent(ed, new SendParameters());
}
}
/************************************************************************************/
}
}
}


在客户端中接收响应,将创建游戏对象
using System.Collections;
using System.Collections.Generic;
using ExitGames.Client.Photon;
using UnityEngine;
using System.Linq;

public class AddPlayerRequest : BaseRequest
{
public override void Awake()
{
base.Awake();

this.operationMode = Collective.OperationMode.ADDPLAYER;
GameContext.GetInstance.AddRequest(this);
}

public override void OnOperationRequest()
{
//告诉服务器开始创建有一个客户端进来的
//在真正的游戏开发当中,一般会传用户名到服务器中,在登录的时候已经存储下来了
GameContext.GetInstance.peer.OpCustom((byte)this.operationMode, null, true);
}

public override void OnOperationResponse(OperationResponse operationResponse)
{
string nameListStr = (string)operationResponse.Parameters.FirstOrDefault(q => q.Key == (byte)Collective.ParameterMode.NAMELIST).Value;
//if (!string.IsNullOrEmpty(nameListStr))
{
//反序列化
List<string> nameList = Collective.XMLTool.XMLDeserialze<List<string>>(nameListStr);
foreach (var name in nameList)
{
if (!string.IsNullOrEmpty(name) && !string.Equals(name, GameContext.GetInstance.mLocalPlayerName))
{
//其他已经上线的玩家
//此时需要在场景中创建玩家
GameContext.GetInstance.CreatePlayer(name);
}
}
}
}
}


在其他游戏客户端接收到了事件,创建游戏角色
using ExitGames.Client.Photon;
using System.Linq;
using UnityEngine;

public class AddPlayerEvent : BaseEvent
{
public override void Awake()
{
base.Awake();

this.eventModel = Collective.EventModel.ADDPLAYER;
GameContext.GetInstance.AddEvent(this);
}

public override void OnEvent(EventData eventData)
{
Debug.Log("添加事件响应");

string name = (string)eventData.Parameters.FirstOrDefault(q => q.Key == (byte)Collective.ParameterMode.PLAYNAME).Value;

GameContext.GetInstance.CreatePlayer(name);
}
}


(三)、创建线程,实时更新各个客户端各个主角的位置信息在有了之前步骤二,能够在各个客户端创建已经登录加入场景的游戏角色后接下来就是实时更新游戏对象移动位置信息思路是在服务器端主类里创建开启一个线程,持续不断地往各个客户端发送全部角色的位置信息的[b]事件[/b]服务器线程的创建与事件的派发
using Photon.SocketServer;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MyGameServer.Thread
{
public class UpdatePlayersPosThread : BaseThread
{
public override void Start()
{
base.Start();
}

protected override void Run()
{
while (true)
{
//没0.2秒刷新位置数据
System.Threading.Thread.Sleep(200);

UpdatePlayerPos();
}
}

public override void Stop()
{
//终止线程
this.mThread.Abort();
}

private void UpdatePlayerPos()
{
//由于字典本身无法序列化 所以用一个对象来存储名字和位置信息
List<Collective.PlayerVector3Pos> playerPosList = new List<Collective.PlayerVector3Pos>();
//将其他在线的主角位置信息打包序列化
foreach (var tempPeer in MyGameServer.peerList)
{
if (!string.IsNullOrEmpty(tempPeer.mName) && tempPeer.mVector3Pos != null)
{
playerPosList.Add(new Collective.PlayerVector3Pos { name = tempPeer.mName, pos = tempPeer.mVector3Pos });
}
}

//序列化
string playerPosListStr = Collective.XMLTool.XMLSerialize<List<Collective.PlayerVector3Pos>>(playerPosList);

Dictionary<byte, object> parameters = new Dictionary<byte, object> { { (byte)Collective.ParameterMode.VECTOR3POSLIST, playerPosListStr } };
//向各个客户端发送位置数据
foreach (var tempPeer in MyGameServer.peerList)
{
if (!string.IsNullOrEmpty(tempPeer.mName))
{
EventData ed = new EventData((byte)Collective.EventModel.UPDATEPOS);
ed.Parameters = parameters;

tempPeer.SendEvent(ed, new SendParameters());
}
}
}
}
}


客户端处理事件响应
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ExitGames.Client.Photon;
using UnityEngine;

class UpdatePosEvent : BaseEvent
{
public override void Awake()
{
base.Awake();

this.eventModel = Collective.EventModel.UPDATEPOS;
GameContext.GetInstance.AddEvent(this);
}

public override void OnEvent(EventData eventData)
{
//取得数据
string playerPosListStr = (string)eventData.Parameters.FirstOrDefault(q => q.Key == (byte)Collective.ParameterMode.VECTOR3POSLIST).Value;

if (!string.IsNullOrEmpty(playerPosListStr))
{
//反序列化
List<Collective.PlayerVector3Pos> playerPosList = Collective.XMLTool.XMLDeserialze<List<Collective.PlayerVector3Pos>>(playerPosListStr);

//根据名字找到匹配的游戏对象 ,将服务器传过来的最新位置赋值上去
foreach (var playerPos in playerPosList)
{
if (!string.IsNullOrEmpty(playerPos.name))
{
GameObject go = GameContext.GetInstance.playerDic.FirstOrDefault(q => q.Key == playerPos.name).Value;
if (go)
{
if (playerPos.pos == null)
{
Debug.LogError("位置信息为空");
}
go.transform.position = new UnityEngine.Vector3(playerPos.pos.posX, playerPos.pos.posY, playerPos.pos.posZ);
}
}
}
}
}
}

会发现在游戏各个客户端可以实时更新角色位置信息!!


这系列文章的源码博主以全部上传,大家可以下载下来学习!!如果本系列文章对你有帮助!点击以下链接关注博文,更多内容持续更新!





原文地址: blog.liujunliang.com.cn

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息