回合卡牌的战斗模块

开发日志系列(十四)

cocos2d-x lua binding C/C++

在我目前参与的这款无操回合卡牌手游中,客户端战斗模块是由我实现的,在项目上线之前一直都生怕出现不可逆转的设计问题,所以我把模块流程描述给美术,别的程序同事听 同时尽量写出来.这个模块几乎全部使用C++实现.

战斗流程

解析服务端战斗数据创建战斗场景创建战斗对象进入场景按回合播放战斗特效对象退出场景显示战斗结果销毁回收内存/重播

  • 战斗数据解析器(parser)

这个解析器主要工作是解析和管理战斗初始数据.除此之外不做任何事情,目前项目中一般的战斗数据长度大概是1.5kb(1200~1600字节),在按数据结构(struct)解析数据的时候需要注意 服务端和客户端 的struct定义数据位对其的问题:

#pragma pack(1)
define some struct..
#pragma pack()

由于结构定义的这段代码运行平台的不同(linux/win/ios/android),如果使用相同的结构解析而不设置位对其 必然会出现结构大小的不同.
此外,解析器还需要支持解析N种版本的战斗数据,我们给战斗协议设置了不同的版本,考虑到可能出现的PVP,PVE等战斗情况.根据战斗数据版本解析数据是非常必要的.

  • 战斗对象实体(BattleObject)

战斗对象的处理直接影响战斗效果.在播放战斗对象的动作的时候(待机动作/技能动作/死亡动作等) 根据对象动作引入了对象状态机,状态机的设计是这个战斗模块的核心部分.关于状态机可以看这里:传送门,主要为战斗对象设置一下几种状态:

enum ECreatureState
{
    ECreatureState_Stand = 1,   /// 站立
    ECreatureState_Move,        /// 移动
    ECreatureState_Fight,       /// 战斗
    ECreatureState_Hit,         /// 受击
    ECreatureState_Dead,        /// 死亡
};

每次切换状态的时候 几乎就是播放相应动作的时机,状态机的管理要点在于:不同的状态做不同的事情,例如只有死亡状态 才能播放死亡动作,不能在死亡状态播放跑动,同样的 如果战斗已经已经是死亡状态 则不可能在没有复活的情况下 执行其他动作.只有复活能打断死亡状态.

  • 技能实体部件(SkillPart)

基于游戏实体的设计模式(概念出自游戏编程精粹4)为战斗对象实体增加了一个技能部件,通过这部件 战斗对象可以做关于技能的任何事情,释放技能就是在战斗过程中主要需要做的.技能部件的设计也使用了状态机机制.把一个技能从释放到结束分成了大概的几个状态:

enum ESkillState
{
    SkillState_Normal,          /// 正常
    SkillState_Move_Teleport,   /// 移动冲锋
    SkillState_Move_Go,         /// 移动状态 去攻击 
    SkillState_Move_Back,       /// 移动状态 回原位
    SkillState_FlyWords,        /// 技能名飘字
    SkillState_Prepare,         /// 蓄气
    SkillState_WaitAttack,      /// 等待施法
    SkillState_RapidAttack,     /// 瞬发型技能施法
    SkillState_BootAttack,      /// 引导型技能施法
    MaxSkillStateCount,
};

经过讨论我们觉得上面的状态已经比较完整了.除非再把BUFF功能加入到技能中,否则应该无需再加入新的状态了.

整个释放技能的流程 同样基于状态机的改变,不同的技能状态做不同的事情:播放不同的特效,操作特效时长,控制人物行为等.

  • 伤害处理(DamageHandle)

伤害处理主要针对 被攻击的战斗对象,这里使用了单例模式.使用单例的原因是由于需要集中的管理扣血 等行为,方面在游戏中同时的展示出对方集体死亡 集体受击等效果.把伤害处理设计成单例的另外一个主要原因是:很多技能有可能是一对多(多对多的也可能有)的群攻 或者 多次群攻.所以没必要为每一个伤害对象运行一份伤害处理代码.

  • 特效播放

一场战斗往往需要播放的特效非常非常的多,所以特效的播放侦听的事件比较少 只有 特效开始 和特效结束,除此之外 特效的播放还设计成 结束即刻回收删除,这点很重要,如果战斗结束才一起删除的话 虽然很小可能会导致内存爆掉,但是过多的特效同时从内存中删除 总感觉不够高效.

  • 技能结束控制(SkillEnd)

怎么判断一个技能是否结束了呢?我们有几个时间点都可以作为技能结束的参照:

  1. 攻击方技能动作完毕(Skill Action End)
  2. 受击方受击动作完毕(Damage Action End)
  3. 攻击特效结束(Attack Magic End)
  4. 受击特效结束(Damage Magic End)
  5. 进程技能人物归位(Move Back End)

从效果来看 是应该满足这里的所有条件才能判定技能结束,但是实际很多的美术动作设计 时间很难掌握.所以我针对不同的技能类型来设定了技能结束时机:

近程技能 结束依赖于 Move Back End
远程技能 结束依赖于 Damage Magic End 和 Damage Action End
飞行技能 结束依赖于 Damage Magic End 和 Damage Action End
冲锋技能 结束依赖于 Move Back End
所有群攻 结束需要满足全部条件

以上便是整个战斗模块的核心理论

1 评论

  1. 不错,以前有幸参与过回合制战斗,可惜当时经验不足,只是根据主程的实现,做出很小的一部分,后续主程换掉,所有的战斗模块都换掉了,我也没有再管过。

发表评论

电子邮件地址不会被公开。 必填项已用*标注