Awesome
Trick or Treat
运行
服务端
要求: python 2.7
python server/main.py
客户端
Unity里运行login场景或者打包直接运行即可。
游戏实现功能
服务端
- 玩家数据管理
- 数据库使用sqlite,玩家登陆时会从数据库中玩家账号密码信息,如果登陆成功,则会从角色信息中读取玩家角色信息(如果不存在则会在服务端重新创建一个玩家角色)
- 服务端会储存玩家的名字,玩家经验值,玩家等级,以及升级的陷阱等级和导弹(玩家远程攻击)等级,历史最高分数等。
- 玩家的等级和陷阱、技能的等级上限目前定为10级。
- 升级
- 玩家击杀怪物会获取经验值和金钱,金钱可以用来购买陷阱,经验值达到一定值玩家会自动升级。玩家升级可以获取技能点,技能点可以用来升级陷阱和导弹攻击伤害。
- 攻击方式/技能
- 普通攻击,点击鼠标左键,单体攻击,攻速较快,伤害较少
- 导弹攻击,点击鼠标右键,群体伤害,伤害较高,具有冷却时间。可以升级,升级会提高伤害
- 注意:导弹攻击具有队友伤害,即会伤害自己和队友!(ps:杀死队友可以获取队友金钱。)
- 怪物
- 怪物寻路逻辑通过A*算法在服务端实现。
- 近战怪(小熊和兔子)。近战怪会一直向玩家移动,直到距离足够接近,对玩家以一定频率发起攻击。其中兔子的移动速度会比小熊快,但是血量比小熊少。
- 远程怪(大象)。大象同样会向玩家移动,当其和玩家的距离到达其射程之后,会向玩家抛出一个南瓜,当南瓜砸到玩家时,玩家的血量会减少。
- 怪物会向距离其最近的玩家发起攻击,如果玩家进入了地图不可选取的区域(寻路无法到达的地方),怪物会停止移动,知道玩家再次进入可以寻路的区域。
- 陷阱
- 玩家可以释放两种陷阱,尖刺陷阱和减速陷阱。释放陷阱需要花费金钱购买。
- 尖刺陷阱
- 尖刺陷阱会对进入其中的怪物进行周期性的伤害。
- 如果玩家升级了尖刺陷阱,之后释放的尖刺陷阱的伤害会变高(玩家升级陷阱不会影响地图上已经存在的陷阱的性能)。
- 减速陷阱
- 减速陷阱会降低进入其中的怪物的移动速度。
- 升级减速陷阱会使玩家之后放置的减速陷阱减速力度更大。
- 游戏流程
- 怪物的产生分为不同波次,后续的波次怪物的数量会增多。
- 每两波怪物产生的时间间隔有10s。
客户端
- 操作:
- 玩家通过第三人称视角操控角色,玩家的攻击(普通攻击和导弹)会射向屏幕中间的准星
- 玩家可以八方向移动
- 怪物:
- 近战怪会持续向玩家移动并且离玩家距离足够近时发动攻击。
- 远程怪会向玩家移动知道玩家进入其攻击射程后,向玩家抛南瓜。(南瓜受重力系统的影响,并保证如果玩家不移动,南瓜能准确砸中玩家)
- 怪物头上具有血条会实时显示服务器传送来的怪物血量信息
- 怪物收到伤害时会发出受伤的声音。
- 关卡流程
- 每波关卡会有一定数量的怪物产生,后边的关卡怪物的数量会不断增多。
- 两个关卡之间会给玩家10s的等待休息时间,并在屏幕上显示下一波怪物到达的倒计时。
- 网络同步
- 客户端服务端数据同步的频率是100ms一次
- 通过平滑处理客户端怪物和同步玩家的移动减少Gameobject的抖动,即每次收到同步位置后不是直接同步位置,而是通过位置获取该物体移动的方向,并在每个客户端帧中移动相应的位移。
- 陷阱
- 玩家点击释放陷阱时会在准星和场景的碰撞点放置一个陷阱,并通过物理系统检测碰撞判断此时陷阱是否处于合理的位置,如果可以释放,则改变其shader,将其变为绿色,否则则变为红色。只有在陷阱为绿色时,玩家点击确认按钮才可以成功放置陷阱。
- 陷阱放置后会开启相应的动画。
- UI
- 游戏中会实时显示玩家的血量,玩家的经验条,玩家的级别,玩家的名称,玩家当前的金钱和分数,玩家的陷阱以及导弹的技能图标和级数,导弹技能的冷却时间,玩家的技能点数,下一波怪物到达的时间等。
- 怪物和其他玩家的上部会实时绘制一个血条用于显示怪物或者其他玩家的当前血量。
服务端
服务端架构
游戏服务端的架构在给的服务端架构模板上编写而成。
服务器主要分为以下几个模块:
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\
目录:游戏界面的各个逻辑的处理和更新
客户端协议发送处理
由于服务端不具有物理引擎,碰撞检测的处理,触发器的使用都需要在客户端进行。为了避免各个客户端向服务端重复发送消息。例如:怪物攻击玩家的消息,只需要一个客户端发送即可。所以各个客户端在处理攻击消息时,默认规定:本机的操作本机去处理。
即玩家的攻击和被攻击操作,只在各个客户端本机去检测。
玩家技能(导弹)的爆炸检测只在发送的本机检测,在发生碰撞时则向服务器发送爆炸消息,服务器在广播给各个客户端。
以防止每个客户端向服务端发送重复的游戏操作消息,扰乱服务端的逻辑。
客户端同步entity位置缓冲
由于客户端服务端通信的频率时每秒10帧,所以如果再客户端直接将服务端传到的怪物和其他玩家的位置设置为其当前的位置,则相应的GameObject的显示则会非常卡顿,所以在接受到服务端传输到的位置后,不能直接设置,而是进行相应的处理。
这里采用的方法是,通过服务端传送到的位置和该Entity在每个客户端本地的位置,计算出一个移动的向量,并通过该Update中的deltaTime和服务器客户端同步的帧时间的比值相乘,计算出该Gameobject在客户端每个Update中的位移,在对其位置进行相应的改变,则其他玩家和怪物的移动则会非常顺畅。
陷阱的位置放置检测
在放置陷阱时,需要玩家首先进行位置选择,并在确认后将陷阱放置完毕。
在进行陷阱检测时,会根据玩家选取的位置是否有障碍物对陷阱的shader进行实时的改变,效果是如果玩家选择的陷阱的放置的地方有障碍物,则将改变陷阱的shader变为纯色透明,并将颜色变为红色。如果是可以放置的位置,则变为绿色透明。
位置的检测是通过摄像机的朝向向场景中打一个射线,并得到落点。计算在落点内的相应立方体大小的空间内是否存在collider,如果不存在collider,则认为可以放置,否则认为不能放置。
为了放置陷阱重叠放置,在选择放置陷阱时,首先关掉陷阱中的collider,在放置陷阱时打开即可以实现放置陷阱重叠放置的功能。