PhotonServer MMO游戏开发

原文地址: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;
        }
    }
}


(二)、主角登录后(在服务器端使用容器将客户端保存),此时发送一个请求,检测除主角外其他游戏客户端
检测到在之前有其他游戏客户端连接进服务器
回发给客户端创建响应:告诉客户端有之前有其他玩家登录到了游戏场景,需要在场景创建其他角色;(注:一个场景创建多个角色)
并且将之前的游戏客户端上发送事件:告诉其他客户端,有个游戏主角加入了,需要在场景中创建该角色;(注:多个场景创建一个角色)
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);
    }
}


(三)、创建线程,实时更新各个客户端各个主角的位置信息
在有了之前步骤二,能够在各个客户端创建已经登录加入场景的游戏角色后
接下来就是实时更新游戏对象移动位置信息
思路是在服务器端主类里创建开启一个线程,持续不断地往各个客户端发送全部角色的位置信息的事件
服务器线程的创建与事件的派发
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


  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值