Compare commits

..

4 commits

10 changed files with 87 additions and 50 deletions

View file

@ -2,6 +2,8 @@
#include "core/os/memory.h" #include "core/os/memory.h"
#include "scene/animation/animation_player.h" #include "scene/animation/animation_player.h"
#include "wave_survival/macros.h" #include "wave_survival/macros.h"
#include "wave_survival/npc_unit.h"
#include "wave_survival/player_detector.h"
#include "wave_survival/state_machine.h" #include "wave_survival/state_machine.h"
void EnemyRifleman::_bind_methods() { void EnemyRifleman::_bind_methods() {
@ -19,7 +21,7 @@ void EnemyRifleman::on_child_entered(Node *node) {
void EnemyRifleman::ready() { void EnemyRifleman::ready() {
this->fsm->add_state(memnew(RiflemanPatrolState)); this->fsm->add_state(memnew(RiflemanPatrolState));
this->fsm->add_state(memnew(RiflemanSeekState)); this->fsm->add_state(memnew(RiflemanChaseState));
this->fsm->add_state(memnew(RiflemanFireState)); this->fsm->add_state(memnew(RiflemanFireState));
} }
@ -57,5 +59,15 @@ EnemyRifleman *RiflemanState::get_target() const {
return cast_to<EnemyRifleman>(get_body()); return cast_to<EnemyRifleman>(get_body());
} }
void RiflemanPatrolState::enter_state() { String RiflemanPatrolState::get_next_state() const {
if (get_body()->get_unit()->is_aware_of_player()) {
return RiflemanChaseState::get_class_static();
}
return get_class();
}
String RiflemanChaseState::get_next_state() const {
if (get_body()->get_detector()->line_of_sight_exists()) {
}
return get_class();
} }

View file

@ -37,18 +37,20 @@ public:
EnemyRifleman *get_target() const; EnemyRifleman *get_target() const;
}; };
class RiflemanPatrolState : public RiflemanState { class RiflemanPatrolState : public EnemyPatrolState {
GDCLASS(RiflemanPatrolState, RiflemanState); GDCLASS(RiflemanPatrolState, EnemyPatrolState);
static void _bind_methods() {} static void _bind_methods() {}
public: public:
virtual void enter_state() override; String get_next_state() const override;
virtual void process(double delta) override;
}; };
class RiflemanSeekState : public RiflemanState { class RiflemanChaseState : public EnemyChaseState {
GDCLASS(RiflemanSeekState, RiflemanState); GDCLASS(RiflemanChaseState, EnemyChaseState);
static void _bind_methods() {} static void _bind_methods() {}
public:
String get_next_state() const override;
}; };
class RiflemanFireState : public RiflemanState { class RiflemanFireState : public RiflemanState {

View file

@ -36,22 +36,8 @@ String WretchedPatrolState::get_next_state() const {
return get_class(); return get_class();
} }
void WretchedChaseState::enter_state() {
get_target()->set_movement_speed(get_target()->get_max_speed());
get_nav()->set_max_speed(get_target()->get_max_speed());
get_nav()->set_target_position(PlayerBody::get_singleton()->get_global_position());
get_anim()->play("ready"); // TODO: replace this with "run"
}
void WretchedChaseState::process(double delta) {
// TODO: optimize this with some checks to avoid re-pathing every frame
get_nav()->set_target_position(PlayerBody::get_singleton()->get_global_position());
Vector3 const direction{ get_target()->get_global_position().direction_to(get_nav()->get_next_path_position()) };
get_target()->set_movement_direction(Vector2{ direction.x, direction.z }.normalized());
}
String WretchedChaseState::get_next_state() const { String WretchedChaseState::get_next_state() const {
if (get_target()->get_global_position().distance_to(PlayerBody::get_singleton()->get_global_position()) < 2.f) { if (get_body()->get_global_position().distance_to(PlayerBody::get_singleton()->get_global_position()) < 2.f) {
return WretchedAttackState::get_class_static(); return WretchedAttackState::get_class_static();
} }
return get_class(); return get_class();

View file

@ -26,21 +26,19 @@ public:
EnemyWretched *get_target() const; EnemyWretched *get_target() const;
}; };
class WretchedPatrolState : public PatrolState { class WretchedPatrolState : public EnemyPatrolState {
GDCLASS(WretchedPatrolState, PatrolState); GDCLASS(WretchedPatrolState, EnemyPatrolState);
static void _bind_methods() {} static void _bind_methods() {}
public: public:
String get_next_state() const override; String get_next_state() const override;
}; };
class WretchedChaseState : public WretchedState { class WretchedChaseState : public EnemyChaseState {
GDCLASS(WretchedChaseState, WretchedState); GDCLASS(WretchedChaseState, EnemyChaseState);
static void _bind_methods() {} static void _bind_methods() {}
public: public:
virtual void enter_state() override;
virtual void process(double delta) override;
virtual String get_next_state() const override; virtual String get_next_state() const override;
}; };

View file

@ -3,6 +3,8 @@
#include "scene/animation/animation_player.h" #include "scene/animation/animation_player.h"
#include "wave_survival/npc_unit.h" #include "wave_survival/npc_unit.h"
#include "wave_survival/patrol_path.h" #include "wave_survival/patrol_path.h"
#include "wave_survival/player_body.h"
#include "wave_survival/player_detector.h"
void EnemyBody::_bind_methods() { void EnemyBody::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_movement_direction", "direction"), &self_type::set_movement_direction); ClassDB::bind_method(D_METHOD("set_movement_direction", "direction"), &self_type::set_movement_direction);
@ -17,6 +19,9 @@ void EnemyBody::on_child_added(Node *node) {
if (NavigationAgent3D * nav{ cast_to<NavigationAgent3D>(node) }) { if (NavigationAgent3D * nav{ cast_to<NavigationAgent3D>(node) }) {
this->nav = nav; this->nav = nav;
} }
if (PlayerDetector * detector{ cast_to<PlayerDetector>(node) }) {
this->detector = detector;
}
if (node->has_node(NodePath("AnimationPlayer"))) { if (node->has_node(NodePath("AnimationPlayer"))) {
this->anim = cast_to<AnimationPlayer>(node->get_node(NodePath("AnimationPlayer"))); this->anim = cast_to<AnimationPlayer>(node->get_node(NodePath("AnimationPlayer")));
} }
@ -49,7 +54,9 @@ void EnemyBody::_notification(int what) {
default: default:
return; return;
case NOTIFICATION_ENTER_TREE: case NOTIFICATION_ENTER_TREE:
connect("child_entered_tree", callable_mp(this, &self_type::on_child_added)); if (!is_ready()) {
connect("child_entered_tree", callable_mp(this, &self_type::on_child_added));
}
return; return;
case NOTIFICATION_READY: case NOTIFICATION_READY:
set_physics_process(true); set_physics_process(true);
@ -97,6 +104,10 @@ StateMachine *EnemyBody::get_fsm() const {
return this->fsm; return this->fsm;
} }
PlayerDetector *EnemyBody::get_detector() const {
return this->detector;
}
void EnemyBody::set_movement_speed(float value) { void EnemyBody::set_movement_speed(float value) {
this->movement_speed = value; this->movement_speed = value;
} }
@ -138,15 +149,15 @@ EnemyBody *EnemyState::get_body() const {
return this->body; return this->body;
} }
void PatrolState::set_patrol_target(Vector3 path_point) { void EnemyPatrolState::set_patrol_target(Vector3 path_point) {
get_nav()->set_target_position(path_point + get_body()->get_unit_offset_3d()); get_nav()->set_target_position(path_point + get_body()->get_unit_offset_3d());
} }
void PatrolState::on_velocity_calculated(Vector3 direction) { void EnemyPatrolState::on_velocity_calculated(Vector3 direction) {
get_body()->set_movement_direction(Vector2{ direction.x, direction.z } / get_body()->get_movement_speed()); get_body()->set_movement_direction(Vector2{ direction.x, direction.z } / get_body()->get_movement_speed());
} }
void PatrolState::enter_state() { void EnemyPatrolState::enter_state() {
this->path = get_body()->get_unit()->get_patrol_path(); this->path = get_body()->get_unit()->get_patrol_path();
float const max_speed{ get_unit()->get_patrol_speed() }; float const max_speed{ get_unit()->get_patrol_speed() };
@ -159,7 +170,7 @@ void PatrolState::enter_state() {
get_nav()->connect("velocity_computed", this->mp_on_velocity_calculated); get_nav()->connect("velocity_computed", this->mp_on_velocity_calculated);
} }
void PatrolState::process(double delta) { void EnemyPatrolState::process(double delta) {
if (this->path) { if (this->path) {
if (get_nav()->is_navigation_finished()) { if (get_nav()->is_navigation_finished()) {
this->path_point += 1; this->path_point += 1;
@ -174,6 +185,20 @@ void PatrolState::process(double delta) {
} }
} }
void PatrolState::exit_state() { void EnemyPatrolState::exit_state() {
get_nav()->disconnect("velocity_computed", this->mp_on_velocity_calculated); get_nav()->disconnect("velocity_computed", this->mp_on_velocity_calculated);
} }
void EnemyChaseState::enter_state() {
get_body()->set_movement_speed(get_body()->get_max_speed());
get_nav()->set_max_speed(get_body()->get_max_speed());
get_nav()->set_target_position(PlayerBody::get_singleton()->get_global_position());
get_anim()->play("ready"); // TODO: replace this with "run"
}
void EnemyChaseState::process(double delta) {
// TODO: optimize this with some checks to avoid re-pathing every frame
get_nav()->set_target_position(PlayerBody::get_singleton()->get_global_position());
Vector3 const direction{ get_body()->get_global_position().direction_to(get_nav()->get_next_path_position()) };
get_body()->set_movement_direction(Vector2{ direction.x, direction.z }.normalized());
}

View file

@ -9,6 +9,7 @@
class NpcUnit; class NpcUnit;
class AnimationPlayer; class AnimationPlayer;
class PatrolPath; class PatrolPath;
class PlayerDetector;
class EnemyBody : public CharacterBody3D { class EnemyBody : public CharacterBody3D {
GDCLASS(EnemyBody, CharacterBody3D); GDCLASS(EnemyBody, CharacterBody3D);
@ -33,6 +34,7 @@ public:
NavigationAgent3D *get_nav() const; NavigationAgent3D *get_nav() const;
AnimationPlayer *get_anim() const; AnimationPlayer *get_anim() const;
StateMachine *get_fsm() const; StateMachine *get_fsm() const;
PlayerDetector *get_detector() const;
void set_movement_speed(float value); void set_movement_speed(float value);
float get_movement_speed() const; float get_movement_speed() const;
@ -50,9 +52,12 @@ private:
HealthStatus *health{ nullptr }; HealthStatus *health{ nullptr };
NavigationAgent3D *nav{ nullptr }; NavigationAgent3D *nav{ nullptr };
AnimationPlayer *anim{ nullptr }; AnimationPlayer *anim{ nullptr };
PlayerDetector *detector{ nullptr };
Vector2 unit_offset{ 0, 0 }; Vector2 unit_offset{ 0, 0 };
}; };
/* ============================== STATES ============================== */
class EnemyState : public State { class EnemyState : public State {
GDCLASS(EnemyState, State); GDCLASS(EnemyState, State);
static void _bind_methods() {} static void _bind_methods() {}
@ -68,8 +73,8 @@ private:
EnemyBody *body{ nullptr }; EnemyBody *body{ nullptr };
}; };
class PatrolState : public EnemyState { class EnemyPatrolState : public EnemyState {
GDCLASS(PatrolState, EnemyState); GDCLASS(EnemyPatrolState, EnemyState);
static void _bind_methods() {} static void _bind_methods() {}
void set_patrol_target(Vector3 path_point); void set_patrol_target(Vector3 path_point);
void on_velocity_calculated(Vector3 direction); void on_velocity_calculated(Vector3 direction);
@ -85,4 +90,13 @@ private:
Callable mp_on_velocity_calculated{ callable_mp(this, &self_type::on_velocity_calculated) }; Callable mp_on_velocity_calculated{ callable_mp(this, &self_type::on_velocity_calculated) };
}; };
class EnemyChaseState : public EnemyState {
GDCLASS(EnemyChaseState, EnemyState);
static void _bind_methods() {}
public:
virtual void enter_state() override;
virtual void process(double delta) override;
};
#endif // !ENEMY_BODY_H #endif // !ENEMY_BODY_H

View file

@ -8,17 +8,6 @@ void PlayerDetector::_bind_methods() {
ADD_SIGNAL(MethodInfo(sig_awareness_changed, PropertyInfo(Variant::BOOL, "aware"))); ADD_SIGNAL(MethodInfo(sig_awareness_changed, PropertyInfo(Variant::BOOL, "aware")));
} }
// check if there is geometry between detector and player
bool PlayerDetector::line_of_sight_exists() const {
PhysicsDirectSpaceState3D::RayParameters params{ this->ray_params };
params.from = get_global_position();
params.to = PlayerBody::get_singleton()->get_global_position();
PhysicsDirectSpaceState3D *space{ get_world_3d()->get_direct_space_state() };
PhysicsDirectSpaceState3D::RayResult result{};
space->intersect_ray(params, result);
return result.collider == nullptr;
}
// Check if the player is in a bounded area in front of the detector and unobscured. // Check if the player is in a bounded area in front of the detector and unobscured.
// As all tests are required to pass, we do them in increasing order of complexity, to minimize unneeded resource use. // As all tests are required to pass, we do them in increasing order of complexity, to minimize unneeded resource use.
bool PlayerDetector::check() const { bool PlayerDetector::check() const {
@ -82,6 +71,17 @@ void PlayerDetector::_notification(int what) {
} }
} }
// check if there is geometry between detector and player
bool PlayerDetector::line_of_sight_exists() const {
PhysicsDirectSpaceState3D::RayParameters params{ this->ray_params };
params.from = get_global_position();
params.to = PlayerBody::get_singleton()->get_global_position();
PhysicsDirectSpaceState3D *space{ get_world_3d()->get_direct_space_state() };
PhysicsDirectSpaceState3D::RayResult result{};
space->intersect_ray(params, result);
return result.collider == nullptr;
}
bool PlayerDetector::is_aware_of_player() const { bool PlayerDetector::is_aware_of_player() const {
return this->aware_of_player; return this->aware_of_player;
} }

View file

@ -6,7 +6,6 @@
class PlayerDetector : public Node3D { class PlayerDetector : public Node3D {
GDCLASS(PlayerDetector, Node3D); GDCLASS(PlayerDetector, Node3D);
static void _bind_methods(); static void _bind_methods();
bool line_of_sight_exists() const;
bool check() const; bool check() const;
void ready(); void ready();
void process(double delta); void process(double delta);
@ -17,6 +16,7 @@ protected:
void _notification(int what); void _notification(int what);
public: public:
bool line_of_sight_exists() const;
bool is_aware_of_player() const; bool is_aware_of_player() const;
private: private:

View file

@ -89,7 +89,7 @@ time_horizon_agents = 0.7
[node name="PlayerDetector" type="PlayerDetector" parent="." unique_id=592530198] [node name="PlayerDetector" type="PlayerDetector" parent="." unique_id=592530198]
unique_name_in_owner = true unique_name_in_owner = true
transform = Transform3D(-1, 0, -8.742278e-08, 0, 1, 0, 8.742278e-08, 0, -1, 0, 1.4599279, 0) transform = Transform3D(-1, 0, -8.742278e-08, 0, 1, 0, 8.742278e-08, 0, -1, 0, 1.5103652, 0)
[node name="HealthStatus" type="HealthStatus" parent="." unique_id=625738980] [node name="HealthStatus" type="HealthStatus" parent="." unique_id=625738980]
unique_name_in_owner = true unique_name_in_owner = true