From 1625a11c420c868b033b52d7fbe4287b43d39c0a Mon Sep 17 00:00:00 2001 From: Sara Date: Mon, 2 Feb 2026 20:51:14 +0100 Subject: [PATCH] feat: generalising of NPC logic --- .../wave_survival/enemies/enemy_rifleman.cpp | 16 +++++++- .../wave_survival/enemies/enemy_rifleman.h | 14 ++++--- .../wave_survival/enemies/enemy_wretched.cpp | 16 +------- .../wave_survival/enemies/enemy_wretched.h | 10 ++--- modules/wave_survival/enemy_body.cpp | 37 ++++++++++++++++--- modules/wave_survival/enemy_body.h | 18 ++++++++- 6 files changed, 74 insertions(+), 37 deletions(-) diff --git a/modules/wave_survival/enemies/enemy_rifleman.cpp b/modules/wave_survival/enemies/enemy_rifleman.cpp index 6f1655ae..e6ba5a13 100644 --- a/modules/wave_survival/enemies/enemy_rifleman.cpp +++ b/modules/wave_survival/enemies/enemy_rifleman.cpp @@ -2,6 +2,8 @@ #include "core/os/memory.h" #include "scene/animation/animation_player.h" #include "wave_survival/macros.h" +#include "wave_survival/npc_unit.h" +#include "wave_survival/player_detector.h" #include "wave_survival/state_machine.h" void EnemyRifleman::_bind_methods() { @@ -19,7 +21,7 @@ void EnemyRifleman::on_child_entered(Node *node) { void EnemyRifleman::ready() { this->fsm->add_state(memnew(RiflemanPatrolState)); - this->fsm->add_state(memnew(RiflemanSeekState)); + this->fsm->add_state(memnew(RiflemanChaseState)); this->fsm->add_state(memnew(RiflemanFireState)); } @@ -57,5 +59,15 @@ EnemyRifleman *RiflemanState::get_target() const { return cast_to(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(); } diff --git a/modules/wave_survival/enemies/enemy_rifleman.h b/modules/wave_survival/enemies/enemy_rifleman.h index 611398f4..e164cc68 100644 --- a/modules/wave_survival/enemies/enemy_rifleman.h +++ b/modules/wave_survival/enemies/enemy_rifleman.h @@ -37,18 +37,20 @@ public: EnemyRifleman *get_target() const; }; -class RiflemanPatrolState : public RiflemanState { - GDCLASS(RiflemanPatrolState, RiflemanState); +class RiflemanPatrolState : public EnemyPatrolState { + GDCLASS(RiflemanPatrolState, EnemyPatrolState); static void _bind_methods() {} public: - virtual void enter_state() override; - virtual void process(double delta) override; + String get_next_state() const override; }; -class RiflemanSeekState : public RiflemanState { - GDCLASS(RiflemanSeekState, RiflemanState); +class RiflemanChaseState : public EnemyChaseState { + GDCLASS(RiflemanChaseState, EnemyChaseState); static void _bind_methods() {} + +public: + String get_next_state() const override; }; class RiflemanFireState : public RiflemanState { diff --git a/modules/wave_survival/enemies/enemy_wretched.cpp b/modules/wave_survival/enemies/enemy_wretched.cpp index 7e0265e8..53d1b604 100644 --- a/modules/wave_survival/enemies/enemy_wretched.cpp +++ b/modules/wave_survival/enemies/enemy_wretched.cpp @@ -36,22 +36,8 @@ String WretchedPatrolState::get_next_state() const { 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 { - 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 get_class(); diff --git a/modules/wave_survival/enemies/enemy_wretched.h b/modules/wave_survival/enemies/enemy_wretched.h index 521d4ae8..814bc9e6 100644 --- a/modules/wave_survival/enemies/enemy_wretched.h +++ b/modules/wave_survival/enemies/enemy_wretched.h @@ -26,21 +26,19 @@ public: EnemyWretched *get_target() const; }; -class WretchedPatrolState : public PatrolState { - GDCLASS(WretchedPatrolState, PatrolState); +class WretchedPatrolState : public EnemyPatrolState { + GDCLASS(WretchedPatrolState, EnemyPatrolState); static void _bind_methods() {} public: String get_next_state() const override; }; -class WretchedChaseState : public WretchedState { - GDCLASS(WretchedChaseState, WretchedState); +class WretchedChaseState : public EnemyChaseState { + GDCLASS(WretchedChaseState, EnemyChaseState); static void _bind_methods() {} public: - virtual void enter_state() override; - virtual void process(double delta) override; virtual String get_next_state() const override; }; diff --git a/modules/wave_survival/enemy_body.cpp b/modules/wave_survival/enemy_body.cpp index 6aa2bc1f..f5244667 100644 --- a/modules/wave_survival/enemy_body.cpp +++ b/modules/wave_survival/enemy_body.cpp @@ -3,6 +3,8 @@ #include "scene/animation/animation_player.h" #include "wave_survival/npc_unit.h" #include "wave_survival/patrol_path.h" +#include "wave_survival/player_body.h" +#include "wave_survival/player_detector.h" void EnemyBody::_bind_methods() { 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(node) }) { this->nav = nav; } + if (PlayerDetector * detector{ cast_to(node) }) { + this->detector = detector; + } if (node->has_node(NodePath("AnimationPlayer"))) { this->anim = cast_to(node->get_node(NodePath("AnimationPlayer"))); } @@ -49,7 +54,9 @@ void EnemyBody::_notification(int what) { default: return; 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; case NOTIFICATION_READY: set_physics_process(true); @@ -97,6 +104,10 @@ StateMachine *EnemyBody::get_fsm() const { return this->fsm; } +PlayerDetector *EnemyBody::get_detector() const { + return this->detector; +} + void EnemyBody::set_movement_speed(float value) { this->movement_speed = value; } @@ -138,15 +149,15 @@ EnemyBody *EnemyState::get_body() const { 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()); } -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()); } -void PatrolState::enter_state() { +void EnemyPatrolState::enter_state() { this->path = get_body()->get_unit()->get_patrol_path(); 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); } -void PatrolState::process(double delta) { +void EnemyPatrolState::process(double delta) { if (this->path) { if (get_nav()->is_navigation_finished()) { 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); } + +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()); +} diff --git a/modules/wave_survival/enemy_body.h b/modules/wave_survival/enemy_body.h index b8a5599c..a2172fd5 100644 --- a/modules/wave_survival/enemy_body.h +++ b/modules/wave_survival/enemy_body.h @@ -9,6 +9,7 @@ class NpcUnit; class AnimationPlayer; class PatrolPath; +class PlayerDetector; class EnemyBody : public CharacterBody3D { GDCLASS(EnemyBody, CharacterBody3D); @@ -33,6 +34,7 @@ public: NavigationAgent3D *get_nav() const; AnimationPlayer *get_anim() const; StateMachine *get_fsm() const; + PlayerDetector *get_detector() const; void set_movement_speed(float value); float get_movement_speed() const; @@ -50,9 +52,12 @@ private: HealthStatus *health{ nullptr }; NavigationAgent3D *nav{ nullptr }; AnimationPlayer *anim{ nullptr }; + PlayerDetector *detector{ nullptr }; Vector2 unit_offset{ 0, 0 }; }; +/* ============================== STATES ============================== */ + class EnemyState : public State { GDCLASS(EnemyState, State); static void _bind_methods() {} @@ -68,8 +73,8 @@ private: EnemyBody *body{ nullptr }; }; -class PatrolState : public EnemyState { - GDCLASS(PatrolState, EnemyState); +class EnemyPatrolState : public EnemyState { + GDCLASS(EnemyPatrolState, EnemyState); static void _bind_methods() {} void set_patrol_target(Vector3 path_point); void on_velocity_calculated(Vector3 direction); @@ -85,4 +90,13 @@ private: 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