核心问题
如何实现所有客户端玩家信息同步(如位置信息)?

同步模式一般分两种:状态同步和帧同步。而本文主要针对MMO类游戏,所以建议用状态同步。

状态发生后,客户端上传操作到服务器,服务器收到后处理行为的结果,然后以广播的方式把状态发送给所有客户端,客户端收到状态后再根据状态显示内容。

MMORPG类游戏制作思路分享

  • 一、配置一个简单的服务器
    • 1、SDK及许可证下载地址
    • 2、配置Server文件
    • 3、添加Server程序,打印日志
  • 二、创建Unity客户端
  • 三、服务器连接数据库
  • 四、Unity连接服务器实现登陆、注册
  • 五、实现客户端玩家的创建同步
  • 六、Unity客户端玩家的位移同步

一、配置一个简单的服务器

1、SDK及许可证下载地址

首先,登录Photon官网下载PhotonServer:下载地址

然后申请下载100人连接数许可证:下载地址

解压SDK后如下:

把下载的许可证放到此目录下:deploy\bin_Win64

然后我们启动PhotonControl.exe,这时候我们就可以看到最大连接数到了100人了

2、配置Server文件

用VS创建一个类库MyGameServer,在这个路径下再创建一个文件夹MyGameServer,然后再MyGameServer文件夹里面再创建一个文件夹命名为bin,创建好后我们在VS里面右击项目—>属性—>生成—>输出路径,把输出路径放在我们刚刚创建的MyGameServer\bin文件里面。
接着导入引用集,打开下图路径,将ExitGamesLibs.dll、Photon.SocketServer.dll、PhotonHostRuntimeInterfaces.dll、log4net.dll(用于日志输出,可以把一个文本写入到文本文档里面)四个程序集引用添加进来。

引入后将服务器端继承自ApplicationBase

using Photon.SocketServer;namespace MyGameServer
{//所有的Server端 主类都要继承自applicationbasepublic class MyGameServer : ApplicationBase{//当一个客户端请求连接的时候,服务器端就会调用这个方法//我们使用peerbase,表示和一个客户端的链接,然后photon就会把这些链接管理起来protected override PeerBase CreatePeer(InitRequest initRequest){return new ClientPeer(initRequest);}//初始化(当整个服务器启动起来的时候调用这个初始化)protected override void Setup(){}//server端关闭的时候protected override void TearDown(){}}
}

然后我们在添加一个类ClientPeer,这个类是管理与客户端的连接的。继承自Photon.SocketServer.ClientPeer

using Photon.SocketServer;
using PhotonHostRuntimeInterfaces;namespace MyGameServer
{//管理跟客户端的链接的class ClientPeer : Photon.SocketServer.ClientPeer{public ClientPeer(InitRequest initRequest) : base(initRequest){}//处理客户端断开连接的后续工作protected override void OnDisconnect(DisconnectReason reasonCode, string reasonDetail){}//处理客户端的请求protected override void OnOperationRequest(OperationRequest operationRequest, SendParameters sendParameters){}}
}

配置好这些信息后,我们右击项目—>生成

进入deploy\MyGameServer\bin文件夹下看到我们项目生产的dll程序集了

3、添加Server程序,打印日志

接下来开始配置我们的TCP,UDP配置文件。
在deploy\bin_Win64文件下找到PhotonServer.config,这个就是我们需要的配置文件。

创建一个Log对象,然后初始化日志,在MyGameServer这个类里面定义一个Log对象

//定义一个Log对象public static readonly ILogger log = LogManager.GetCurrentClassLogger();

然后在Setup里面初始化日志 ,在CreatePeer方法里处理客户端连接的事件

     //当一个客户端请求连接的时候,服务器端就会调用这个方法//我们使用peerbase,表示和一个客户端的链接,然后photon就会把这些链接管理起来protected override PeerBase CreatePeer(InitRequest initRequest){log.Info("一个客户端连接进来了!");return new ClientPeer(initRequest);}//初始化(当整个服务器启动起来的时候调用这个初始化)protected override void Setup(){//日志的初始化(定义配置文件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\binFileInfo 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对象就可以输出了}//server端关闭的时候protected override void TearDown(){}

重新在VS里面生成下然后关闭并重启Photon,打开日志,就可以看到我们自己的日志了

二、创建Unity客户端

在Unity3D里面导入photon unity客户端的dll,继承IPhotonPeerListener

1、在客户端发起与服务器的连接,服务器回应给客户端
客户端通过SendOperation()方法发起指定的请求,服务器通过OnOperationRequest()方法接收客户端请求,然后调用SendOperationResponse()方法给客户端一个回应,客户端通过OnOperationResponse()方法获取请求的回调信息。

2、从服务器给客户端直接发送事件SendEvent
在unity客户端里面的PhotonEngine类里面有一个方法OnEvent(),这个方法表示如果客户端没有发起请求,但是服务器端向客户端通知一些事情的时候就会通过OnEvent来进行响应

三、服务器连接数据库

用NHibernate数据库映射工具来连接数据库
参考此博客:http://www.u3d8.com/?p=1414

四、Unity连接服务器实现登陆、注册

客户端
一、首先是工具类Singleton封装的单例脚本

二、消息订阅分发类HandlerMediat,用于分发接收到的服务器消息。如果不太懂观察者模式使用的 可以参考:消息订阅分发机制的实际应用

三、HandlerMediat类对应的枚举OperationCode,该枚举值是与服务器消息类型保持一致。记录该消息是属于什么类型,比如用户登录消息、用户注册消息、获取背包消息等等

四、创建监听服务器消息抽象基类HandlerBase

五、创建监听服务器登录消息类LoginHandler继承自:HandlerBase,这里因为登录和注册消息比较简单,所以我统一放在了登录消息类里。

该类建完后,在登录场景中创建物体, 命名为“Handler”,并挂载LoginHandler脚本

六、消息发送类LoginRequest,同样这里将登录消息和注册消息都放在了该类里

七、UI脚本,我们创建LoginPanel和RegisterPanel分别管理登录界面和注册界面的UI。这里为了方便,所有UI都是外部挂载上去的。

八、修改第二节挂载的PhotonEngine脚本。添加一个ReturnCode枚举值,对应服务器消息成功失败。在OnOperationResponse方法里处理接收到的消息。

服务器
首先我们在服务器创建一个文件夹Handler,这个文件夹下的文件都用来接收处理客户端发来的消息。

在Handler文件夹下创建相应的脚本IHandlerBase、LoginHandler

创建Net文件夹,并将与网络相关的ClientPeer、MyGameServer脚本拖入进去

在Net文件夹创建脚本HandlerMediat、OperationCode

创建Tools文件夹,并将NhibernateHelper脚本拖入进去

在Tools文件夹创建脚本DictTool

一、接口IHandlerBase 作为接收客户端消息类的基类

二、LoginHandler类,该类用来接收客户端发送的登录、注册消息的处理

三、HandlerMediat类,该类与Unity客户端的类似,用于分发接收到客户端的消息

四、HandlerMediat类对应的枚举OperationCode,该枚举值是记录该消息是属于什么类型,比如用户登录消息、用户注册消息、获取背包消息等等

五、DictTool类,该类是做字典解析的封装

六、修改MyGameServer脚本,添加一个ReturnCode枚举值,对应消息成功失败。在Setup和TearDown方法里分别添加Handler和移除Handler

七、修改ClientPeer脚本,在OnOperationRequest方法里分发消息

五、实现客户端玩家的创建同步

在客户端与服务器端同步创建玩家这个步骤里

客户端需要做三件事情:

1.当前客户端创建角色,发送告知服务器端

2.当前客户端接收服务器发送的其它在线客户端的玩家角色信息

3.其它客户端接收服务器发送的当前客户端的角色信息

服务器端需要做两件事情:

1.告诉当前客户端 其它在线客户端的信息,让当前客户端创建其它客户端玩家角色

2.告诉其它客户端 有新的客户端加入,需要创建新客户端玩家角色

六、Unity客户端玩家的位移同步

位移同步的思路

一、客户端每隔指定时间向服务器端发送位置信息

二、服务器接收并记录该客户端的位置信息

三、服务器每隔指定时间广播给所有在线客户端发送所有客户端的位置信息

四、客户端接收服务器发送的所有客户端位置信息,并修改他们位置

客户端位移同步
一、客户端每隔指定时间给服务器发送位置信息,实现位置实时更新

二、在客户端监听服务器发送的所有客户端位置信息,在场景中同步其它玩家位置

服务器端位移同步
一、接收客户端发来的位置信息

二、记录每个客户端的用户名,坐标

三、开启线程,每隔指定时间向所有在线客户端发送所有客户端的位置信息

using MyGameServer.Common;
using Photon.SocketServer;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Xml.Serialization;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里面的usernameplayerdata.x = peer.x;//设置playerdata里面的Positionplayerdata.y = peer.y;playerdata.z = peer.z;playerDatraList.Add(playerdata);//把playerdata放入集合}}//进行Xml序列化成StringStringWriter 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(1, 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();//终止线程}}
}
private SyncPositionThread syncPositinThread = new SyncPositionThread();protected override void Setup(){Instance = this;syncPositinThread.Run();// 日志的初始化(定义配置文件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\binFileInfo 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对象就可以输出了AddHandler();}// server端关闭的时候protected override void TearDown(){syncPositinThread.Stop();RemoveHandler();log.Info("关闭了服务器");}

相关博客参考:
利用Photon实现实时联网对战(一)https://www.pianshen.com/article/45091657315/
利用Photon实现实时联网对战(二)PUN SDK介绍 https://www.pianshen.com/article/24311640962/
photon sever实战 http://www.u3d8.com/?cat=324
使用photon sever 自建服务器https://www.pianshen.com/article/17951701363/

MMORPG类游戏制作思路分享(Unity3D+PhotonServer)相关推荐

  1. 基于Springboot+Vue的MOBA类游戏攻略分享平台

    摘 要 随着信息技术和网络技术的飞速发展,人类已进入全新信息化时代,传统管理技术已无法高效,便捷地管理信息.为了迎合时代需求,优化管理效率,各种各样的管理系统应运而生,各行各业相继进入信息管理时代,M ...

  2. 基于vue的MOBA类游戏攻略分享平台

    082-springboot基于vue的MOBA类游戏攻略 开发语言:Java 框架:springboot JDK版本:JDK1.8 服务器:tomcat7 数据库:mysql 5.7 数据库工具:N ...

  3. 基于Vue+Springboot的MOBA类游戏攻略分享平台【毕业设计,源码,论文】

    摘 要 随着信息技术和网络技术的飞速发展,人类已进入全新信息化时代,传统管理技术已无法高效,便捷地管理信息.为了迎合时代需求,优化管理效率,各种各样的管理系统应运而生,各行各业相继进入信息管理时代,M ...

  4. java 坦克世界源代码教程_译文教程:坦克世界游戏制作技术分享

    Hello . 大家好,今天给大家带来坦克世界游戏制作技术分享,希望能给大家带来一些启发,我是神棍赵. 1.介绍 坦克世界拖更两年,内心各种不满.近日有了新动作,不争气的我也去申请了账号继承.今天就舔 ...

  5. springboot+vue项目之MOBA类游戏攻略分享平台(java项目源码+文档)

    风定落花生,歌声逐流水,大家好我是风歌,混迹在java圈的辛苦码农.今天要和大家聊的是一款基于springboot的MOBA类游戏攻略分享平台.项目源码以及部署相关请联系风歌,文末附上联系信息 .

  6. 基于springboot和vue的MOBA类游戏攻略分享平台【附项目源码】

    基于springboot和vue的MOBA类游戏攻略分享平台 开发语言:Java 框架:springboot JDK版本:JDK1.8 服务器:tomcat7 数据库:mysql 5.7 数据库工具: ...

  7. springboot基于vue的MOBA类游戏攻略分享平台

    系统分析 系统可行性分析 1.经济可行性 由于本系统本身存在一些技术层面的缺陷,并不能直接用于商业用途,只想要通过该系统的开发提高自身学术水平,不需要特定服务器等额外花费.所有创造及工作过程仅需在个人 ...

  8. 基于springboot和vue的MOBA类游戏攻略分享平台【附项目源码】分享

    基于springboot和vue的MOBA类游戏攻略分享平台 开发语言:Java 框架:springboot JDK版本:JDK1.8 服务器:tomcat7 数据库:mysql 5.7 数据库工具: ...

  9. pygame制作rpg类游戏或者模拟经营类游戏的思路

    Pygame 能够支持开发 RPG 类或者模拟经营类游戏.Pygame 提供了图形界面.事件处理.音频处理等基础功能,开发者可以利用这些功能实现自己的游戏逻辑. 例如,开发者可以利用 Pygame 实 ...

最新文章

  1. C语言指针表示二维数组的方法!_只愿与一人十指紧扣_新浪博客
  2. measure_profile_sheet_of_light算子说明
  3. 27道高频Spring面试题,你能答对几个?
  4. NOIP模拟题——来自风平浪静的明天
  5. Simple Introduction to Dirichlet Process
  6. mysql设置php权限_MYSQL新建用户并设置权限
  7. node多版本管理--nvmw
  8. 从镜头到滤光片 解读光学透雾监控摄像机
  9. idea部署springboot项目到外部tomcat
  10. iverilog+gtkwave 进行仿真
  11. Lambda表达式妙用
  12. 算法:动态规划,最大子数组之和 Maximum Subarray
  13. Newtonsoft.Json.Compact
  14. android编程实现128条形码的生成和识别
  15. RainMeter学习2
  16. python123随机密码生成、生成三组n位密码_生成随机密码
  17. 【CCNA3】思科基本命令汇总+网线线序
  18. 路径详解(绝对路径,相对路径,根相对路径)
  19. Chrome html播放器卡顿,谷歌Chrome浏览器卡顿原因及解决办法
  20. DYNAMIC DETECTION

热门文章

  1. 利用C语言判断年份,闰年问题
  2. STM32单片机蓝牙APP语音识别智能家居系统风扇灯空调窗帘温度湿度入侵检测
  3. 你应该知道的 7 个很棒的 Java 项目
  4. 《统计学习方法》第10章 隐马尔科夫模型 HMM算法 纯Python代码实现 + 前后向算法矩阵形式 + 课后习题答案
  5. WhaleCTF-隐写术 彩虹的声音
  6. 七日杀unity报错_我玩七日杀 总是闪退,下面是错误报告的内容
  7. 深度学习国外课程资料(Deep Learning for Self-Driving Cars)+(Deep Reinforcement Learning and Control )
  8. Java 保护Excel 工作簿和工作表
  9. UMLet创建自定义元素
  10. UMLet安装与使用