自定义规则指南

在我们的环境中,Buff是一种能够附加在AttributeObject的子类上的系统性效果(注:这里的系统性指的是Buff自身的规则属性不因持有者的改变而改变,因而不依赖于持有者而能自成一个系统),能够对持有者的attribute进行修改。一个典型的Buff工作流程是这样的:首先,特定的Event为一个AttributeObject子类实例附加一个Buff,此时持有者可以确认到自身当前持有这一Buff;然后,依据Buff自身的特性,将由当前或另一个Event调用其对应的BuffWorkFunction,从而生效,更改attribute;最后,Buff会被特定的Event移除,持有者在确认时会发现自己已经不再持有这一Buff。附加、生效、移除是三个典型的时间点,后面会反复提到。

Buff提供了用户自定义的可能性,用户可以自行编写Buff的作用函数BuffWorkFunction,使得Buff利用其持有者、或是环境的可访问信息,以用户期望的方式修改其持有者的attribute。这一特性要求开发者对Buff作用的机制有比较明确的了解,如果想要利用这一特性,应尽可能。本文也包含了一个quick start guide,如果不想自定义Buff作用函数,只需要增加一种新的consume buff或是equip buff,请直接参阅此部分内容。

本文的剩余部分将首先给出buff的configuration简介,然后介绍几种常见的buff类型和配置的实例,最后介绍BuffWorkFunction的作用机制和参数传递方式,并给出了一个自定义新BuffWorkFunction的流程。

快速使用指南

这两类buff的附加、生效、移除时机是已有的,只需要在特定的config文件中增加新的数值即可。

配置一种新的consume buff

※实例1:假设需要引入一种新的食物FoodA,消耗后立即获得3点HP和5点饥渴度:

config/4_item.csv中,以如下形式配置(与consume buff有关的内容已经粗体标黑,其他内容均为示例,只要符合配置规范即可):

ItemName NameInt EquipBuff ConsumeBuff Slot Durability Duration EquipDuration SynthesizeTable
FoodA 74751 null EatFoodA null -1 5:00 -1  

config/6_buff.csv中,以如下形式配置:

BuffName NameInt Enhance Duration Repeat BuffWorkFunction
EatFoodA 216 HP:3;Thirst:5 0 0 0

其中,4_item中的ConsumeBuff字段必须与6_buff中BuffName字段相同;Duration Repeat BuffWorkFunction在这种情形下设为0.

※实例2:假设需要引入一种新的食物foodB,消耗后在5个step内,每个step结束时增加3点HP:

config/4_item.csv中,以如下形式配置:

ItemName NameInt EquipBuff ConsumeBuff Slot Durability Duration EquipDuration SynthesizeTable
FoodB 449517 null EatFoodB null -1 5:00 -1  

config/6_buff.csv中,以如下形式配置:

BuffName NameInt Enhance Duration Repeat BuffWorkFunction
EatFoodB 729 HP:3 5 1 0

其中,Duration是持续发挥作用的step数,Repeat设置为1,BuffWorkFunction为0.

配置一种新的equip buff

假设需要引入一种新的装备,名为Hammer,装备后攻击力上升20,卸下时攻击力下降20.

config/4_item.csv中,以如下形式配置(与equip buff有关的内容已经粗体标黑,其他内容均为示例,只要符合配置规范即可):

ItemName NameInt EquipBuff ConsumeBuff Slot Durability Duration EquipDuration SynthesizeTable
Hammer 74751 EquipHammer null null 40 -1 -1  

config/6_buff.csv中,以如下形式配置:

BuffName NameInt Enhance Duration Repeat BuffWorkFunction
EquipHammer 128 ATK:20 -1 0 1

其中,4_item中的EquipBuff字段必须与6_buff中BuffName字段相同;一般来说,equip buff的Duration Repeat BuffWorkFunction依次为-1 0 1.

Buff config简介

config/6_buff.csv文件包含了buff的配置,其标准字段包括:

字段 类型 含义
BuffName string buff的名称。
NameInt int 系统调用时,引用的buff编号(本表格中必须是唯一的)。
Enhance dict-like (key[string]:value[float]) buff保存的固定字典变量,指定了特定attribute的enhance参数值;这一参数值的具体作用则要由BuffWorkFuntion来决定。
Duration int buff的默认持续时间
-1: 表示永续,其持有者不会因为时间的流逝而移除这类buff;
0: 表示即时buff,其持有者在当前step结束后立即移除这类buff;
正整数: 表示持有者自动丢弃buff的时间步,其持有者在经过这段step数之后,会移除这类buff(与0在语义上是连续的)。
Repeat bool true: 持有者在每个step结束时,调用一次BuffWorkFunction
false: 持有者仅在当个step调用一次BuffWorkFuntion
BuffWorkFunction array-like ([int(;float;float…)]) 第0位: BuffWorkFunction的种类, int型
第1~任意位: 根据BuffWorkFunction的需求,自定义的参数,float型。

Buff分类(按Duration与Repeat)与举例

这一分类主要考虑的是buff工作的周期(也即附加和移除的时间间隔)和生效的时间点。有两种,一种是Instant,一种是Delayed,Instant在附加buff的event中即刻生效,delayed则是在每个step结束生效,且可能有不同的移除时机。

Instant Buff

这类Buff, Duration与Repeat皆为0(事实上只需要Duration是0,即便Repeat是1也不会改变什么),代表立刻生效且不再有后续效果。 生效的时间点是:立即生效(附加的同时生效),工作周期是0(生效立即移除)。典型配置如:

BuffName NameInt Enhance Duration Repeat BuffWorkFunction
EatMeat 0 HP:10;Hunger:50 0 0 0

当agent吃了一块肉,其HP与Hunger分别上升10和50。BuffWorkFuntion应选用直接修改基础数值的0号函数BasicValueAdd(详见BuffWorkFunction)。在后端更新过程中,当某个event附加这类buff之时,后端会立即调用其对应的BuffWorkFunction,并随后移除该buff。

Delayed Buff

这类Buff的Duration不是0,代表在step结束时才生效,且经过一段step之后才会被移除。 生效的时间点是:每个step结束后(由AfterStepEvent管理),工作周期为Duration(-1为永久)。典型配置如:

BuffName NameInt Enhance Duration Repeat BuffWorkFunction
WeaponBranch 7 ATK:10;AttackDistance:2 -1 0 1
EatIce 4 Temperature:-2;Thirst:4 5 1 0
Metabolic 16 Hunger:1;Thirst:2 -1 1 2;50;1;10;1

当agent装备上Branch,其ATK和AttackDistance仅在装备期间上升10和2。BuffWorkFunction应选用修改offset值的1号函数OffsetValueAdd(详见BuffWorkFunction)。这一Buff并不由时间管理,所以Duration是-1。

当agent吃了Ice之后,其在5个step内,Temperature每step-2,thirst每step+4。BuffWorkFunction应选用修改基础数值的0号函数。

Metabolic是特殊的Buff,其在每个step都会发生作用,且永不解除。BuffWorkFunction是自定义的。

BuffWorkFuntion

BuffWorkFunction决定了Buff生效时发挥的实际作用,也即对attribute做了什么样的修改。

接口格式

std::function<void(GamePtr game, int buffID, const AttributeObjectPtr &target, const AreaPtr &area)>

game: 当前game接口。 buffID: buff的nameInt。 object: 持有者。 area: 当前区域。

基本函数(2种)

这两种基本函数是与attribute交互的基础范式,自定义时可参考这两者设置attribute的形式。

BasicValueAdd

为持有者的属性直接加上对应的值。这一改变直接写入持有者的attribute值,移除buff之后,不会取消原有的效果。一般来说,consume带来的buff一般会选用这一种work function。

void BasicValueAdd(GamePtr game, int buffID, const AttributeObjectPtr& target, const AreaPtr &area)
{
    auto buff = dynamic_pointer_cast<Buff>(game->getFactory()->getTemplate(game->getConfig()->intToName(Eden::Type::BUFF, buffID)));
    for (auto &iter: buff->enhance)
        target->setAttribute(iter.first, target->getAttribute(iter.first) + iter.second);
}

OffsetValueAdd

为持有者的属性附加一个offset。这一改变写入持有者attribute对应的offset,移除buff之后,取消原有的效果。一般来说,equip带来的buff一般会选用这一种work function。

void OffsetValueAdd(GamePtr game, int buffID, const AttributeObjectPtr& target, const AreaPtr &area)
{
    auto buff = dynamic_pointer_cast<Buff>(game->getFactory()->getTemplate(game->getConfig()->intToName(Eden::Type::BUFF, buffID)));
    for (auto &iter: buff->enhance)
        target->addBuffDelta(buffID, iter.first, iter.second);
}

自定义函数指南

自定义函数均位于src/object/buff/BuffWorkFunction.cpp中,请于此处编写符合接口要求的函数,并在BuffWorkFunction的构造函数中将其置入。其在config/6_buff.csv中BuffWorkFunction字段的第一位数值即为BuffWorkFunction的构造函数中插入的顺序编号(从0开始)。以下是一些Tips.

怎么根据buffID获取buff指针?

利用如下的代码:

auto buff = dynamic_pointer_cast<Buff>(game->getFactory()->getTemplate(game->getConfig()->intToName(Eden::Type::BUFF, buffID)));

Buff是全局的,只保存一个template,通过名称访问即可。

怎么访问/修改持有者现有的属性?

BuffWorkFunction的2号参数const AttributeObjectPtr& target是持有者的AttributeObject类型指针,可以通过这个指针得到需要的属性数值和增加了offset的属性数值,并修改这些数值。

auto hunger = target->getAttribute("Hunger");       // 访问名为Hunger的属性基本数值
auto buffedATK = target->getBuffedAttribute("ATK"); // 访问名为ATK的buff后属性数值
target->setAttribute("Hunger", hunger+10);          // 设置Hunger的属性数值为当前hunger值+10
target->addBuffDelta(buffID, "ATK", 5);             // 给ATK加上5的offset,该数值在getBuffedAttribute中生效,													// buff移除后该offset消失

怎么访问地图信息?

BuffWorkFunction拥有当前的区域指针const AreaPtr &area,可以访问当前地图上的信息,例如指定位置是否有某些物体。

obj = area->getMap()->getObject(5,5);
bool excited = (obj != nullptr && obj->getName() == "Gannon"); // 如果地图(5,5)位置有物体且名为Gannon,
                                                               // excited置为true

怎么访问动作的结果?

BuffWorkFunction拥有当前的游戏指针GamePtr game,可以访问持有者上次动作的结果。

auto res = game->agentResult()[target->getID()]; //获取target上次动作的结果

自定义函数的参数怎么传递和访问?

目前支持任意数量的float型参数。在config/6_buff.csv的BuffWorkFunction字段中,首位表示类型,后续可以加任意数量的float类型参数,以分号分隔。之后,在buff->buffParam中可以访问到这些参数。

BuffName NameInt Enhance Duration Repeat BuffWorkFunction
Metabolic 16 Hunger:1;Thirst:2 -1 1 2;50;1;10;1

例如上述的Metabolic中,通过buff->buffParam得到的 vector数值是{50, 1, 10, 1}。

完全自定义Buff指南

Step1: 我的Buff怎么附加?

Buff的附加要利用各种event。一般来说,常见的buff都是equip和consume附带的,更复杂的buff则可能需要自定义事件来引入。如果是equip和consume buff,请参阅Quick Start Guide,然后直接进入Step3; 如果认为equip和consume不够,需要自定义事件,请进入Step2。

Step2: 我的Buff在何时生效?

原则上,Buff生效只有两个时间点,一种是Duration为0的Instant Buff,一旦附加即生效;一种是Duration不为0的Delayed Buff,附加之后,只在step结束时生效。设置Duration即可修改生效时机,决定好生效时机之后,就可以开始自定义事件了。

请在src/event文件夹下自定义您的新事件。一个事件要求继承基类Event,并强制要求初始化基类的构造函数:

Event::Event(const string &eventName, float beginStep, const AreaPtr &area)

eventName是事件的名称,beginStep是事件触发的step,area是区域指针。然后,要求override execute函数:

virtual EventPtr execute()=0;

每个Event要求返回另一个Event的指针,允许是Success/Failure这类只表示结果的DummyEvent,也可以是任意的其他Event。

如果想要为Instant Buff创建附加事件,这个事件一般是agent主动的action,建议放在action文件夹;可以参考src/event/action/ConsumeEvent.cpp,核心功能应形如:

_applicatorAttrPtr->addBuff(buffID, getBeginStep());
_applicatorAttrPtr->instantBuffWork(getBeginStep(), _area);

然后,在src/event/EventCreator.cpp的switch语句中,参考其他事件,注册该事件的入口,允许Agent创建该事件(请留意序号的对应关系)。

如果是为Delayed Buff创建附加事件,这一事件一般是game所管理的,例如地图某一区域上带有的Buff需要在step结束后根据当前的state来判定,建议放在game文件夹。事实上,如果是游戏开始时直接附加一个buff,完全可以在src/event/game/AreaInitEvent.cpp直接增加;如果是step结束时条件判定,则可以在src/event/game/AfterStepEvent.cpp增加。另外的时间点则需要在写完功能之后,在src/game/Game.cpp的Game::update函数中手动选择一个合适的时机,将这一事件推入事件队列中。

注:引入新的Event是一件比较麻烦的事情,如果有此需求,也可以直接提给我。

Step3: 我的Buff在何时移除?

config/6_buff.csv中配置Duration即可。

Step4: 我的BuffWorkFunction要选用什么?

如果是固定数值的,直接选用0或1;如果有规则,则应参考BuffWorkFunction自定义。

Existing Example: Metabolic

Metabolic对应的是现实中的新陈代谢,每个step结束时扣除Agent一定的饱食度和饥渴度。我们按照上面的步骤来看:

1. 怎么附加

我们希望在游戏开始时而不是特定动作中附加给所有Agent,因而既不是consume也不是equip,需要在别的事件中定义。

2. 生效时机

Metabolic应该在每个step结束时生效,是一个持续时间无限长的Delayed Buff。决定好这一点之后,我们认为Metabolic应该在游戏开始的事件中附加给所有Agent,因此我们需要在src/event/game/AreaInitEvent.cpp的execute函数中写入这样的逻辑:

for (auto &agent: iter.second) {
    auto attrBeing = dynamic_pointer_cast<AttributeObject>(agent);
    attrBeing->addBuff(_game->getConfig()->nameToInt("Metabolic"), getBeginStep());
}

3. 移除

不移除

4. BuffWorkFunction

我们想让Agent高温多掉饥渴度,低温多掉饱食度,也就是说我们有一个分段函数: \(f(thirst)=\begin{cases} &t_b &\text{ if } x>\tau_1 \\ &t_b+t_{off} &\text{ if } x\leq\tau_1 \end{cases}, g(hunger)=\begin{cases} &h_b &\text{ if } x<\tau_2 \\ &h_b+h_{off} &\text{ if } x\geq\tau_2 \end{cases}\) 其中$t_b$, $h_b$是常态下的饱食度与饥渴度消耗,$t_{off}$, $h_{off}$是异常状态下增加的消耗,$\tau_1$, $\tau_2$是高低温的判定阈值。因此,我们将常态消耗作为enhance,另外四个数值作为BuffWorkFunction的固有参数,可以这样写BuffWorkFunction(位于src/object/buff/BuffWorkFunction.cpp):

void MetabolicConsume(GamePtr game, int buffID, const AttributeObjectPtr& target, const AreaPtr &area)
{
    auto buff = dynamic_pointer_cast<Buff>(game->getFactory()->getTemplate(game->getConfig()->intToName(Eden::Type::BUFF, buffID)));
    auto enhance = buff->enhance;
    auto buffParam = buff->buffParam;
    float basicHungerConsumption = enhance["Hunger"];
    float basicThirstConsumption = enhance["Thirst"];
    target->setAttribute("Hunger", target->getAttribute("Hunger") - basicHungerConsumption);
    target->setAttribute("Thirst", target->getAttribute("Thirst") - basicThirstConsumption);
    if (buffParam.size() != 4)
    {
        ERROR("Metabolic Consume Param Format Check Fail")
        return;
    }
    float temperature  = target->getAttribute("Temperature");
    float thirstHighTh = buffParam[0];
    float thirstDelta  = buffParam[1];
    float hungerLowTh  = buffParam[2];
    float hungerDelta  = buffParam[3];
    if (temperature >= thirstHighTh)
        target->setAttribute("Thirst", target->getAttribute("Thirst") - thirstDelta);
    if (temperature <= hungerLowTh)
        target->setAttribute("Hunger", target->getAttribute("Hunger") - hungerDelta);
}

config/6_buff.csv中如下配置参数:

BuffName NameInt Enhance Duration Repeat BuffWorkFunction
Metabolic 16 Hunger:1;Thirst:2 -1 1 2;50;1;10;1

这样,我们就完成了一个自定义Buff的实例。