Я пытаюсь внедрить GOAP в свою игру. Игра представляет собой игру типа симулятора, которая не только выиграет от GOAP, но и почти по существу является требованием, основанным на объеме того, что может делать агент.
У меня есть следующий код. Здесь нет планировщика, только определения действий, состояния и т. д.
#include <vector>
#include <string>
#include <unordered_map>
class Condition
{
public:
bool operator()(const int value) const
{
return m_min <= value && value <= m_max;
}
enum class OP : std::uint8_t
{
EE, LT, GT, LE, GE
};
Condition(const int min, const int max, const OP op)
:
m_op(op),
m_min(min),
m_max(max)
{}
static Condition CreateEE(const int goal)
{
return Condition(goal, goal, OP::EE);
}
static Condition CreateGT(const int goal)
{
return Condition(goal+1, std::numeric_limits<int>::max(), OP::GT);
}
static Condition CreateLT(const int goal)
{
return Condition(std::numeric_limits<int>::min(), goal-1, OP::LT);
}
static Condition CreateGE(const int goal)
{
return Condition(goal, std::numeric_limits<int>::max(), OP::GT);
}
static Condition CreateLE(const int goal)
{
return Condition(std::numeric_limits<int>::min(), goal, OP::LT);
}
private:
OP m_op;
int m_min;
int m_max;
};
class Effect
{
public:
enum class OP : std::uint8_t
{
ADD, SUB, MUL, DIV, EQ
};
Effect(OP op, int value)
:
m_op(op),
m_value(value)
{
}
protected:
OP m_op;
int m_value;
};
class Action
{
public:
void AddPrereq(const std::string& name, Condition value)
{
m_prereqs.emplace_back(name,value);
}
void AddEffect(const std::string& name, Effect effect)
{
m_effects.emplace_back(name,effect);
}
void SetCost(const float cost)
{
m_cost = cost;
}
private:
float m_cost;
std::vector<std::pair<std::string,Condition>> m_prereqs;
std::vector<std::pair<std::string,Effect>> m_effects;
};
class WorldState
{
public:
void AddFact(const std::string& name, const int value)
{
m_facts[name] = value;
}
private:
std::unordered_map<std::string,int> m_facts;
};
class GoalState
{
public:
void AddCondition(const std::string& name, const Condition condition)
{
m_conditions.emplace_back(name, condition);
}
private:
std::vector<std::pair<std::string,Condition>> m_conditions;
};
int main()
{
Action goTo;
goTo.AddEffect("isAtPosition", Effect(Effect::OP::EQ, 1));
Action pickUp;
pickUp.AddPrereq("isAtPosition", Condition::CreateEE(1));
pickUp.AddEffect("itemInPossession", Effect(Effect::OP::EQ, 1));
Action drop;
drop.AddPrereq("itemInPossession", Condition::CreateEE(1));
drop.AddEffect("itemAtPosition", Effect(Effect::OP::EQ, 1));
WorldState ws;
ws.AddFact("itemAtPosition", 0);
GoalState gs;
gs.AddCondition("itemAtPosition", Condition::CreateGE(1));
return 0;
}
Пока я просто использую логические значения, потому что в моем тестовом примере агент перемещается в позицию, берет предмет и перемещает его в другую позицию. Позже целью может быть перемещение n элементов или что-то в этом роде.
Действия будут такими:
- Гото (ставит агента на место)
- PickUp (передаёт предмет во владение)
- Гото (ставит агента на место)
- Бросить (кладет предмет на место) Цель достигнута!
Но как мне использовать здесь действие Goto
дважды? Эффект такой же, установка переменной "isAtPosition". Нужно ли мне создавать новую переменную состояния для «isAtItemPosition» и «isAtDestinationPosition»?
Потому что мне кажется, что я эффективно разрабатываю конкретные Действия для каждой возможной Цели, в результате чего я определяю все возможные последовательности Действий.
Как закодировать информацию о состоянии таким образом, чтобы одни и те же действия из пула могли применяться на разных этапах, давая разный эффект? (Перейти к позиции элемента и перейти к позиции назначения).