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 diff --git a/modules/wave_survival/player_detector.cpp b/modules/wave_survival/player_detector.cpp index 3a550bfd..6869cccf 100644 --- a/modules/wave_survival/player_detector.cpp +++ b/modules/wave_survival/player_detector.cpp @@ -8,17 +8,6 @@ void PlayerDetector::_bind_methods() { 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. // As all tests are required to pass, we do them in increasing order of complexity, to minimize unneeded resource use. 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 { return this->aware_of_player; } diff --git a/modules/wave_survival/player_detector.h b/modules/wave_survival/player_detector.h index b9447aca..f57f0dca 100644 --- a/modules/wave_survival/player_detector.h +++ b/modules/wave_survival/player_detector.h @@ -6,7 +6,6 @@ class PlayerDetector : public Node3D { GDCLASS(PlayerDetector, Node3D); static void _bind_methods(); - bool line_of_sight_exists() const; bool check() const; void ready(); void process(double delta); @@ -17,6 +16,7 @@ protected: void _notification(int what); public: + bool line_of_sight_exists() const; bool is_aware_of_player() const; private: diff --git a/project/assets/models/environment/wall_2x1.blend b/project/assets/models/environment/wall_2x1.blend index 11f93329..8d0c351d 100644 Binary files a/project/assets/models/environment/wall_2x1.blend and b/project/assets/models/environment/wall_2x1.blend differ diff --git a/project/objects/enemies/enemy_wretched.tscn b/project/objects/enemies/enemy_wretched.tscn index 0c0fb3f5..0254546c 100644 --- a/project/objects/enemies/enemy_wretched.tscn +++ b/project/objects/enemies/enemy_wretched.tscn @@ -89,7 +89,7 @@ time_horizon_agents = 0.7 [node name="PlayerDetector" type="PlayerDetector" parent="." unique_id=592530198] 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] unique_name_in_owner = true