#include "enemy_body.h" #include "macros.h" #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); ClassDB::bind_method(D_METHOD("get_unit"), &self_type::get_unit); BIND_PROPERTY(Variant::FLOAT, max_speed); } void EnemyBody::on_child_added(Node *node) { if (StateMachine * fsm{ cast_to(node) }) { this->fsm = fsm; } 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"))); } } void EnemyBody::ready() { this->fsm = cast_to(get_node(NodePath("%StateMachine"))); this->nav = cast_to(get_node(NodePath("%NavigationAgent3D"))); this->health = cast_to(get_node(NodePath("%HealthStatus"))); } void EnemyBody::physics_process(double delta) { GETSET(velocity, { velocity = Vector3{ this->movement_direction.x * this->movement_speed, velocity.y, this->movement_direction.y * this->movement_speed } + (velocity * delta); if (!is_on_floor() && this->health->get_health() > 0) { velocity += get_gravity() * delta; } }); if (!this->movement_direction.is_zero_approx()) { look_at(get_global_position() + Vector3{ this->movement_direction.x, 0.f, this->movement_direction.y }); } move_and_slide(); } void EnemyBody::_notification(int what) { if (Engine::get_singleton()->is_editor_hint()) { return; } switch (what) { default: return; case NOTIFICATION_ENTER_TREE: if (!is_ready()) { connect("child_entered_tree", callable_mp(this, &self_type::on_child_added)); } return; case NOTIFICATION_READY: set_physics_process(true); ready(); return; case NOTIFICATION_PHYSICS_PROCESS: physics_process(get_physics_process_delta_time()); return; } } void EnemyBody::set_max_speed(float speed) { this->max_speed = speed; } float EnemyBody::get_max_speed() const { return this->max_speed; } void EnemyBody::set_movement_direction(Vector2 direction) { this->movement_direction = direction; } void EnemyBody::set_unit(NpcUnit *unit) { this->unit = unit; } NpcUnit *EnemyBody::get_unit() const { return this->unit; } HealthStatus *EnemyBody::get_health() const { return this->health; } NavigationAgent3D *EnemyBody::get_nav() const { return this->nav; } AnimationPlayer *EnemyBody::get_anim() const { return this->anim; } 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; } float EnemyBody::get_movement_speed() const { return this->movement_speed; } void EnemyBody::set_unit_offset(Vector2 offset) { this->unit_offset = offset; } Vector2 EnemyBody::get_unit_offset() const { return this->unit_offset; } Vector3 EnemyBody::get_unit_offset_3d() const { return { this->unit_offset.x, 0, this->unit_offset.y }; } void EnemyState::set_target(Node *node) { this->body = cast_to(node); ERR_FAIL_COND_EDMSG(this->body == nullptr, "EnemyState initialised invalid target"); } NpcUnit *EnemyState::get_unit() const { return this->body->get_unit(); } NavigationAgent3D *EnemyState::get_nav() const { return this->body->get_nav(); } AnimationPlayer *EnemyState::get_anim() const { return this->body->get_anim(); } EnemyBody *EnemyState::get_body() const { return this->body; } void EnemyPatrolState::set_patrol_target(Vector3 path_point) { get_nav()->set_target_position(path_point + get_body()->get_unit_offset_3d()); } void EnemyPatrolState::on_velocity_calculated(Vector3 direction) { get_body()->set_movement_direction(Vector2{ direction.x, direction.z } / get_body()->get_movement_speed()); } void EnemyPatrolState::enter_state() { this->path = get_body()->get_unit()->get_patrol_path(); float const max_speed{ get_unit()->get_patrol_speed() }; get_body()->set_movement_speed(max_speed); get_nav()->set_max_speed(max_speed); if (this->path) { Vector3 const nav_target{ this->path->get_closest_point(get_body()->get_global_position(), &this->path_point) }; set_patrol_target(nav_target); } get_nav()->connect("velocity_computed", this->mp_on_velocity_calculated); } void EnemyPatrolState::process(double delta) { if (this->path) { if (get_nav()->is_navigation_finished()) { this->path_point += 1; set_patrol_target(this->path->point_at(this->path_point)); } Vector3 const direction{ get_body()->get_global_position().direction_to(get_nav()->get_next_path_position()).normalized() }; if (get_nav()->get_avoidance_enabled()) { get_nav()->set_velocity(direction); } else { on_velocity_calculated(direction); } } } 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()); }