-1: 表示永续,其持有者不会因为时间的流逝而移除这类buff;
0: 表示即时buff,其持有者在当前step结束后立即移除这类buff;
正整数: 表示持有者自动丢弃buff的时间步,其持有者在经过这段step数之后,会移除这类buff(与0在语义上是连续的)。
这一分类主要考虑的是buff工作的周期(也即附加和移除的时间间隔)和生效的时间点。有两种,一种是Instant,一种是Delayed,Instant在附加buff的event中即刻生效,delayed则是在每个step结束生效,且可能有不同的移除时机。
这类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。
这类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是自定义的。
BuffWorkFunction决定了Buff生效时发挥的实际作用,也即对attribute做了什么样的修改。
std::function<void(GamePtr game, int buffID, const AttributeObjectPtr &target, const AreaPtr &area)>
game: 当前game接口。 buffID: buff的nameInt。 object: 持有者。 area: 当前区域。
这两种基本函数是与attribute交互的基础范式,自定义时可参考这两者设置attribute的形式。
为持有者的属性直接加上对应的值。这一改变直接写入持有者的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);
}
为持有者的属性附加一个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.
利用如下的代码:
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的附加要利用各种event。一般来说,常见的buff都是equip和consume附带的,更复杂的buff则可能需要自定义事件来引入。如果是equip和consume buff,请参阅Quick Start Guide,然后直接进入Step3; 如果认为equip和consume不够,需要自定义事件,请进入Step2。
原则上,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是一件比较麻烦的事情,如果有此需求,也可以直接提给我。
在config/6_buff.csv
中配置Duration即可。
如果是固定数值的,直接选用0或1;如果有规则,则应参考BuffWorkFunction自定义。
Metabolic对应的是现实中的新陈代谢,每个step结束时扣除Agent一定的饱食度和饥渴度。我们按照上面的步骤来看:
我们希望在游戏开始时而不是特定动作中附加给所有Agent,因而既不是consume也不是equip,需要在别的事件中定义。
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());
}
不移除
我们想让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的实例。