回合制战斗架构

实现回合制战斗的特点就是,战斗过程按照回合为单位来处理逻辑。处理好一个回合的逻辑,整场战斗也就差不多实现完了。

回合制战斗也分两种类型,自动释放技能类型和手动释放技能类型。自动释放技能类型不用玩家选择技能,手动释放技能类型需要玩家每回合选择技能来释放。这里我只介绍自动释放技能类型的,因为手动释放技能只是在每回合加个超时等待玩家输入技能。

下面把战斗执行单元成为英雄,也就是卡牌游戏里的卡牌。

首先,一场战斗的流程如下:

  1. 双方英雄入场,比如 5v5,按照某种顺序排好位置
  2. 按照某种出手顺序,逐个英雄出手攻击
  3. 回合结束处理每个英雄身上的特殊状态
  4. 回合到最大值或者某一方全部死亡则战斗结束
  5. 发送战报给客户端播放战斗效果

战斗主要逻辑就是上面的2,3步骤。

技能分两种技能,主动技能和被动技能。英雄出手攻击的过程就是释放一个主动技能,被动技能是在某些特殊情况下自动释放的技能。主动技能和被动技能的区别只是触发时机不同,下面先说说主动技能如何实现。

举个例子:

对随机2名敌方单位造成90%伤害,并给自身增加15%的伤害

这里我们根据行动目标的不同,把这个技能拆分两个操作。第一个操作是随机找到敌方2个英雄,第二个是对自己操作。伪代码如下:

-- 随机 2 名敌方单位造成 90% 伤害
local targets = random_target(heros, OTHER_SIDE, 2)
for target in pairs(targets) do
    local hurt = 0.9 * my.attr.hurt
    target.attr.hp = target.attr.hp - hurt
end

-- 自身伤害属性增加 15%
my.attr.hurt = my.attr.hurt * 1.15

直接这样把代码写死的话,需求变化,代码就需要频繁的变。所有我们要抽出不变的东西写成代码,把变化的东西做成配置 Excel 表格。不变的是什么呢?行动流程是不变的,可以这样写行动流程:

-- 每回合的逻辑
for round=1,cfg.MAX_ROUND do
    -- 每个英雄轮流出手
    for heros in ipairs(heros) do
        -- 1. 处理行动前状态
        if hero.attr.hp > 0 then
            process_round_status(fid, hero)
        end
        -- 2. 出手逻辑
        if hero.attr.hp > 0 then
            process_action(fid, hero)
        end

        -- 检查是否结束战斗
        if check_fight_end(fid) then
            is_end = true
            break
        end
    end
    if is_end then
        break
    end
end
-- 上面的变量 f_id 是用来定位这场战斗的,打印日志和读取某些数据的时候用的。

先说下处理行动前状态是用来做什么的,这段逻辑用用来处理某些特殊 buffer 的。buffer 是用状态机实现的,英雄身上会挂着一堆状态,状态有回合数限制的。比如英雄身上有个持续两回合的状态,两回合到了就要把这个状态删掉。这个删状态的触发逻辑就在 process_round_status 里实现。

接下来主要实现上面的出手逻辑,执行技能操作区1 -> 执行技能操作区2 ...

技能操作区的划分就是按照技能目标的不同来区分,比如上面的例子:“对随机2名敌方单位造成90%伤害,并给自身增加15%的伤害”,操作区1就是 对随机2名敌方单位造成90%伤害, 操作区2就是 给自身增加15%的伤害

这样,一个技能的行动逻辑就主要是实现一个技能的操作区的逻辑了,递归或者循环处理完所有的操作区就行了。下面就以操作区1为例子讲下如何配 Excel 表。

技能配表可以这样配,先是配选目标

技能ID 目标选择范围 目标排序方法 目标选择基础数量
JN101 敌方 血量最低 2

上面这样配的是操作区1的目标选择参数,目标选择范围可以是我方,敌方,全员,上操作区的目标等等其他能想到的目标。目标排序方法就是来配置是选血量低的还是其他属性低或者高的。选择数量这里直接配的数字,也可以配成函数,就能支持技能等级的不同设置不同的目标数量了。

选完目标后,就是命中率和操作区的处理过程了,处理过程要配置成函数的,可以这样配:

命中率公式 附加状态 状态回合数 处理过程
500 att 1 扣除气血=伤害值*1/3

命中率可以配成函数,结果大于等于 1000 就是100% 命中, 500 就是 50% 的命中率。

附加状态就是这个状态就是给目标挂的一个状态,状态回合数就是状态的生存时间。

处理过程就是要执行的行动逻辑,比如这里配的扣除气血为伤害值的三分之一。

下面还需要一个配置表来配置状态和状态的触发时机,状态的触发时机是战斗逻辑的关键,先这样配状态表:

状态ID 状态名 触发时机
att 攻击 NONE
die 死亡 NONE
miankong_up 增加免控 SUBHP_ATTACK

触发时机一般有这几种,扣血时,被扣血时,死亡时等等。

总体来看,一个回合的战斗过程就是,释放技能,给目标加上某些状态,并执行处理过程改变某些属性,再在某些时机执行状态的效果。

上面配置 Excel 的过程中,支持直接配置中文的表达式,这里的导表工具是 Python 写的,表头是这样定义的 op3_att(xfun)(f_id,atk,target,status,damage)(技能变量)

这个表示第三个操作区的处理过程,类型是 xfun 支持中文的函数,f_id,atk,target,status,damage 是函数的参数, (技能变量) 是用来替换中文的 Sheet。技能变量表大概是这样的:

策划用名(不用翻译) 程序变量获取 程序变量设置 变量名
id(str) get(str) set(str) var(str)
A气血 fightd.get_attr_v(f_id, atk, 'hp') fightd.set_attr_tmp_v(f_id, atk, status, 'hp', $value) a_hp

解析表达式就是先用=切割表达式,有等号就是替换成 set 操作。比如 A气血=100 转成代码就是 fightd.set_attr_tmp_v(f_id, atk, status, 'hp', 100)

还可以用分号 ; 来切割支持多个表达式。具体如何实现就不贴代码了。

其实画点图可能更有助于理解,后续再补充吧。

点击进入评论 ...