feat: Started defining GOAP concepts
This commit is contained in:
parent
ad84556207
commit
844b905174
37
src/goap/action.cpp
Normal file
37
src/goap/action.cpp
Normal file
|
@ -0,0 +1,37 @@
|
|||
#include "action.hpp"
|
||||
|
||||
namespace goap {
|
||||
Action::Action(WorldState &requires, WorldState &effects)
|
||||
: requires{requires}, effects{effects} {}
|
||||
|
||||
WorldState const &Action::get_required() const {
|
||||
return this->requires;
|
||||
}
|
||||
|
||||
WorldState const &Action::get_effects() const {
|
||||
return this->effects;
|
||||
}
|
||||
|
||||
ActionID Action::get_id() const {
|
||||
return this->id;
|
||||
}
|
||||
|
||||
ActionData::ActionData(Action *action)
|
||||
: data{action} {}
|
||||
|
||||
ActionData::~ActionData() {
|
||||
if(this->data != nullptr)
|
||||
delete this->data;
|
||||
}
|
||||
}
|
||||
|
||||
bool TestAction::is_done_for(goap::ActorWorldState *) {
|
||||
return true;
|
||||
}
|
||||
bool TestAction::is_possible_for(goap::ActorWorldState *) {
|
||||
return true;
|
||||
}
|
||||
goap::State *TestAction::get_apply_state() const {
|
||||
return nullptr;
|
||||
}
|
||||
|
65
src/goap/action.hpp
Normal file
65
src/goap/action.hpp
Normal file
|
@ -0,0 +1,65 @@
|
|||
#ifndef GOAP_ACTION_HPP
|
||||
#define GOAP_ACTION_HPP
|
||||
|
||||
#include "actor_world_state.hpp"
|
||||
#include "state.hpp"
|
||||
#include <godot_cpp/variant/string.hpp>
|
||||
#include <godot_cpp/templates/vector.hpp>
|
||||
#include <godot_cpp/templates/hash_map.hpp>
|
||||
|
||||
#define GOAP_ACTION(Name_) \
|
||||
public: \
|
||||
_FORCE_INLINE_ static gd::String get_static_class() { return #Name_; }\
|
||||
_FORCE_INLINE_ gd::String get_class() const { return #Name_; }\
|
||||
private:
|
||||
|
||||
namespace goap {
|
||||
typedef int ActionID;
|
||||
|
||||
/*! A goap action construct.
|
||||
*
|
||||
* Child classes an be registered with `ActionDB::register_action`
|
||||
*/
|
||||
class Action {
|
||||
friend class ActionDB;
|
||||
GOAP_ACTION(Action);
|
||||
public:
|
||||
virtual ~Action() = default;
|
||||
|
||||
virtual bool is_possible_for(ActorWorldState *state) = 0;
|
||||
virtual bool is_done_for(ActorWorldState *state) = 0;
|
||||
virtual State *get_apply_state() const = 0;
|
||||
|
||||
WorldState const &get_required() const;
|
||||
WorldState const &get_effects() const;
|
||||
|
||||
ActionID get_id() const;
|
||||
protected:
|
||||
Action() = default;
|
||||
Action(WorldState &requires, WorldState &effects);
|
||||
private:
|
||||
WorldState requires{};
|
||||
WorldState effects{};
|
||||
ActionID id{-1};
|
||||
};
|
||||
|
||||
/* Storage wrapper for managed action class.
|
||||
*/
|
||||
struct ActionData {
|
||||
ActionData() = default;
|
||||
ActionData(Action *action);
|
||||
~ActionData();
|
||||
Action *data{nullptr};
|
||||
};
|
||||
}
|
||||
|
||||
class TestAction : public goap::Action {
|
||||
GOAP_ACTION(TestAction);
|
||||
public:
|
||||
TestAction() = default;
|
||||
virtual bool is_done_for(goap::ActorWorldState *) override;
|
||||
virtual bool is_possible_for(goap::ActorWorldState *) override;
|
||||
virtual goap::State *get_apply_state() const override;
|
||||
};
|
||||
|
||||
#endif // !GOAP_ACTION_HPP
|
36
src/goap/action_db.cpp
Normal file
36
src/goap/action_db.cpp
Normal file
|
@ -0,0 +1,36 @@
|
|||
#include "action_db.hpp"
|
||||
|
||||
namespace goap {
|
||||
StaticString::~StaticString() {
|
||||
if(this->ptr != nullptr)
|
||||
delete this->ptr;
|
||||
}
|
||||
|
||||
gd::String &StaticString::get() {
|
||||
if(this->ptr == nullptr)
|
||||
this->ptr = new gd::String();
|
||||
return *this->ptr;
|
||||
}
|
||||
|
||||
ActionID ActionDB::register_action(Action *instance, gd::String name) {
|
||||
instance->id = ActionDB::actions.size();
|
||||
if(!ActionDB::hint.get().is_empty())
|
||||
ActionDB::hint.get() += ",";
|
||||
ActionDB::hint.get() += name;
|
||||
ActionDB::actions.push_back(ActionData(instance));
|
||||
return instance->get_id();
|
||||
}
|
||||
|
||||
Action const *ActionDB::get_action(ActionID index) {
|
||||
if(ActionDB::actions.size() >= index || index < 0)
|
||||
return nullptr;
|
||||
return ActionDB::actions[index].data;
|
||||
}
|
||||
|
||||
gd::String const &ActionDB::get_enum_hint() {
|
||||
return ActionDB::hint.get();
|
||||
}
|
||||
|
||||
gd::Vector<ActionData> ActionDB::actions{gd::Vector<ActionData>()};
|
||||
StaticString ActionDB::hint{};
|
||||
}
|
55
src/goap/action_db.hpp
Normal file
55
src/goap/action_db.hpp
Normal file
|
@ -0,0 +1,55 @@
|
|||
#ifndef GOAP_ACTION_DB_HPP
|
||||
#define GOAP_ACTION_DB_HPP
|
||||
|
||||
#include "action.hpp"
|
||||
|
||||
namespace goap {
|
||||
struct StaticString {
|
||||
private:
|
||||
gd::String *ptr{nullptr};
|
||||
public:
|
||||
StaticString() = default;
|
||||
~StaticString();
|
||||
gd::String &get();
|
||||
};
|
||||
|
||||
/*! Global access to all (registered) Action types.
|
||||
*
|
||||
* Register them with the `register_action` function from `initialize_gdextension_types()`.
|
||||
* To represent an Action in the editor, the getter and setter should act as if the property is an int.
|
||||
* The property should then be registered as an enum with `get_enum_hint()` as hint.
|
||||
* Inside the getter, `Action->get_id()` can be returned. While the setter assigns the underlying value to `ActionDB::get_action(id)`.
|
||||
* ```
|
||||
* void Example::set_action(int id) {
|
||||
* this->action = ActionDB::get_action(id);
|
||||
* }
|
||||
* int Example::get_action() const {
|
||||
* return this->action->get_id();
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
class ActionDB {
|
||||
protected:
|
||||
static ActionID register_action(Action *instance, gd::String name);
|
||||
public:
|
||||
/*! Get action by ID.
|
||||
*/
|
||||
static Action const *get_action(ActionID id);
|
||||
/*! Get the csv godot enum hint.
|
||||
*/
|
||||
static gd::String const &get_enum_hint();
|
||||
/*! Add action to database.
|
||||
*
|
||||
* \returns Action's assigned ID.
|
||||
*/
|
||||
template<class TAction>
|
||||
_FORCE_INLINE_ static ActionID register_action() {
|
||||
return ActionDB::register_action(new TAction(), TAction::get_static_class());
|
||||
}
|
||||
private:
|
||||
static gd::Vector<ActionData> actions;
|
||||
static StaticString hint;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // !GOAP_ACTION_DB_HPP
|
7
src/goap/actor_world_state.cpp
Normal file
7
src/goap/actor_world_state.cpp
Normal file
|
@ -0,0 +1,7 @@
|
|||
#include "actor_world_state.hpp"
|
||||
|
||||
namespace goap {
|
||||
void ActorWorldState::_bind_methods() {
|
||||
#define CLASSNAME ActorWorldState
|
||||
}
|
||||
}
|
21
src/goap/actor_world_state.hpp
Normal file
21
src/goap/actor_world_state.hpp
Normal file
|
@ -0,0 +1,21 @@
|
|||
#ifndef GOAP_ACTOR_WORLD_STATE_HPP
|
||||
#define GOAP_ACTOR_WORLD_STATE_HPP
|
||||
|
||||
#include <godot_cpp/classes/node.hpp>
|
||||
#include <godot_cpp/templates/hash_map.hpp>
|
||||
#include <godot_cpp/templates/pair.hpp>
|
||||
#include <godot_cpp/variant/string_name.hpp>
|
||||
|
||||
namespace gd = godot;
|
||||
|
||||
namespace goap {
|
||||
typedef gd::HashMap<gd::StringName, gd::Variant> WorldState;
|
||||
typedef gd::KeyValue<gd::StringName, gd::Variant> WorldProperty;
|
||||
|
||||
class ActorWorldState : public gd::Node {
|
||||
GDCLASS(ActorWorldState, gd::Node);
|
||||
static void _bind_methods();
|
||||
};
|
||||
}
|
||||
|
||||
#endif // !GOAP_ACTOR_WORLD_STATE_HPP
|
24
src/goap/goal.hpp
Normal file
24
src/goap/goal.hpp
Normal file
|
@ -0,0 +1,24 @@
|
|||
#ifndef GOAP_GOAL_HPP
|
||||
#define GOAP_GOAL_HPP
|
||||
|
||||
#include "actor_world_state.hpp"
|
||||
#include <godot_cpp/classes/resource.hpp>
|
||||
|
||||
namespace gd = godot;
|
||||
|
||||
namespace goap {
|
||||
class Goal : public gd::Resource {
|
||||
GDCLASS(Goal, gd::Resource);
|
||||
static void _bind_methods();
|
||||
public:
|
||||
gd::Dictionary get_desired_state() const;
|
||||
void set_desired_state(gd::Dictionary dict);
|
||||
private:
|
||||
//! Do not select this goal unless all requirements are met.
|
||||
WorldState requirements;
|
||||
//! The state to find a path to.
|
||||
WorldState desired_state;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // !GOAP_GOAL_HPP
|
34
src/goap/planner.cpp
Normal file
34
src/goap/planner.cpp
Normal file
|
@ -0,0 +1,34 @@
|
|||
#include "planner.hpp"
|
||||
#include "goap/action_db.hpp"
|
||||
#include "utils/godot_macros.hpp"
|
||||
|
||||
namespace goap {
|
||||
void Planner::_bind_methods() {
|
||||
#define CLASSNAME Planner
|
||||
GDPROPERTY_HINTED(actions, gd::Variant::PACKED_INT32_ARRAY, gd::PROPERTY_HINT_ENUM, ActionDB::get_enum_hint());
|
||||
}
|
||||
|
||||
void Planner::_enter_tree() {
|
||||
this->world_state = this->get_node<ActorWorldState>("../ActorWorldState");
|
||||
}
|
||||
|
||||
Plan Planner::plan_for_goal(gd::Ref<Goal> goal) {
|
||||
return {};
|
||||
}
|
||||
|
||||
void Planner::set_actions(gd::PackedInt32Array array) {
|
||||
this->actions.clear();
|
||||
for(int id : array) {
|
||||
Action const *action = ActionDB::get_action(id);
|
||||
if(action != nullptr && !this->actions.has(action))
|
||||
this->actions.push_back(action);
|
||||
}
|
||||
}
|
||||
|
||||
gd::PackedInt32Array Planner::get_actions() const {
|
||||
gd::PackedInt32Array array{};
|
||||
for(Action const *action : this->actions)
|
||||
action->get_id();
|
||||
return array;
|
||||
}
|
||||
}
|
32
src/goap/planner.hpp
Normal file
32
src/goap/planner.hpp
Normal file
|
@ -0,0 +1,32 @@
|
|||
#ifndef GOAP_PLANNER_HPP
|
||||
#define GOAP_PLANNER_HPP
|
||||
|
||||
#include "action.hpp"
|
||||
#include "actor_world_state.hpp"
|
||||
#include "goal.hpp"
|
||||
#include "godot_cpp/variant/packed_int32_array.hpp"
|
||||
#include <godot_cpp/templates/vector.hpp>
|
||||
#include <godot_cpp/classes/node.hpp>
|
||||
|
||||
namespace gd = godot;
|
||||
|
||||
namespace goap {
|
||||
typedef gd::Vector<Action> Plan;
|
||||
|
||||
class Planner : public gd::Node {
|
||||
GDCLASS(Planner, gd::Node);
|
||||
static void _bind_methods();
|
||||
public:
|
||||
virtual void _enter_tree() override;
|
||||
|
||||
Plan plan_for_goal(gd::Ref<Goal> goal);
|
||||
|
||||
void set_actions(gd::PackedInt32Array array);
|
||||
gd::PackedInt32Array get_actions() const;
|
||||
private:
|
||||
ActorWorldState *world_state{nullptr};
|
||||
gd::Vector<Action const *> actions;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // !GOAP_PLANNER_HPP
|
32
src/goap/state.cpp
Normal file
32
src/goap/state.cpp
Normal file
|
@ -0,0 +1,32 @@
|
|||
#include "state.hpp"
|
||||
|
||||
namespace goap {
|
||||
void State::_bind_methods() {}
|
||||
|
||||
void MoveTo::_bind_methods() {}
|
||||
|
||||
void MoveTo::_enter_tree() {
|
||||
this->agent = this->get_node<gd::NavigationAgent3D>("../NavigationAgent3D");
|
||||
this->agent->set_target_position(this->target_node->get_global_position());
|
||||
this->parent_node3d = Object::cast_to<gd::Node3D>(this->get_parent());
|
||||
}
|
||||
|
||||
void MoveTo::_exit_tree() {
|
||||
this->agent->set_target_position(this->parent_node3d->get_global_position());
|
||||
}
|
||||
|
||||
void MoveTo::_process(double delta_time) {
|
||||
gd::Vector3 const pos = this->parent_node3d->get_global_position();
|
||||
gd::Vector3 const target = this->agent->get_next_path_position();
|
||||
gd::Vector3 const direction = (target - pos).normalized();
|
||||
this->parent_node3d->set_global_position(direction * delta_time);
|
||||
this->parent_node3d->look_at(pos - gd::Vector3{direction.x, 0.f, direction.z});
|
||||
|
||||
if(this->agent->is_navigation_finished())
|
||||
this->queue_free();
|
||||
}
|
||||
|
||||
void Activate::_bind_methods() {}
|
||||
|
||||
void Animate::_bind_methods() {}
|
||||
}
|
48
src/goap/state.hpp
Normal file
48
src/goap/state.hpp
Normal file
|
@ -0,0 +1,48 @@
|
|||
#ifndef GOAP_STATE_HPP
|
||||
#define GOAP_STATE_HPP
|
||||
|
||||
#include "godot_cpp/classes/navigation_agent3d.hpp"
|
||||
#include <godot_cpp/classes/node.hpp>
|
||||
#include <godot_cpp/classes/node3d.hpp>
|
||||
|
||||
namespace gd = godot;
|
||||
|
||||
namespace goap {
|
||||
class State : public gd::Node {
|
||||
GDCLASS(State, gd::Node);
|
||||
static void _bind_methods();
|
||||
};
|
||||
|
||||
/*! Uses navigation to chase the desired target node.
|
||||
*/
|
||||
class MoveTo : public State {
|
||||
GDCLASS(MoveTo, State);
|
||||
static void _bind_methods();
|
||||
public:
|
||||
virtual void _enter_tree() override;
|
||||
virtual void _exit_tree() override;
|
||||
virtual void _process(double delta_time) override;
|
||||
public:
|
||||
gd::Node3D *target_node{nullptr};
|
||||
private:
|
||||
gd::Node3D *parent_node3d{nullptr};
|
||||
gd::NavigationAgent3D *agent{nullptr};
|
||||
};
|
||||
|
||||
class Activate : public State {
|
||||
GDCLASS(Activate, State);
|
||||
static void _bind_methods();
|
||||
public:
|
||||
gd::Node3D *target_node{nullptr};
|
||||
gd::String animation{};
|
||||
};
|
||||
|
||||
class Animate : public State {
|
||||
GDCLASS(Animate, State);
|
||||
static void _bind_methods();
|
||||
public:
|
||||
gd::String animation{};
|
||||
};
|
||||
}
|
||||
|
||||
#endif // !GOAP_STATE_HPP
|
Loading…
Reference in a new issue