Photon Server(六)服务器连接客户端总结(源码一览)

服务器端代码

MyGameServer项目下的代码

这里我贴上前面文章的所有代码,仅供大家学习与参考首先是服务器端的两个项目MyGanemServerCommon一览,

Paste_Image.png

Paste_Image.png

首先贴上MyGameServer项目类里面的所有代码
首先是Handler文件夹下的
BaseHandler

using Common;
using Photon.SocketServer;

namespace MyGameServer.Handler
{
public abstract class BaseHandler
    {
        public OperationCode opCode;
        //处理请求的方法
        public abstract void OnOperationRequest(OperationRequest operationRequest, SendParameters sendParameters, ClientPeer peer);
    }
}

DefaultHandler

using System;
using Photon.SocketServer;

namespace MyGameServer.Handler
{
    class DefaultHandler : BaseHandler
    {
        public DefaultHandler()
        {
            opCode = Common.OperationCode.Default;
        }
        public override void OnOperationRequest(OperationRequest operationRequest, SendParameters sendParameters, ClientPeer peer)
        {
            throw new NotImplementedException();
        }
    }
}

LoginHandler

using Photon.SocketServer;
using Common;
using Common.Tools;
using MyGameServer.Manager;

namespace MyGameServer.Handler
{
    //处理登陆请求的类
    class LoginHandler : BaseHandler
    {
        public LoginHandler()
        {
            opCode=OperationCode.Login;//赋值OperationCode为Login,表示处理的是那个请求
        }
        //登陆请求的处理的代码
        public override void OnOperationRequest(OperationRequest operationRequest, SendParameters sendParameters, ClientPeer peer)
        {
            //根据发送过来的请求获得用户名和密码
            string username = DictTool.GetValue<byte, object>(operationRequest.Parameters, (byte)ParameterCode.Username)as string;
            string password= DictTool.GetValue<byte, object>(operationRequest.Parameters, (byte)ParameterCode.Password) as string;
            //连接数据库进行校验
            UserManager manager = new UserManager();
            bool isSuccess = manager.VerifyUser(username, password);
            OperationResponse response = new OperationResponse(operationRequest.OperationCode);
            //如果验证成功,把成功的结果利用response.ReturnCode返回成功给客户端
            if (isSuccess)
            {
                response.ReturnCode = (short)Common.ReturnCode.Success;
                peer.username = username;//保持当前用户的用户名让ClenPeer管理起来
            }
            else//否则返回失败给客户端
            {
                response.ReturnCode = (short)Common.ReturnCode.Failed;
            }
            //把上面的回应给客户端
            peer.SendOperationResponse(response, sendParameters);

        }
    }
}

RegisterHandler

using Photon.SocketServer;
using Common;
using Common.Tools;
using MyGameServer.Manager;
using MyGameServer.Model;

namespace MyGameServer.Handler
{
    //处理注册请求的类
    class RegisterHandler : BaseHandler
    {
        public RegisterHandler()
        {
            opCode = OperationCode.Register;

        }
        //注册请求的处理的代码
        public override void OnOperationRequest(OperationRequest operationRequest, SendParameters sendParameters, ClientPeer peer)
        {
            string username = DictTool.GetValue<byte, object>(operationRequest.Parameters, (byte)ParameterCode.Username) as string;
            string password = DictTool.GetValue<byte, object>(operationRequest.Parameters, (byte)ParameterCode.Password) as string;
            UserManager manager = new UserManager();
            User user = manager.GetByUsername(username);//根据username查询数据
            OperationResponse responser = new OperationResponse(operationRequest.OperationCode);
            //如果没有查询到代表这个用户没被注册过可用
            if (user == null)
            {
                //添加输入的用户和密码进数据库
                user = new User() { Username = username, Password = password };
                manager.Add(user);
                responser.ReturnCode = (short) ReturnCode.Success;//返回成功

            }
            else//否者这个用户被注册了
            {
                responser.ReturnCode = (short)ReturnCode.Failed;//返回失败
            }
            // 把上面的结果给客户端
            peer.SendOperationResponse(responser,sendParameters);
        }
    }
}

SyncPlayerHandler

using System.Collections.Generic;
using Photon.SocketServer;
using Common;
using System.Xml.Serialization;
using System.IO;

namespace MyGameServer.Handler
{
    class SyncPlayerHandler : BaseHandler
    {
        public SyncPlayerHandler()
        {
            opCode = OperationCode.SyncPlayer;
        }
        //获取其他客户端相对应的用户名请求的处理代码
        public override void OnOperationRequest(OperationRequest operationRequest, SendParameters sendParameters, ClientPeer peer)
        {
            //取得所有已经登陆(在线玩家)的用户名
            List<string> usernameList = new List<string>();
            
            foreach (ClientPeer tempPeer in MyGameServer.Instance.peerlist)
            {
              //string.IsNullOrEmpty(tempPeer.username);//如果用户名为空表示没有登陆
              //如果连接过来的客户端已经登陆了有用户名了并且这个客户端不是当前的客户端
                if (string.IsNullOrEmpty(tempPeer.username )== false&&tempPeer!=peer)
                {
                    //把这些客户端的Usernam添加到集合里面
                    usernameList.Add(tempPeer.username);
                }
            }
            //通过xml序列化进行数据传输,传输给客户端
            StringWriter sw = new StringWriter();
            XmlSerializer serlizer = new XmlSerializer(typeof(List<string>));
            serlizer.Serialize(sw,usernameList);//进行序列化,参数一  输出流,参数二 表示序列化的对象
            sw.Close();//完成后关闭这个流
            string usernameListString = sw.ToString();

            //给客户端响应
            Dictionary<byte, object> data = new Dictionary<byte, object>();
            data.Add((byte)ParameterCode.UsernameList, usernameListString);
            OperationResponse response = new OperationResponse(operationRequest.OperationCode);
            response.Parameters = data;
            peer.SendOperationResponse(response, sendParameters);

            //告诉其他客户端有新的客户端加入
            foreach (ClientPeer tempPeer in MyGameServer.Instance.peerlist)
            {
                if (string.IsNullOrEmpty(tempPeer.username)==false&&tempPeer!=peer)
                {
                    EventData ed = new EventData((byte)EventCode.NewPlayer);
                    Dictionary<byte, object> data2 = new Dictionary<byte, object>();
                    data2.Add((byte)ParameterCode.Username, peer.username);//把新进来的用户名传递给其他客户端
                    ed.Parameters = data2;
                    tempPeer.SendEvent(ed,sendParameters);//发送事件
                }
            }
        }
    }
}

SyncPositionHandler

using Photon.SocketServer;
using Common;
using Common.Tools;

namespace MyGameServer.Handler
{
    class SyncPositionHandler : BaseHandler

    {
        public SyncPositionHandler()
        {
            opCode = OperationCode.SyncPosition;
        }
        //获取客户端位置请求的处理的代码
        public override void OnOperationRequest(OperationRequest operationRequest, SendParameters sendParameters, ClientPeer peer)
        {
            //接收位置并保持起来
            float x = (float)DictTool.GetValue<byte, object>(operationRequest.Parameters,(byte)ParameterCode.x);
            float y = (float)DictTool.GetValue<byte, object>(operationRequest.Parameters, (byte)ParameterCode.y);
            float z = (float)DictTool.GetValue<byte, object>(operationRequest.Parameters, (byte)ParameterCode.z);

            peer.x = x;peer.y = y;peer.z = z;//把位置数据传递给Clientpeer保存管理起来

            MyGameServer.log.Info(x + "--" + y+"--"+z);//输出测试
        }
    }
}

然后就是Manager文件夹下的类了
IUserManager

using System.Collections.Generic;
using MyGameServer.Model;

namespace MyGameServer.Manager
{
    interface IUserManager
    {
        void Add(User ser);
        void Update(User user);//更新数据
        void Remove(User user); //删除数据
        User GetById(int id); //根据ID获取数据
        User GetByUsername(string username); //根据username获取数据
        ICollection<User> GetAllUsers();  //获取所有数据
        bool VerifyUser(string username, string password);//验证用户密码
    }
}

UserManager

using System.Collections.Generic;
using MyGameServer.Model;
using NHibernate;
using NHibernate.Criterion;

namespace MyGameServer.Manager
{
    class UserManager : IUserManager
    {
        
        public void Add(User user)
        {
            //也可使用成一个事务
            using (ISession session = NhibernateHelper.OpenSession())
            {
                using (ITransaction transaction = session.BeginTransaction())//事务的开始
                {
                    //进行操作
                    session.Save(user);
                    transaction.Commit();//事物的提交
                }
            }
        }
       
        public ICollection<User> GetAllUsers()
        {
            using (ISession session = NhibernateHelper.OpenSession())
            {
                //Restrictions.Eq()表示添加查询条件
                // criteria.UniqueResult<User>();得到唯一的结果,返回的是User对象
                IList<User> users = session.CreateCriteria(typeof(User)).List<User>();


                return users;
            }
        }

        public User GetById(int id)//查询条件不会更改数据所以不需要使用事务
        {
            using (ISession session = NhibernateHelper.OpenSession())
            {
                    //进行操作
                    User user = session.Get<User>(id);//删除数据
                    return user;  
            }
        }
       
        public User GetByUsername(string username)
        {
            using (ISession session = NhibernateHelper.OpenSession())
            {
                //Restrictions.Eq()表示添加查询条件
                // criteria.UniqueResult<User>();得到唯一的结果,返回的是User对象
                User user = session.CreateCriteria(typeof(User)).Add(Restrictions.Eq("Username", username)).UniqueResult<User>();//创建一个配置文件
                return user;
            }
           
        }
       
        public void Remove(User user)
        {
            using (ISession session = NhibernateHelper.OpenSession())
            {
                using (ITransaction transaction = session.BeginTransaction())//事务的开始
                {
                    //进行操作
                    session.Delete(user);//删除数据
                    transaction.Commit();//事物的提交
                }
            }
        }
        
        public void Update(User  user)
        {
            using (ISession session = NhibernateHelper.OpenSession())
            {
                using (ITransaction transaction = session.BeginTransaction())//事务的开始
                {
                    //进行操作
                    session.Update(user);//更新数据
                    transaction.Commit();//事物的提交
                }
            }
        }

        public bool VerifyUser(string username, string password)
        {
            using (ISession session = NhibernateHelper.OpenSession())
            {
                IList<User> users = session.CreateCriteria(typeof(User)).List<User>();
                User user = session.CreateCriteria(typeof(User))
                          .Add(Restrictions.Eq("Username", username))
                          .Add(Restrictions.Eq("Password", password))
                          .UniqueResult<User>();
                if (user == null) return false;
                return true ;
            }
        }
    }
}

然后就是Mappings文件夹下的配置文件了
User.hbm.xml

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
                   assembly="MyGameServer"
                   namespace="MyGameServer.Model">
  <!--assembly:模型所在的程序集-->
  <!--namespace:表示User这个类在那个命名空间下的那个文件内-->
  <class name="User" table="user">
    <!--表示User这个类跟数据库里面的user表对应-->
    <id name="Id" column="id" type="Int32">
      <!--id name配置User类里面主键的映射,column表示跟数据库中哪个主键对应,type类型-->
      <generator class="native"> </generator>
      <!--generator表示一个生成器,class指定generator的类型,native表示使用数据库自带的生成器-->
    </id>
    <property name="Username" column="UserName" type="String"> </property>
    <property name="Password" column="PassWord" type="String"> </property>
    <property name="Registerdate" column="registerdate" type="Date"></property>
  </class>
</hibernate-mapping>

Model文件夹下的
User

using System;
namespace MyGameServer.Model
{
  public   class User
    {
        public virtual int Id { get; set; }
        public virtual string  Username { get; set; }//用户名
        public virtual string Password { get; set; }//密码
        public virtual DateTime Registerdate { get; set; }//s注册时间
    }
}

Threads文件夹下的多线程开启和使用
SyncPositionThread

using Photon.SocketServer;
namespace MyGameServer.Threads
{
    class SyncPositionThread
    {
        private Thread t;

        //启动线程的方法
        public void Run()
        {
            t = new Thread(UpdataPosition);//UpdataPosition表示线程要启动的方法
            t.IsBackground = true;//后台运行
            t.Start();//启动线程
        }

        private void UpdataPosition()
        {
            Thread.Sleep(5000);//开始的时候休息5秒开始同步
            while (true)//死循环
            {
                Thread.Sleep(100);//没隔0.1秒同步一次位置信息
                //进行同步
                SendPosition();

            }
        }
        //把所有客户端的位置信息发送到各个客户端
        //封装位置信息,封装到字典里,然后利用Xml序列化去发送
        private void SendPosition()
        {
            //装载PlayerData里面的信息
            List<PlayerData> playerDatraList = new List<PlayerData>();
            foreach (ClientPeer peer in MyGameServer.Instance.peerlist)//遍历所有客户段
            {
                if (string.IsNullOrEmpty(peer.username)==false)//取得当前已经登陆的客户端
                {
                    PlayerData playerdata = new PlayerData();
                    playerdata.Username = peer.username;//设置playerdata里面的username
                    playerdata.pos = new Vector3Data() { x = peer.x, y = peer.y, z = peer.z };//设置playerdata里面的Position
                    playerDatraList.Add(playerdata);//把playerdata放入集合
                }
            }
            //进行Xml序列化成String
            StringWriter sw = new StringWriter();
            XmlSerializer serializer = new XmlSerializer(typeof(List<PlayerData>));
            serializer.Serialize(sw,playerDatraList);
            sw.Close();
            string playerDataListString = sw.ToString();


            Dictionary<byte, object> data = new Dictionary<byte, object>();
            data.Add((byte)ParameterCode.PlayerDataList,playerDataListString);//把所有的playerDataListString装载进字典里面
            //把Xml序列化的信息装在字典里发送给各个客户端
            foreach (ClientPeer peer in MyGameServer.Instance.peerlist)
            {
                if (string.IsNullOrEmpty(peer.username) == false)
                {
                    EventData ed = new EventData((byte)EventCode.SyncPosition);
                    ed.Parameters = data;
                    peer.SendEvent(ed,new SendParameters());

                }
            }

        }
        //关闭线程
        public  void Stop()
        {
            t.Abort();//终止线程
        }
    }
}

然后就是单独的类ClientPeer

using Photon.SocketServer;
using PhotonHostRuntimeInterfaces;
using MyGameServer.Handler;
using Common.Tools;
using Common;

namespace MyGameServer
{
    //管理跟客户端的链接的
   public  class ClientPeer : Photon.SocketServer.ClientPeer
    {
        public float x, y, z;
        public string username;

        public ClientPeer(InitRequest initRequest) : base(initRequest)
        {


        }
        //处理客户端断开连接的后续工作
        protected override void OnDisconnect(DisconnectReason reasonCode, string reasonDetail)
        {
            MyGameServer.Instance.peerlist.Remove(this);//断开连接的时候List里面移除当前的ClientPeer客户端
        }
        //处理客户端的请求
        protected override void OnOperationRequest(OperationRequest operationRequest, SendParameters sendParameters)
        {
            //OperationRequest封装了请求的信息
            //SendParameters 参数,传递的数据


            //通过客户端的OperationCode从HandlerDict里面获取到了需要的Hander
            BaseHandler handler = DictTool.GetValue<OperationCode, BaseHandler>(MyGameServer.Instance.HandlerDict,(OperationCode)operationRequest.OperationCode);
    
           //如果找到了需要的hander就调用我们hander里面处理请求的方法
            if (handler != null)
            {
                handler.OnOperationRequest(operationRequest, sendParameters, this);

            }
            else//否则我们就使用默认的hander
            {
                BaseHandler defaultHandler = DictTool.GetValue<OperationCode, BaseHandler>(MyGameServer.Instance.HandlerDict, OperationCode.Default);
                defaultHandler.OnOperationRequest(operationRequest,sendParameters,this);
            }


            //通过operationRequest获取到opCode请求,在客户端发起请求的时候我们设置了opCode为1
            //因为客户端可能发起很多次请求,我们服务器要对每一次请求做相应的回应,所以需要相对应的opCode
            //switch (operationRequest.OperationCode)
            //{
            //    case 1:
            //        MyGameServer.log.Info("收到一个客户端的请求");

            //    //得到客户端的数据并打印到服务器的日志里面
            //    Dictionary<byte, object> data = operationRequest.Parameters;
            //    object intValue;
            //    data.TryGetValue(1, out intValue);
            //    object StringValue;
            //    data.TryGetValue(2, out StringValue);
            //    MyGameServer.log.Info("从客户端得到数据:" + intValue.ToString() + StringValue.ToString());

            //    //从服务器发送数据给客户端
            //    OperationResponse opResponse = new OperationResponse(1);//给其指定一个opCode,区分不同的请求的响应

            //    Dictionary<byte, object> data2 = new Dictionary<byte, object>();
            //    data2.Add(1, 200);
            //    data2.Add(2, "谢谢,我很好!");
            //    opResponse.SetParameters(data2);

            //    //这里接受到请求后需要给客户端一个回应
            //    SendOperationResponse(opResponse, sendParameters);//通过这个方法给客户端一个响应

            //    //客户端没有发送请求给服务器,服务器直接发送数据给客户端
            //    EventData ed = new EventData(1);//客户端有opCode,那么服务器之间发送就需要eventCode,这我们也设置eventcode为1
            //    ed.Parameters = data2;
            //    SendEvent(ed, new SendParameters());

            //    break;
            //    default:
            //        break;
            //}
        }
    }
}

NHibernate的配置文件hibernate.cfg.xml

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
  <session-factory>
    <property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>
    <property name="dialect">NHibernate.Dialect.MySQL5Dialect</property>
    <!--用来配置使用什么版本的数据库-->
    <property name="connection.driver_class">NHibernate.Driver.MySqlDataDriver</property>
    <!--用来配置使用什么数据库-->
    <property name="connection.connection_string">Server=localhost;Database=mygamedb;User ID=root;Password=root;</property>
    <!--配置MySQL的IP地址数据库名用户名和密码-->

    <property name="show_sql">true</property>
  </session-factory>
</hibernate-configuration>

log4net的配置文件log4net.config

<?xml version="1.0" encoding="utf-8" ?>
<log4net debug="false" update="Overwrite">

  <appender name="RollingFileAppender" type="log4net.Appender.RollingFileAppender">
    <file type="log4net.Util.PatternString" value="%property{Photon:ApplicationLogPath}\\MyGame.Server.log" />
    <appendToFile value="true" />
    <maximumFileSize value="5000KB" />
    <maxSizeRollBackups value="2" />
    <layout type="log4net.Layout.PatternLayout">
      <conversionPattern value="%d [%t] %-5p %c - %m%n" />
    </layout>
  </appender>

  <appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender">
    <layout type="log4net.Layout.PatternLayout">
      <param name="ConversionPattern" value="%d [%t] %-5p %c - %m%n" />
    </layout>
    <filter type="log4net.Filter.LevelRangeFilter">
      <levelMin value="DEBUG" />
      <levelMax value="FATAL" />
    </filter>
  </appender>

  <!-- logger -->
  <root>
    <level value="INFO" />
    <!--<appender-ref ref="ConsoleAppender" />-->
    <appender-ref ref="RollingFileAppender" />
  </root>

  <logger name="OperationData">
    <level value="INFO" />
  </logger>

</log4net>

MyGameServer类

using System.Collections.Generic;
using Photon.SocketServer;
using ExitGames.Logging;
using System.IO;
using ExitGames.Logging.Log4Net;
using log4net.Config;
using Common;
using MyGameServer.Handler;
using MyGameServer.Threads;

namespace MyGameServer
{
    //所有的Server端 主类都要继承自applicationbase
    public class MyGameServer : ApplicationBase
    {
        public  static readonly ILogger log = LogManager.GetCurrentClassLogger();//定义一个Log对象

        public static MyGameServer Instance { get; private set; }


        //存储所有的Handler
        public Dictionary<OperationCode, BaseHandler> HandlerDict = new Dictionary<OperationCode, BaseHandler>();

        //存放所有的Client客户端
        public List<ClientPeer> peerlist = new List<ClientPeer>();//通过这个集合可以访问到所有客户端的Peer,从而向任何一个客户端发送数据

        private SyncPositionThread syncPositinThread = new SyncPositionThread();


        //当一个客户端请求连接的时候,服务器端就会调用这个方法
        //我们使用peerbase,表示和一个客户端的链接,然后photon就会把这些链接管理起来
        protected override PeerBase CreatePeer(InitRequest initRequest)
        {
            log.Info("一个客户端连接进来了!");
             ClientPeer peer = new ClientPeer(initRequest);//每链接一个客户端过来我们就把这个客户端存储起来添加到List里面
            peerlist.Add(peer);
            return peer;
        }
        //初始化(当整个服务器启动起来的时候调用这个初始化)
        protected override void Setup()
        {
            Instance = this;

            //日志的初始化(定义配置文件log4net位置)

            //Path.Combine  表示连接目录和文件名,可以屏蔽平台的差异
            // Photon: ApplicationLogPath 就是配置文件里面路径定义的属性
            //this.ApplicationPath 表示可以获取photon的根目录,就是Photon-OnPremise-Server-SDK_v4-0-29-11263\deploy这个目录
            //这一步是设置日志输出的文档文件的位置,这里我们把文档放在Photon-OnPremise-Server-SDK_v4-0-29-11263\deploy\bin_Win64\log里面
            log4net.GlobalContext.Properties["Photon:ApplicationLogPath"] = Path.Combine(Path.Combine(Path.Combine(this.ApplicationRootPath,"bin_win64")),"log");
            //this.BinaryPath表示可以获取的部署目录就是目录Photon-OnPremise-Server-SDK_v4-0-29-11263\deploy\MyGameServer\bin
            FileInfo configFileInfo = new FileInfo(Path.Combine(this.BinaryPath,"log4net.config"));//告诉log4net日志的配置文件的位置
            //如果这个配置文件存在
            if (configFileInfo.Exists)
            {
                LogManager.SetLoggerFactory(Log4NetLoggerFactory.Instance);//设置photon我们使用哪个日志插件
                XmlConfigurator.ConfigureAndWatch(configFileInfo);//让log4net这个插件读取配置文件
            }



            log.Info("Setup Completed!");//最后利用log对象就可以输出了

            InitHandler();
            syncPositinThread.Run();
        }

        //用来初始化Handler
        public void InitHandler()
        {
            LoginHandler loginHandler = new LoginHandler();
            HandlerDict.Add(loginHandler.opCode,loginHandler);
            DefaultHandler defaultHandler = new DefaultHandler();
            HandlerDict.Add(defaultHandler.opCode,defaultHandler);

            RegisterHandler registerHandler = new RegisterHandler();
            HandlerDict.Add(registerHandler.opCode, registerHandler);

            SyncPositionHandler syncPositionHandler = new SyncPositionHandler();
            HandlerDict.Add(syncPositionHandler.opCode, syncPositionHandler);

            SyncPlayerHandler syncPlayerHandler = new SyncPlayerHandler();
            HandlerDict.Add(syncPlayerHandler.opCode,syncPlayerHandler);
        }

        //server端关闭的时候
        protected override void TearDown()
        {
            syncPositinThread.Stop();
            log.Info("关闭了服务器");
        }
    }
}

NhibernateHelper

using NHibernate;
using NHibernate.Cfg;

namespace MyGameServer
{
    class NhibernateHelper
    {
        private static ISessionFactory _sessionFactory;
        private static ISessionFactory SessionFactory
        {
            get
            {
                if (_sessionFactory==null)
                {
                    var configuration = new Configuration();
                    configuration.Configure();
                    configuration.AddAssembly("MyGameServer");

                    _sessionFactory = configuration.BuildSessionFactory();
                }
                return _sessionFactory;
            }
        }
        public static ISession OpenSession()
        {
            return SessionFactory.OpenSession();
        }
    }
}

好了,MyGameServer项目里面的代码就这些.

Common项目下的代码

下面是Common项目里面的代码了
首先是Tools文件夹下工具类
DictTool

using System.Collections.Generic;
namespace Common.Tools
{
   public  class DictTool
    {
        public static Value GetValue<Key, Value>(Dictionary<Key, Value> dicr, Key key)
        {
            Value value;
           
            bool isSuccess = dicr.TryGetValue(key, out value);
            if (isSuccess)
            {
                return value;
            }
            else
            {
                return default(Value);
            }
        }
    }
}

EventCode

namespace Common
{
    public  enum EventCode :byte//区分服务器向客户端发送的事件的类型
    {
      NewPlayer,
      SyncPosition
    }
}

OperationCode

namespace Common
{
    public enum OperationCode:byte//区分请求和响应的类型
    {
        Login,
        Register,
        Default,
        SyncPosition,
        SyncPlayer
    }
}

ParameterCode

namespace Common
{
    public enum ParameterCode:byte//区分传送数据时候参数的类型
    {
        Username,
        Password,
        Position,
        x,y,z,
        UsernameList,
        PlayerDataList
    }
}

PlayerData

using System;

namespace Common
{
    [Serializable]
   public  class PlayerData
    {
        public Vector3Data pos { get; set; }
        public  string Username { get; set; }
    }
}

ReturnCode

namespace Common
{
    public  enum ReturnCode:short//区分请求返回值,成功或者失败
    {
        Success,
        Failed
    }
}

Vector3Data

using System;

namespace Common
{
    [Serializable]
   public  class Vector3Data
    {
        public float x { get; set; }
        public float y { get; set; }
        public float z { get; set; }
    }
}

服务器端的代码就这些了,下面是客户端的

客户端代码

场景一登陆注册界面的代码

首先是PhotonEngine脚本下的代码

using System.Collections.Generic;
using UnityEngine;
using ExitGames.Client.Photon;
using Common;
using Common.Tools;

public class PhotonEngine : MonoBehaviour,IPhotonPeerListener {

    public  static PhotonEngine Instance;
    public static PhotonPeer Peer//让外界可以访问我们的PhotonPeer
    {
        get
          {
            return peer;
           }
        }
    //创建一个字典,根据OperationCode去找到所有相对应的Request对象
    private Dictionary<OperationCode, Request> RequestDict = new Dictionary<OperationCode, Request>();

    private Dictionary<EventCode, BaseEvent> EventDict = new Dictionary<EventCode, BaseEvent>();

    public static string username;//保存当前用户的用户名

    private  static PhotonPeer peer;
    private void Awake()
    {
        if (Instance == null)
        {
            Instance = this;
            DontDestroyOnLoad(this.gameObject);
        }else if (Instance != this)
        {
            Destroy(this.gameObject);return;
        }
    }

    // Use this for initialization
    void Start () {
        //连接服务器端
        //通过Listender连接服务器端的响应
        //第一个参数 指定一个Licensed(监听器) ,第二个参数使用什么协议
         peer = new PhotonPeer(this,ConnectionProtocol.Udp);
        //连接 UDP的 Ip地址:端口号,Application的名字
        peer.Connect("127.0.0.1:5055", "MyGame1");
        
    }
    
    // Update is called once per frame
    void Update () {
      
            peer.Service();//需要一直调用Service方法,时时处理跟服务器端的连接
    }
    //当游戏关闭的时候(停止运行)调用OnDestroy
    private void OnDestroy()
    {
        //如果peer不等于空并且状态为正在连接
        if (peer != null && peer.PeerState == PeerStateValue.Connected)
        {
            peer.Disconnect();//断开连接
        }
    }

    //
    public void DebugReturn(DebugLevel level, string message)
    {
     
    }
    //如果客户端没有发起请求,但是服务器端向客户端通知一些事情的时候就会通过OnEvent来进行响应 
    public void OnEvent(EventData eventData)
    {
        // 把服务器端接受到的事件分发到各个BaseEvent去处理
        EventCode code = (EventCode)eventData.Code;
        BaseEvent e=  DictTool.GetValue<EventCode, BaseEvent>(EventDict, code);//通过EventCode得到一个BaseEvent
        e.OnEvent(eventData);
    }
    //当我们在客户端向服务器端发起请求后,服务器端接受处理这个请求给客户端一个响应就会在这个方法里进行处理
    public void OnOperationResponse(OperationResponse operationResponse)
    {
        OperationCode opCode = (OperationCode)operationResponse.OperationCode;//得到响应的OperationCode
        Request request = null;
        bool temp = RequestDict.TryGetValue(opCode, out request);//是否得到这个响应
       // 如果得到这个响应
        if (temp)
        {
            request.OnOperationResponse(operationResponse);//处理Request里面的响应
        }
        else
        {
            Debug.Log("没有找到对应的响应处理对象");
        }
    }
    //如果连接状态发生改变的时候就会触发这个方法。
    //连接状态有五种,正在连接中(PeerStateValue.Connecting),已经连接上(PeerStateValue.Connected),正在断开连接中( PeerStateValue.Disconnecting),已经断开连接(PeerStateValue.Disconnected),正在进行初始化(PeerStateValue.InitializingApplication)
    public void OnStatusChanged(StatusCode statusCode) { 
    
    }

    //添加Requst
    public void AddRequst(Request requst)
    {
    RequestDict.Add(requst.OpCode, requst);

    }
    //移除Requst
    public void RemoveRequst(Request request)
    {
        RequestDict.Remove(request.OpCode);
    }
    //添加Event事件
    public void AddEvent(BaseEvent Event)
    {
        EventDict.Add(Event.EvCode, Event);
    }
    //移除Event事件
    public void RemoveEvent(BaseEvent Event)
    {
        EventDict.Remove(Event.EvCode);
    }
}

然后是发送请求的基类Request脚本

using Common;
using ExitGames.Client.Photon;
using UnityEngine;

public  abstract class Request:MonoBehaviour  {

     public  OperationCode OpCode;
    
     public   abstract void DefaultRequse();//向服务器发起请求方法
     public   abstract void OnOperationResponse(OperationResponse operationResponse);//服务器收到消息响应给客户端的方法

    //当这个组件初始化的时候添加这个Request
    public virtual void Start()
    {
        PhotonEngine.Instance.AddRequst(this);
    }
    //当这个组件被销毁的时候移除这个Request
    public void OnDestroy()
    {
        PhotonEngine.Instance.RemoveRequst(this);
    }
}

下面就是登陆面板上的两个脚本了LoginRequest脚本Loginpanel脚本

Paste_Image.png
using System.Collections.Generic;
using ExitGames.Client.Photon;
using UnityEngine;
using Common;

public class LoginRequest : Request
{
    [HideInInspector]
    public  string Username;
    [HideInInspector]
    public   string Password;

    private Loginpanel loginpanel;
  public   override void Start()
    {
        base.Start();
        loginpanel = GetComponent<Loginpanel>();
    }

    //发起请求
    public override void DefaultRequse()
    {
        Dictionary<byte, object> data = new Dictionary<byte, object>();
        data.Add((byte)ParameterCode.Username,Username);
        data.Add((byte)ParameterCode.Password, Password);
        PhotonEngine.Peer.OpCustom((byte)OpCode,data,true);//把用户名和密码传递给服务器
    }
    //得到响应
    public override void OnOperationResponse(OperationResponse operationResponse)
    {
        ReturnCode returnCode = (ReturnCode)operationResponse.ReturnCode;
        if (returnCode == ReturnCode.Success)//如果登陆成功保持当前用户的用户名
        {
            PhotonEngine.username = Username;
        }
        loginpanel.OnLoginResponse(returnCode);
      
    }
}
using UnityEngine;
using UnityEngine.UI;
using Common;
using UnityEngine.SceneManagement;

public class Loginpanel : MonoBehaviour {

    public GameObject RegisterPanel;//注册面板
    public InputField username;//UI界面输入的用户名
    public InputField password;//UI界面输入的密码
    private LoginRequest loginRequest;//提示信息

    public Text hintMessage;
    private void Start()
    {

        loginRequest = GetComponent<LoginRequest>();
    }
    //点击登陆按钮
    public void OnLoginButton()
    {
        hintMessage.text = "";
        loginRequest.Username = username.text;
        loginRequest.Password = password.text;
        loginRequest.DefaultRequse();
    }
    //点击注册按钮跳转到注册界面
    public void OnRegisterButton()
    {
        gameObject.SetActive(false);
        RegisterPanel.SetActive(true);
    }

    //登陆操作,如果验证成功登陆,失败提示用户或密码错误
    public void OnLoginResponse(ReturnCode returnCode)
    {
        if (returnCode == ReturnCode.Success)
        {
            //验证成功,跳转到下一个场景
            hintMessage.text = "用户名和密码验证成功";
            SceneManager.LoadScene(1);
        }
        else if (returnCode == ReturnCode.Failed)
        {
     
            hintMessage.text = "用户名或密码错误";
        }
    }
}

然后就是注册面板的两个脚本RegisterRequest脚本RegistrerPanel脚本

using System.Collections.Generic;
using ExitGames.Client.Photon;
using UnityEngine;
using Common;

public class RegisterRequest : Request
{
    [HideInInspector]
    public string username;
    [HideInInspector]
    public string password;

    private RegistrerPanel registerPanel;
    public override void Start()
    {
        base.Start();
        registerPanel = GetComponent<RegistrerPanel>();
    }
    //发起请求
    public override void DefaultRequse()
    {
        Dictionary<byte, object> data = new Dictionary<byte, object>();
        data.Add((byte)ParameterCode.Username, username);
        data.Add((byte)ParameterCode.Password, password);
        PhotonEngine.Peer.OpCustom((byte)OpCode, data, true);
    }
    ////得到服务器的响应
    public override void OnOperationResponse(OperationResponse operationResponse)
    {
        ReturnCode returnCode = (ReturnCode)operationResponse.ReturnCode;
        registerPanel.OnRegisterResponse(returnCode);
    }
}
using UnityEngine;
using UnityEngine.UI;
using Common;

public class RegistrerPanel : MonoBehaviour {

    public GameObject loginpanel;
    public InputField usernameIF;
    public InputField passwordIF;
    public Text hintMassage;

    private RegisterRequest registerRequest;
    private void Start()
    {
        registerRequest = GetComponent<RegisterRequest>();
    }
    //点击事件确认登陆
    public void OnRegisterButton()
    {
        hintMassage.text = "";
        registerRequest.username = usernameIF.text;
        registerRequest.password = passwordIF.text;
        registerRequest.DefaultRequse();
    }
    //点击事件返回
    public void OnBackButton()
    {
        loginpanel.SetActive(true);
        gameObject.SetActive(false);

    }
    
    public void OnRegisterResponse(ReturnCode returnCode)
    {
        if (returnCode == ReturnCode.Success)
        {
            hintMassage.text = "注册成功,请返回登陆";
      }
        else if (returnCode == ReturnCode.Failed)
        {
            hintMassage.text = "所用的用户名已被注册,请更改用户名";

        }
    }

}
场景二同步其他客户端角色和位置的代码

首先是接收服务器事件的基类代码BaseEvent脚本

using Common;
using ExitGames.Client.Photon;
using UnityEngine;

public abstract class BaseEvent : MonoBehaviour
{

    public EventCode EvCode;

    public abstract void OnEvent(EventData eventData);//接收服务器发送过来的数据与消息

    //当这个组件初始化的时候添加这个Request
    public virtual void Start()
    {
        PhotonEngine.Instance.AddEvent(this);
    }
    //当这个组件被销毁的时候移除这个Request
    public void OnDestroy()
    {
        PhotonEngine.Instance.RemoveEvent(this);
    }
}

然后是控制各个请求和接受事件的四个代码脚本了。

Paste_Image.png

SyncPositionRequest

using System;
using System.Collections.Generic;
using ExitGames.Client.Photon;
using UnityEngine;
using Common;

public class SyncPositionRequest : Request {

    [HideInInspector]
    public Vector3 pos;
    //发起位置信息请求
    public override void DefaultRequse()
    {
        //把位置信息x,y,z传递给服务器端
        Dictionary<byte, object> data = new Dictionary<byte, object>();
        data.Add((byte)ParameterCode.x,pos.x);
        data.Add((byte)ParameterCode.y, pos.y);
        data.Add((byte)ParameterCode.z, pos.z);

        PhotonEngine.Peer.OpCustom((byte)OpCode, data, true);//把Player位置传递给服务器
    }

    public override void OnOperationResponse(OperationResponse operationResponse)
    {
        throw new NotImplementedException();
    }
}

SyncPlayerRequest

using System.Collections.Generic;
using ExitGames.Client.Photon;
using Common;
using Common.Tools;
using System.IO;
using System.Xml.Serialization;

public class SyncPlayerRequest : Request
{
    private Player player;
    public override void Start()
    {
        base.Start();
        player = GetComponent<Player>();
    }
    //发起请求
    public override void DefaultRequse()
    {
        PhotonEngine.Peer.OpCustom((byte)OpCode, null, true);//把Player位置传递给服务器
    }
    //处理服务器响应给客户端的数据
    public override void OnOperationResponse(OperationResponse operationResponse)
    {
        //接收xml格式的字符串
       string usernameListString=(string)DictTool.GetValue<byte, object>(operationResponse.Parameters,(byte)ParameterCode.UsernameList);

        //通过xml反序列化解析传输过来的List数据 接受完后关闭
        using (StringReader reader = new StringReader(usernameListString))
        {
            XmlSerializer serializer = new XmlSerializer(typeof(List<string>));
           List<string> usernameList= (List<string>)serializer.Deserialize(reader);//表示读取字符串

            player.OnSyncPlayerResponse(usernameList);
        }
    }
}

NewPlayerEvent

using ExitGames.Client.Photon;
using Common.Tools;
using Common;


public class NewPlayerEvent : BaseEvent {

    private Player player;
    public override void Start()
    {
        base.Start();
        player = GetComponent<Player>();
    }
    //接受服务器发送过来的数据
    public override void OnEvent(EventData eventData)
    {
        string username = (string)DictTool.GetValue<byte, object>(eventData.Parameters,(byte) ParameterCode.Username);
        player.OnNewPlayerEvent(username);//根据用户名实例化出来新的Player出来
    }

}

Player

using System.Collections.Generic;
using UnityEngine;
using Common;
using Common.Tools;

public class Player : MonoBehaviour {

    //标识,为true表示是本地的Player,
    public bool islocalplayer = true;

    public string username;//表示这个player属于哪个用户

    public GameObject PlayerPrefab;//需要实例出来的Prefab

    public  GameObject player;

    private SyncPositionRequest SyncPosRequest;
    private SyncPlayerRequest syncPlayerRequest;


    private Vector3 lastPosition = Vector3.zero;
    private float moveOffset = 0.1f;

    //存储所有实例化出来的Player
    private Dictionary<string, GameObject> playerDic = new Dictionary<string, GameObject>();

    // Use this for initialization
    void Start () {
           //设置本地的Player的颜色设置成绿色
        player. GetComponent<Renderer>().material.color = Color.green;
        SyncPosRequest = GetComponent<SyncPositionRequest>();
        syncPlayerRequest = GetComponent<SyncPlayerRequest>();
        syncPlayerRequest.DefaultRequse();
        //参数一 方法名,参数二 从等多久后开始执行这个方法  参数三 同步的时间速率。这里一秒同步十次
        InvokeRepeating("SyncPosition", 3, 1 / 10f);//重复调用某个方法                                      
    }
    //位置信息时时更新
    void SyncPosition()
    {
        //如果玩家的位置当前玩家的位置和上玩家上一个的位置距离大于0.1,就表示玩家移动了,就需要他位置的同步
        if (Vector3.Distance(player.transform.position, lastPosition) > moveOffset)
        {
            lastPosition = player.transform.position;
            SyncPosRequest.pos = player.transform.position;//把这个位置信息传递给SyncPosRequest
            SyncPosRequest.DefaultRequse();//调用位置信息同步的请求
        }
    }

    // Update is called once per frame
    void Update () {
        //只有本地的Player,可以控制移动     
            float h = Input.GetAxis("Horizontal");
            float v = Input.GetAxis("Vertical");
           player.transform.Translate(new Vector3(h,0,v)*Time.deltaTime*4);
    }

    //实例化其他客户端的角色
    public void OnSyncPlayerResponse(List<string> usernameList)
    {
        //创建其他客户端的角色
        foreach (string username in usernameList)
        {
            OnNewPlayerEvent(username);
        }
    }

    public void OnNewPlayerEvent(string username)
    {
        GameObject go = GameObject.Instantiate(PlayerPrefab);
        playerDic.Add(username, go);//利用集合保存所有的其他客户端
    }


    public void OnSyncPositionEvent(List<PlayerData> playerDataList)
    {
        foreach (PlayerData pd in playerDataList)//遍历所有的数据
        {
            GameObject go = DictTool.GetValue<string, GameObject>(playerDic, pd.Username);//根据传递过来的Username去找到所对应的实例化出来的Player

            //如果查找到了相应的角色,就把相应的位置信息赋值给这个角色的position
            if (go != null)
            {
                go.transform.position = new Vector3() { x = pd.pos.x, y = pd.pos.y, z = pd.pos.z };
            }
        }
    }
}

现在所有的代码都贴上了,仅供学习

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容