Compare commits
4 commits
f0aba0caf7
...
1625a11c42
| Author | SHA1 | Date | |
|---|---|---|---|
| 1625a11c42 | |||
| 41bcf4cdf7 | |||
| dfc5ac680a | |||
| d430da0801 |
10 changed files with 87 additions and 50 deletions
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
if (!is_ready()) {
|
||||||
connect("child_entered_tree", callable_mp(this, &self_type::on_child_added));
|
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());
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue