Unity制作的多人在线的第三人称策略类射击游戏 ,服务端实现怪物寻路,服务端由python 实现。 (服务端没有用任何python第三方库)
服务端
要求: python 2.7
python server/main.py
客户端
Unity里运行login场景或者打包直接运行即可。
游戏服务端的架构在给的服务端架构模板上编写而成。
服务器主要分为以下几个模块:
simpleServer.py
,服务器主模块,主要负责游戏所有entity的管理以及游戏服务器的主流程的运行。storage/
目录, 数据存储模块,用于和sqlite数据库交互,储存玩家账户以及游戏角色的信息
gameDatabase.py
: 是用于链接数据库的单例模块table.py
: 是操纵数据库表的基类accountTable.py
: 是用于储存用户登陆注册信息的表playerEntityTable.py
: 是用于用户游戏角色数据的表service/
目录,即服务器提供的服务模块,通过客户端传入的指定的server id和command id来调用不同的回掉函数进行处理
dispatcher.py
: 模板中提供的分配器的基类loginService.py
: 用于处理登陆注册逻辑的服务missileEntityService.py
: 用于处理导弹(玩家技能)相关逻辑的服务monsterEntityService.py
: 用于处理怪物相关逻辑的服务playerEntityService.py
: 用于处理各个玩家相关逻辑的服务trapEntityService.py
: 用于处理陷阱相关逻辑的服务serviceMsg.py
: 用于传入通信信息的类path_finder/
目录,寻路模块,用于怪物自动寻路。
navmesh_matrix.txt
: 游戏场景的地图数据,在mapReader.py
模块中读入。mapReader.py
: 地图读取模块,用于读入地图数据,并进行相应的预处理pathFinder.py
: 寻路模块,用于传入地图中的起始位置和终点位置,返回路径的列表。enemyMove.py
: 怪物移动模块,获取怪物的移动方向。调用pathFinder.py
模块,通过传入的怪物坐标和目的玩家坐标,返回怪物移动的方向向量netWork/
目录,框架中的网络通信模块,管理已经连接的客户端以及消息的收发
netStream.py
: 发送接收数据的底层模块simpleHost.py
: clinet的管理模块entities/
目录,服务器端的各个entity的类模块,各个entity在游戏服务器中管理。
entity.py
: 各个entity的基类missileEntity.py
: 导弹(玩家技能)的模块monsterEntity.py
: 怪物模块playerEntity.py
: 玩家(player)模块trapEntity.py
: 陷阱模块common_server/
目录,服务器通用模块
timer.py
: 服务器计时器模块common/
目录,服务器基本设置
conf.py
: 通信消息格式的配置constrants.py
: 用到的常量以及游戏数据的配置events.py
: 框架中的事件模块,用于定义消息格式header.py
: 框架中的消息头模块,用于序列化和反序列化通信消息头部msgHandler.py
: 消息解析器,用于处理和解析自定义消息格式游戏的怪物寻路在服务器端完成,通过在二维网格中的A*算法实现,传入的地图数据是从Unity中导出的二维网格坐标。
在进行寻路时首先找到场景中距离其最近的玩家的坐标,随后找到在寻路网格中距离怪物和玩家最近的可循路的整点坐标,通过A*算法计算出怪物到达玩家的路径。取路径中的怪物的下两个路径的坐标点,通过怪物的当前坐标,计算出怪物的移动向量返回给上层。
逻辑层通过设置的怪物的速度和该怪物移动的向量,在entity.tick中改变怪物的位置,并在服务器每次帧同步中将怪物的位置实时同步给各个客户端。
为减少寻路算法带给服务器的压力,因为每次寻路后获取的是每个怪物移动的方向向量,所以不必服务器每帧对场景中的每个怪物都寻路一次。
故在实现时设置了怪物的寻路帧数,服务器设置的是4,即4个服务器帧(0.4秒)寻路一次,每次寻路只改变怪物的移动方向的单位向量。
而怪物的位置则可以通过缓存的怪物移动方向的单位向量和怪物的速度在每个服务器帧实时改变。
客户端代码都在Assets/Scripts
目录下。
Managers/TPSGameManager.cs
: 客户端的主要游戏流程模块,通过解析服务端传递的同步消息,对场景中的GameObject进行相应的处理Server/NetworkSettings.cs
: 网络通信模块,用于发送和接受消息。并将接受到的消息处理过后储存在消息队列中供上层模块调用。同时该模块为单例模式。Message/
目录,客户端消息格式定义目录:
ClientMsg.cs
: 客户端向服务端发送消息的消息格式,其中有一个ClientMsg基类以及各个继承自基类的消息通信class。ServerMsg.cs
: 服务端向客户端发送的消息痛惜格式,其中有一个ServerMsg基类以及各个消息通信格式的classMessageHandler.cs
: 消息处理器,用于发送消息的格式的封装以及接受消息的格式的解析。在接受服务器消息时,通过MessageHandler.cs
模块将消息解析统一解析成各个格式,并用基类ServerMsg指向它们并返回给上层。上层逻辑则通过得到的消息中的消息类别,对各个消息进行分类处理。Player\
目录: 玩家的各个游戏逻辑的处理显示,以及调用MessageHandler.cs
向服务端发送通信消息Missile\
目录: 玩家发送导弹以及怪物远程攻击(南瓜)的游戏逻辑处理以及消息发送。Login\UserLogin.cs
:登陆界面的逻辑处理Enemy\
目录:怪物的游戏逻辑处理及消息发送Settings\
目录:游戏常量以及游戏参数设置UI\
目录:游戏界面的各个逻辑的处理和更新由于服务端不具有物理引擎,碰撞检测的处理,触发器的使用都需要在客户端进行。为了避免各个客户端向服务端重复发送消息。例如:怪物攻击玩家的消息,只需要一个客户端发送即可。所以各个客户端在处理攻击消息时,默认规定:本机的操作本机去处理。
即玩家的攻击和被攻击操作,只在各个客户端本机去检测。
玩家技能(导弹)的爆炸检测只在发送的本机检测,在发生碰撞时则向服务器发送爆炸消息,服务器在广播给各个客户端。
以防止每个客户端向服务端发送重复的游戏操作消息,扰乱服务端的逻辑。
由于客户端服务端通信的频率时每秒10帧,所以如果再客户端直接将服务端传到的怪物和其他玩家的位置设置为其当前的位置,则相应的GameObject的显示则会非常卡顿,所以在接受到服务端传输到的位置后,不能直接设置,而是进行相应的处理。
这里采用的方法是,通过服务端传送到的位置和该Entity在每个客户端本地的位置,计算出一个移动的向量,并通过该Update中的deltaTime和服务器客户端同步的帧时间的比值相乘,计算出该Gameobject在客户端每个Update中的位移,在对其位置进行相应的改变,则其他玩家和怪物的移动则会非常顺畅。
在放置陷阱时,需要玩家首先进行位置选择,并在确认后将陷阱放置完毕。
在进行陷阱检测时,会根据玩家选取的位置是否有障碍物对陷阱的shader进行实时的改变,效果是如果玩家选择的陷阱的放置的地方有障碍物,则将改变陷阱的shader变为纯色透明,并将颜色变为红色。如果是可以放置的位置,则变为绿色透明。
位置的检测是通过摄像机的朝向向场景中打一个射线,并得到落点。计算在落点内的相应立方体大小的空间内是否存在collider,如果不存在collider,则认为可以放置,否则认为不能放置。
为了放置陷阱重叠放置,在选择放置陷阱时,首先关掉陷阱中的collider,在放置陷阱时打开即可以实现放置陷阱重叠放置的功能。