feat: implemented last bits required to create a 'follow player' goal
This commit is contained in:
parent
540d91ddcf
commit
ea730d61b4
|
@ -1,4 +1,4 @@
|
|||
[gd_scene load_steps=12 format=3 uid="uid://dpda341t6ipiv"]
|
||||
[gd_scene load_steps=9 format=3 uid="uid://dpda341t6ipiv"]
|
||||
|
||||
[sub_resource type="Curve" id="Curve_7rmf4"]
|
||||
min_value = 0.2
|
||||
|
@ -18,38 +18,21 @@ albedo_color = Color(0.94902, 0.909804, 0, 1)
|
|||
[sub_resource type="BoxMesh" id="BoxMesh_f5yvh"]
|
||||
size = Vector3(0.125, 0.14, 0.94)
|
||||
|
||||
[sub_resource type="MoveStateArgs" id="MoveStateArgs_752r2"]
|
||||
argument_property = &"target_position"
|
||||
[sub_resource type="MoveStateArgs" id="MoveStateArgs_ibmkn"]
|
||||
argument_property = &"player_character"
|
||||
|
||||
[sub_resource type="Action" id="Action_ksl64"]
|
||||
[sub_resource type="Action" id="Action_gtisq"]
|
||||
effects = {
|
||||
"prereq": true
|
||||
"is_near_player": true
|
||||
}
|
||||
apply_state = SubResource("MoveStateArgs_752r2")
|
||||
|
||||
[sub_resource type="Action" id="Action_mdru6"]
|
||||
effects = {
|
||||
"not_goal": 1
|
||||
}
|
||||
|
||||
[sub_resource type="AnimateStateArgs" id="AnimateStateArgs_tlart"]
|
||||
argument_property = &"fire_weapon"
|
||||
|
||||
[sub_resource type="Action" id="Action_7v1i5"]
|
||||
prerequisites = {
|
||||
"prereq": true
|
||||
}
|
||||
effects = {
|
||||
"goal": true
|
||||
}
|
||||
apply_state = SubResource("AnimateStateArgs_tlart")
|
||||
apply_state = SubResource("MoveStateArgs_ibmkn")
|
||||
|
||||
[sub_resource type="Goal" id="Goal_sqtwb"]
|
||||
goal_state = {
|
||||
"target_dead": true
|
||||
"is_near_player": true
|
||||
}
|
||||
prerequisites = {
|
||||
"has_target": true
|
||||
"is_near_player": false
|
||||
}
|
||||
|
||||
[node name="PlayerCharacter" type="CharacterActor"]
|
||||
|
@ -83,5 +66,5 @@ surface_material_override/0 = SubResource("StandardMaterial3D_scmx3")
|
|||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.53551, 0.931313, 0)
|
||||
|
||||
[node name="Planner" type="Planner" parent="."]
|
||||
actions = [SubResource("Action_ksl64"), SubResource("Action_mdru6"), SubResource("Action_7v1i5")]
|
||||
actions = [SubResource("Action_gtisq")]
|
||||
goals = [SubResource("Goal_sqtwb")]
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
#include "character_actor.hpp"
|
||||
#include "planner.hpp"
|
||||
#include "projectile_pool.hpp"
|
||||
#include "state.hpp"
|
||||
#include "tunnels_game_mode.hpp"
|
||||
#include "utils/game_root.hpp"
|
||||
#include "utils/godot_macros.h"
|
||||
#include <cmath>
|
||||
#include <godot_cpp/classes/navigation_agent3d.hpp>
|
||||
|
@ -12,6 +16,8 @@ void CharacterActor::_bind_methods() {
|
|||
#define CLASSNAME CharacterActor
|
||||
GDPROPERTY_HINTED(rotation_speed_curve, Variant::OBJECT, PROPERTY_HINT_RESOURCE_TYPE, "Curve");
|
||||
GDFUNCTION_ARGS(set_velocity_target, "value");
|
||||
GDFUNCTION(get_is_near_player);
|
||||
GDFUNCTION(get_player_character);
|
||||
}
|
||||
|
||||
void CharacterActor::_enter_tree() { GDGAMEONLY();
|
||||
|
@ -20,12 +26,14 @@ void CharacterActor::_enter_tree() { GDGAMEONLY();
|
|||
this->target_rotation = this->get_global_transform().get_basis().get_quaternion();
|
||||
this->health = this->get_node<Health>("Health");
|
||||
this->primary_weapon_pool = this->get_node<ProjectilePool>("ProjectilePool");
|
||||
this->planner = this->get_node<goap::Planner>("Planner");
|
||||
}
|
||||
|
||||
void CharacterActor::_process(double delta_time) { GDGAMEONLY();
|
||||
this->process_rotation(delta_time);
|
||||
if(!this->mode_manual) {
|
||||
this->process_ai(delta_time);
|
||||
this->process_behaviour(delta_time);
|
||||
this->process_navigation(delta_time);
|
||||
}
|
||||
if(this->firing) {
|
||||
this->try_fire_weapon();
|
||||
|
@ -83,6 +91,7 @@ void CharacterActor::set_manual_mode(bool value) {
|
|||
ProcessMode const mode = value ? ProcessMode::PROCESS_MODE_DISABLED : ProcessMode::PROCESS_MODE_PAUSABLE;
|
||||
//this->nav_agent->set_process_mode(mode);
|
||||
this->nav_agent->set_avoidance_priority(value ? 1.f : 0.9f);
|
||||
this->set_state(goap::State::new_invalid());
|
||||
}
|
||||
|
||||
void CharacterActor::set_rotation_speed_curve(Ref<Curve> curve) {
|
||||
|
@ -119,10 +128,54 @@ Vector3 CharacterActor::get_velocity_target() const {
|
|||
return this->velocity_target;
|
||||
}
|
||||
|
||||
void CharacterActor::process_ai(double delta_time) {
|
||||
float const distance = this->nav_agent->get_target_position().distance_squared_to(this->get_global_position());
|
||||
float const target_distance_sqr = std::pow(this->nav_agent->get_target_desired_distance(), 2.f);
|
||||
if(!this->nav_agent->is_navigation_finished() && distance >= target_distance_sqr) {
|
||||
bool CharacterActor::get_is_near_player() const {
|
||||
return this->get_player_character()->get_global_position().distance_to(this->get_global_position()) < 5.f;
|
||||
}
|
||||
|
||||
CharacterActor *CharacterActor::get_player_character() const {
|
||||
Ref<TunnelsGameMode> game_mode = GameRoot::get_singleton()->get_game_mode();
|
||||
return game_mode->get_player_instance()->get_character();
|
||||
}
|
||||
|
||||
void CharacterActor::set_state(goap::State state) {
|
||||
switch(this->current_state.type) {
|
||||
default:
|
||||
break;
|
||||
case goap::State::STATE_MOVE_TO:
|
||||
this->nav_agent->set_target_position(this->get_global_position());
|
||||
break;
|
||||
}
|
||||
this->current_state = state;
|
||||
switch(state.type) {
|
||||
default:
|
||||
break;
|
||||
case goap::State::STATE_MOVE_TO:
|
||||
this->move_to(state.move_to->get_global_position());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void CharacterActor::process_behaviour(double delta_time) {
|
||||
if(this->current_state.is_complete(this) || this->planner->is_action_complete())
|
||||
this->set_state(this->planner->get_next_state());
|
||||
switch(this->current_state.type) {
|
||||
default:
|
||||
break;
|
||||
case goap::State::STATE_MOVE_TO:
|
||||
if(this->nav_agent->get_target_position().distance_to(this->current_state.move_to->get_global_position()) > 2.f)
|
||||
this->nav_agent->set_target_position(this->current_state.move_to->get_global_position());
|
||||
break;
|
||||
case goap::State::STATE_ACTIVATE:
|
||||
break;
|
||||
case goap::State::STATE_ANIMATE:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void CharacterActor::process_navigation(double delta_time) {
|
||||
float const distance_sqr = this->nav_agent->get_target_position().distance_squared_to(this->get_global_position());
|
||||
float const distance_target_sqr = std::pow(this->nav_agent->get_target_desired_distance(), 2.f);
|
||||
if(!this->nav_agent->is_navigation_finished() && distance_sqr >= distance_target_sqr) {
|
||||
Vector3 const target_position = this->nav_agent->get_next_path_position();
|
||||
Vector3 const direction = (target_position - this->get_global_position()).normalized();
|
||||
if(this->nav_agent->get_avoidance_enabled())
|
||||
|
|
|
@ -3,14 +3,18 @@
|
|||
|
||||
#include "character_data.hpp"
|
||||
#include "health.hpp"
|
||||
#include "state.hpp"
|
||||
#include "projectile_pool.hpp"
|
||||
#include "state.hpp"
|
||||
#include <godot_cpp/classes/character_body3d.hpp>
|
||||
#include <godot_cpp/classes/curve.hpp>
|
||||
|
||||
namespace godot {
|
||||
class NavigationAgent3D;
|
||||
class TunnelsPlayer;
|
||||
class AnimationPlayer;
|
||||
namespace goap {
|
||||
class Planner;
|
||||
};
|
||||
|
||||
class CharacterActor : public CharacterBody3D,
|
||||
public IHealthEntity {
|
||||
|
@ -38,18 +42,18 @@ public:
|
|||
|
||||
void set_rotation_speed_curve(Ref<Curve> curve);
|
||||
Ref<Curve> get_rotation_speed_curve() const;
|
||||
|
||||
virtual Health *get_health() override;
|
||||
virtual Health const *get_health() const override;
|
||||
|
||||
void set_character_data(Ref<CharacterData> data);
|
||||
|
||||
void set_weapon_muzzle(Node3D *node);
|
||||
|
||||
void set_velocity_target(Vector3 value);
|
||||
Vector3 get_velocity_target() const;
|
||||
bool get_is_near_player() const;
|
||||
CharacterActor *get_player_character() const;
|
||||
void set_state(goap::State state);
|
||||
protected:
|
||||
void process_ai(double delta_time);
|
||||
void process_behaviour(double delta_time);
|
||||
void process_navigation(double delta_time);
|
||||
void process_rotation(double delta_time);
|
||||
void try_fire_weapon();
|
||||
private:
|
||||
|
@ -65,19 +69,21 @@ private:
|
|||
float fire_timer{0.f};
|
||||
// the origin point for projectiles
|
||||
Node3D *weapon_muzzle{nullptr};
|
||||
// whatever the AI is currently targetting
|
||||
// something that the AI wants to target
|
||||
Node *target{nullptr};
|
||||
// the current state of the actor
|
||||
goap::State current_state{}; // the current state
|
||||
goap::State current_state{goap::State::new_invalid()};
|
||||
AnimationPlayer *anim_player{nullptr};
|
||||
|
||||
Health *health{nullptr};
|
||||
ProjectilePool *primary_weapon_pool{nullptr};
|
||||
NavigationAgent3D *nav_agent{nullptr};
|
||||
goap::Planner *planner{nullptr};
|
||||
|
||||
Ref<Curve> rotation_speed_curve{};
|
||||
// character data assigned when spawned
|
||||
Ref<CharacterData> data;
|
||||
float fire_interval{0.f}; // derived from the current weapon's rpm
|
||||
float fire_interval{0.f}; // derived from 1 / the current weapon's rps
|
||||
|
||||
static float const ACCELERATION;
|
||||
static float const WALK_SPEED;
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#include "action.hpp"
|
||||
#include "character_actor.hpp"
|
||||
#include "global_world_state.hpp"
|
||||
#include "state.hpp"
|
||||
#include "utils/godot_macros.h"
|
||||
#include <cstdlib>
|
||||
#include <godot_cpp/templates/pair.hpp>
|
||||
|
@ -61,7 +62,6 @@ void Planner::_bind_methods() {
|
|||
#define CLASSNAME Planner
|
||||
GDPROPERTY_HINTED(actions, Variant::ARRAY, PROPERTY_HINT_ARRAY_TYPE, GDRESOURCETYPE(Action));
|
||||
GDPROPERTY_HINTED(goals, Variant::ARRAY, PROPERTY_HINT_ARRAY_TYPE, GDRESOURCETYPE(Goal));
|
||||
GDFUNCTION_ARGS(gdscript_make_plan, "goal");
|
||||
}
|
||||
|
||||
void Planner::_ready() {
|
||||
|
@ -79,21 +79,12 @@ static Vector<Ref<Action>> trace_path(FromMap &map, PlannerNode &end) {
|
|||
return edges;
|
||||
}
|
||||
|
||||
Array Planner::gdscript_make_plan(Ref<Goal> goal) {
|
||||
Vector<Ref<Action>> plan = this->make_plan(goal);
|
||||
Array out{};
|
||||
int i{0};
|
||||
UtilityFunctions::print("plan len: ", plan.size());
|
||||
for(Ref<Action> const &action : plan) {
|
||||
out.push_back(action);
|
||||
UtilityFunctions::print("plan[", i++, "]: ", this->actions.find(action));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
Vector<Ref<Action>> Planner::make_plan(Ref<Goal> goal) {
|
||||
Vector<Ref<Action>> Planner::make_plan() {
|
||||
// clear cache every planning phase
|
||||
this->cached_world_state.clear();
|
||||
Ref<Goal> goal = this->select_goal();
|
||||
if(!goal.is_valid())
|
||||
return {};
|
||||
// ordered list of all nodes still being considered
|
||||
Vector<PlannerNode> open{PlannerNode::goal_node(goal->goal_state)};
|
||||
PlannerNode first = open.get(0);
|
||||
|
@ -130,6 +121,20 @@ Vector<Ref<Action>> Planner::make_plan(Ref<Goal> goal) {
|
|||
return {};
|
||||
}
|
||||
|
||||
Ref<Goal> Planner::select_goal() {
|
||||
for(Ref<Goal> const &goal : this->goals) {
|
||||
bool can_try{true};
|
||||
for(WorldProperty const &prop : goal->prerequisites) {
|
||||
if(prop.value != this->get_world_property(prop.key)) {
|
||||
can_try = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(can_try) return goal;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
Variant Planner::get_world_property(StringName prop_key) {
|
||||
if(prop_key.begins_with("g_")) {
|
||||
return this->global_world_state->get_world_property(prop_key);
|
||||
|
@ -172,13 +177,28 @@ Vector<Ref<Action>> Planner::find_actions_satisfying(WorldState requirements) {
|
|||
for(WorldProperty &prop : requirements) {
|
||||
if(act->effects.has(prop.key)
|
||||
&& act->effects.get(prop.key) == prop.value
|
||||
&& this->can_do(act))
|
||||
&& this->can_do(act)) {
|
||||
found_actions.push_back(act);
|
||||
}
|
||||
}
|
||||
}
|
||||
return found_actions;
|
||||
}
|
||||
|
||||
bool Planner::is_action_complete() {
|
||||
return this->plan.get(0)->get_is_completed(this->actor);
|
||||
}
|
||||
|
||||
State Planner::get_next_state() {
|
||||
if(!this->plan.is_empty())
|
||||
this->plan.remove_at(0);
|
||||
if(this->plan.is_empty())
|
||||
this->plan = this->make_plan();
|
||||
if(this->plan.is_empty())
|
||||
return State::new_invalid();
|
||||
return this->plan.get(0)->apply_state->construct(this->actor);
|
||||
}
|
||||
|
||||
void Planner::set_actions(Array value) {
|
||||
this->actions.clear();
|
||||
this->actions.resize(value.size());
|
||||
|
|
|
@ -46,8 +46,8 @@ class Planner : public Node {
|
|||
public:
|
||||
virtual void _ready() override;
|
||||
|
||||
Array gdscript_make_plan(Ref<Goal> goal);
|
||||
Vector<Ref<Action>> make_plan(Ref<Goal> goal);
|
||||
Vector<Ref<Action>> make_plan();
|
||||
Ref<Goal> select_goal();
|
||||
|
||||
Variant get_world_property(StringName prop_key);
|
||||
|
||||
|
@ -55,9 +55,11 @@ public:
|
|||
Vector<PlannerNode> find_neighbours_of(PlannerNode &node);
|
||||
Vector<Ref<Action>> find_actions_satisfying(WorldState requirements);
|
||||
|
||||
bool is_action_complete();
|
||||
State get_next_state();
|
||||
|
||||
void set_actions(Array actions);
|
||||
Array get_actions() const;
|
||||
|
||||
void set_goals(Array goals);
|
||||
Array get_goals() const;
|
||||
private:
|
||||
|
@ -67,6 +69,7 @@ private:
|
|||
// configured settings
|
||||
Vector<Ref<Action>> actions{}; // available actions
|
||||
Vector<Ref<Goal>> goals{}; // available goals
|
||||
Vector<Ref<Action>> plan{};
|
||||
};
|
||||
|
||||
struct PlannerNodeHasher {
|
||||
|
|
|
@ -29,6 +29,10 @@ State State::new_activate(Node *node) {
|
|||
};
|
||||
}
|
||||
|
||||
State State::new_invalid() {
|
||||
return { .type = State::Type::STATE_TYPE_MAX };
|
||||
}
|
||||
|
||||
bool State::is_complete(CharacterActor *context) const {
|
||||
switch(this->type) {
|
||||
default:
|
||||
|
|
|
@ -14,6 +14,7 @@ struct State {
|
|||
static State new_move_to(Node3D *location);
|
||||
static State new_animate(StringName animation);
|
||||
static State new_activate(Node *node);
|
||||
static State new_invalid();
|
||||
|
||||
bool is_complete(CharacterActor *context) const;
|
||||
|
||||
|
|
Loading…
Reference in a new issue