feat: fully implemented click-to-move
This commit is contained in:
parent
1ddfa17659
commit
8cfbbcce27
24 changed files with 684 additions and 90 deletions
|
|
@ -9,8 +9,8 @@
|
|||
|
||||
#define GOAP_ACTION(Name_) \
|
||||
public: \
|
||||
_FORCE_INLINE_ static gd::String get_static_class() { return #Name_; }\
|
||||
_FORCE_INLINE_ virtual gd::String get_class() const override { return #Name_; }\
|
||||
_FORCE_INLINE_ static gd::StringName get_static_class() { return #Name_; }\
|
||||
_FORCE_INLINE_ virtual gd::StringName get_class() const override { return #Name_; }\
|
||||
private:
|
||||
|
||||
namespace goap {
|
||||
|
|
@ -23,11 +23,11 @@ typedef int ActionID;
|
|||
class Action {
|
||||
friend class ActionDB;
|
||||
public:
|
||||
static gd::String get_static_class() { return "Action"; }
|
||||
virtual gd::String get_class() const { return "Action"; }
|
||||
static gd::StringName get_static_class() { return "Action"; }
|
||||
virtual gd::StringName get_class() const { return "Action"; }
|
||||
|
||||
virtual ~Action();
|
||||
virtual State *get_apply_state() const = 0;
|
||||
virtual State *get_apply_state(ActorWorldState *context) const = 0;
|
||||
|
||||
bool is_completed(ActorWorldState *context) const;
|
||||
bool is_possible(ActorWorldState *context) const;
|
||||
|
|
@ -38,7 +38,8 @@ public:
|
|||
ActionID get_id() const;
|
||||
protected:
|
||||
Action() = default;
|
||||
|
||||
template<class TState>
|
||||
TState *create_state() const;
|
||||
virtual bool procedural_is_possible(ActorWorldState *context) const;
|
||||
virtual bool procedural_is_completed(ActorWorldState *context) const;
|
||||
protected:
|
||||
|
|
@ -49,6 +50,13 @@ protected:
|
|||
private:
|
||||
ActionID id{-1};
|
||||
};
|
||||
|
||||
template <class TState>
|
||||
TState *Action::create_state() const {
|
||||
TState *state = memnew(TState);
|
||||
state->action = this;
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
#endif // !GOAP_ACTION_HPP
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
#include "planner.hpp"
|
||||
#include "action_db.hpp"
|
||||
#include "godot_cpp/classes/engine.hpp"
|
||||
#include "godot_cpp/classes/global_constants.hpp"
|
||||
#include "godot_cpp/templates/hashfuncs.hpp"
|
||||
#include "utils/godot_macros.hpp"
|
||||
|
|
@ -106,49 +107,58 @@ Plan Planner::plan_for_goal(gd::Ref<Goal> goal) {
|
|||
return {};
|
||||
}
|
||||
gd::Vector<WorldStateNode> open{goal_node};
|
||||
NodeMap from{};
|
||||
NodeScoreMap best_path_cost{};
|
||||
NodeScoreMap heuristic_cost{};
|
||||
NodeMap from{}; // mapping nodes to the next node in the plan
|
||||
NodeScoreMap best_path_cost{}; // map nodes to the cost of the best path found so far
|
||||
NodeScoreMap heuristic_cost{}; // map nodes to their approximate cost to get to their goal
|
||||
|
||||
// insert with the goal node into the cost maps,
|
||||
// path scored 0 as it's the goal
|
||||
best_path_cost.insert(goal_node, 0.f);
|
||||
heuristic_cost.insert(goal_node, goal_node.requirements_unmet());
|
||||
|
||||
WorldStateNode current{goal_node};
|
||||
// repeat until current has no traversible edges
|
||||
while(!open.is_empty()) {
|
||||
current = open.get(0);
|
||||
// return found path when all requirements are met
|
||||
if(current.requirements_unmet() == 0)
|
||||
return this->unroll_plan(current, from);
|
||||
open.remove_at(0);
|
||||
open.remove_at(0); // pop current off the top of the open set
|
||||
// get all actions that contribute to the open requirements of `current`
|
||||
gd::Vector<Action const *> edges{this->get_neighbours(current)};
|
||||
gd::UtilityFunctions::print("found ", edges.size(), " possible actions");
|
||||
for(Action const * action : edges) {
|
||||
// construct the node produced by traversing edge
|
||||
WorldStateNode node_through{current, action};
|
||||
// calculate the cost of getting here from current through action.
|
||||
float const path_cost{best_path_cost.get(current) + 1.f};
|
||||
gd::UtilityFunctions::print("hashes ", WorldStateNodeHasher::hash(node_through), " ; ", WorldStateNodeHasher::hash(current));
|
||||
gd::UtilityFunctions::print("equiv: ", current == node_through);
|
||||
// store only if path through `current` represents a new fastest way to get to the new node
|
||||
if(!best_path_cost.has(node_through) || best_path_cost.get(node_through) >= path_cost) {
|
||||
gd::UtilityFunctions::print("node through action ", action->get_class(), " added to open set");
|
||||
// update cheapest route found
|
||||
best_path_cost[node_through] = path_cost;
|
||||
// approximate cost to goal
|
||||
heuristic_cost[node_through] = path_cost + node_through.requirements_unmet();
|
||||
// store path to get to this point
|
||||
from[node_through] = current;
|
||||
open.erase(node_through);
|
||||
// (re-) insert node according to priority based on unmet requirements
|
||||
if(open.has(node_through))
|
||||
open.erase(node_through);
|
||||
open.ordered_insert(node_through);
|
||||
} else {
|
||||
gd::UtilityFunctions::print("node through action ", action->get_class(), " scores worse than previous at same state. ", path_cost, " vs ", best_path_cost[node_through]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
gd::UtilityFunctions::print("failed to find plan for goal ", goal->get_path());
|
||||
gd::UtilityFunctions::push_warning("failed to find plan for goal ", goal->get_path());
|
||||
return {};
|
||||
}
|
||||
|
||||
void Planner::set_actions(gd::Array array) {
|
||||
this->actions.clear();
|
||||
for(size_t i = 0; i < array.size(); ++i) {
|
||||
Action const *action = ActionDB::get_action(array[i]);
|
||||
Action const *action = int(array[i]) < 0 ? nullptr : ActionDB::get_action(array[i]);
|
||||
if(action != nullptr && !this->actions.has(action)) {
|
||||
this->actions.push_back(action);
|
||||
} else if(gd::Engine::get_singleton()->is_editor_hint() && !this->actions.has(nullptr)) {
|
||||
this->actions.push_back(nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -156,7 +166,7 @@ void Planner::set_actions(gd::Array array) {
|
|||
gd::Array Planner::get_actions() const {
|
||||
gd::Array array{};
|
||||
for(Action const *action : this->actions)
|
||||
array.push_back(action->get_id());
|
||||
array.push_back(action == nullptr ? -1 : action->get_id());
|
||||
return array;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@
|
|||
#include <cstdint>
|
||||
#include <godot_cpp/classes/node.hpp>
|
||||
#include <godot_cpp/templates/vector.hpp>
|
||||
#include <godot_cpp/variant/packed_int32_array.hpp>
|
||||
|
||||
namespace gd = godot;
|
||||
|
||||
|
|
@ -25,7 +24,7 @@ struct WorldStateNode {
|
|||
WorldStateNode(WorldStateNode const &last_state, Action const *last_action);
|
||||
~WorldStateNode() = default;
|
||||
int requirements_unmet() const;
|
||||
WorldState state{};
|
||||
WorldState state{}; // the only part considered in hashing
|
||||
WorldState open_requirements{};
|
||||
Action const *last_action{nullptr};
|
||||
ActorWorldState *context;
|
||||
|
|
|
|||
|
|
@ -1,32 +1,33 @@
|
|||
#include "state.hpp"
|
||||
#include "goap/action.hpp"
|
||||
#include "goap/actor_world_state.hpp"
|
||||
#include "utils/godot_macros.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 State::_bind_methods() {
|
||||
#define CLASSNAME State
|
||||
GDSIGNAL("end_state");
|
||||
}
|
||||
|
||||
void MoveTo::_exit_tree() {
|
||||
this->agent->set_target_position(this->parent_node3d->get_global_position());
|
||||
void State::_enter_tree() {
|
||||
this->world_state = this->get_node<ActorWorldState>("../ActorWorldState");
|
||||
}
|
||||
|
||||
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 State::_process(double delta_time) {
|
||||
if(this->action->is_completed(this->world_state)) {
|
||||
this->end_state();
|
||||
}
|
||||
}
|
||||
|
||||
void Activate::_bind_methods() {}
|
||||
|
||||
void Animate::_bind_methods() {}
|
||||
Action const *State::get_action() const {
|
||||
return this->action;
|
||||
}
|
||||
|
||||
void State::_end_state() {}
|
||||
|
||||
void State::end_state() {
|
||||
this->_end_state();
|
||||
this->queue_free();
|
||||
this->emit_signal("end_state");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,47 +1,29 @@
|
|||
#ifndef GOAP_STATE_HPP
|
||||
#define GOAP_STATE_HPP
|
||||
|
||||
#include "godot_cpp/classes/navigation_agent3d.hpp"
|
||||
#include "goap/actor_world_state.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();
|
||||
};
|
||||
class Action;
|
||||
|
||||
/*! Uses navigation to chase the desired target node.
|
||||
*/
|
||||
class MoveTo : public State {
|
||||
GDCLASS(MoveTo, State);
|
||||
class State : public gd::Node {
|
||||
friend class Action;
|
||||
GDCLASS(State, gd::Node);
|
||||
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};
|
||||
protected:
|
||||
Action const *get_action() const;
|
||||
virtual void _end_state();
|
||||
void end_state();
|
||||
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{};
|
||||
ActorWorldState *world_state{nullptr};
|
||||
Action const *action{nullptr};
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue