diff --git a/src/enemy.cpp b/src/enemy.cpp index 71552fe..c9d9c60 100644 --- a/src/enemy.cpp +++ b/src/enemy.cpp @@ -8,26 +8,35 @@ void Enemy::_bind_methods() { #define CLASSNAME Enemy - GDFUNCTION_ARGS(notice_player, "player"); GDPROPERTY(update_interval, gd::Variant::FLOAT); } void Enemy::_ready() { - this->anim_tree = this->get_node("CharacterModel/AnimationTree"); - this->agent = this->get_node("%NavigationAgent3D"); + this->anim_tree = this->get_node("CharacterModel/AnimationTree"); // character animation tree from model (model shared with player) + this->agent = this->get_node("%NavigationAgent3D"); // navigation agent + // set up the timer used to reduce process time gd::Timer *timer{memnew(gd::Timer)}; this->add_child(timer); timer->start(this->update_interval); timer->connect("timeout", callable_mp(this, &Enemy::update)); + // starting target rotation is just the current rotation this->target_rotation = this->get_rotation().y; + // a sound the enemy makes while alive to induce tension and inform the player of an enemy nearby this->drone_sound = this->get_node("%DroneSound"); + // debugging label (only enable if available) if(this->has_node("%DebugLabel")) this->debug_label = this->get_node("%DebugLabel"); + // setup navigation obstacle avoidance this->agent->connect("velocity_computed", callable_mp(this, &Enemy::_on_velocity_calculated)); + // fetch player and setup current action + this->player = Player::get_player_instance(); + this->current_action_fn = (ActionFn)&Enemy::wait_line_of_sight; } void Enemy::_process(double delta) { + // update debuging label this->set_current_state_name(this->current_state_name); + // rotate towards target (defined by either aiming or navigation) float const angle_left{gd::Math::wrapf(this->target_rotation - this->get_rotation().y, -M_PIf, M_PIf)}; float const step(gd::Math::sign(angle_left) * delta * (this->anim_tree->get_current_state().begins_with("Run") ? this->TURN_SPEED : this->AIM_SPEED)); if(gd::Math::abs(angle_left) <= gd::Math::abs(step)) { @@ -37,6 +46,7 @@ void Enemy::_process(double delta) { this->rotate_y(step); this->at_target_angle = false; } + // keep track of the player's last known transform for chasing if(this->can_see_player) { this->last_known_player_position = this->player->get_global_position(); this->last_known_player_rotation = -this->player->get_global_rotation().y; @@ -148,15 +158,16 @@ Enemy::ActionFn Enemy::stop_running() { } void Enemy::_physics_process(double delta) { + // transform root motion to global velocity gd::Basis const basis{this->get_global_basis()}; gd::Vector3 const motion{this->anim_tree->get_root_motion_position()}; this->set_velocity(gd::Vector3{ basis.get_column(0) * motion.x + basis.get_column(1) * motion.y + basis.get_column(2) * motion.z - } / delta); - this->move_and_slide(); - this->update_can_see_player(); + } / delta); // convert from m/s to m/frame + this->move_and_slide(); // update movement + this->update_can_see_player(); // check vision } void Enemy::damage() { @@ -170,20 +181,20 @@ void Enemy::damage() { this->set_current_state_name("None"); } -void Enemy::notice_player(Player *player) { - this->player = player; - this->current_action_fn = (ActionFn)&Enemy::wait_line_of_sight; -} - void Enemy::update_can_see_player() { - if(this->player == nullptr) + // don't update sightlines if player is not found + // also don't update sightlines if the current action is firing, to avoid sudden turn-around shots that feel unfair + if(this->player == nullptr || this->current_action_fn == (ActionFn)&Enemy::wait_end_of_shot) return; + // calculate line segment to cast gd::Vector3 const origin{this->get_global_position() + gd::Vector3{0.f, 1.8f, 0.f}}; gd::Vector3 const target{this->player->get_global_position() + gd::Vector3{0.f, 1.8f, 0.f}}; + // check if the target is in field of view float const dot{(target - origin).normalized().dot(this->get_global_basis().get_column(2))}; if(this->current_action_fn != (ActionFn)&Enemy::chase_player && dot <= 0.2f && target.distance_to(origin) > 4.f) { - this->can_see_player = false; + this->can_see_player = false; // target not in field of view } else { + // check if the sightline is obstructed by raycast gd::PhysicsDirectSpaceState3D *space{this->get_world_3d()->get_direct_space_state()}; gd::Ref query{gd::PhysicsRayQueryParameters3D::create(origin, target)}; gd::Dictionary dict{space->intersect_ray(query)}; diff --git a/src/enemy.hpp b/src/enemy.hpp index 2ecf974..9401451 100644 --- a/src/enemy.hpp +++ b/src/enemy.hpp @@ -32,7 +32,6 @@ public: ActionFn stop_running(); virtual void _physics_process(double delta) override; virtual void damage() override; - void notice_player(Player *player); void update_can_see_player(); void set_update_interval(float time); float get_update_interval() const; @@ -44,7 +43,7 @@ private: int const SHOTS_BEFORE_MOVE{5}; float const AIM_SPEED{8.f}; float const TURN_SPEED{10.f}; - float const AIM_OFFSET{-0.18f}; + float const AIM_OFFSET{-0.2f}; float const STAB_RANGE{3.5f}; float const MOVING_NAV_PRIORITY{.5f}; float const STATIONARY_NAV_PRIORITY{1.f}; diff --git a/src/player.cpp b/src/player.cpp index c0404c4..0575c52 100644 --- a/src/player.cpp +++ b/src/player.cpp @@ -9,6 +9,10 @@ void Player::_bind_methods() { GDFUNCTION(get_input_directions); } +void Player::_enter_tree() { + this->player_instance = this; +} + void Player::_ready() { if(gd::Engine::get_singleton()->is_editor_hint()) return; @@ -123,3 +127,9 @@ void Player::_on_switch_shoulder(gd::Ref event, float) { gd::Vector2 Player::get_input_directions() const { return this->input_directions; } + +Player *Player::get_player_instance() { + return Player::player_instance; +} + +Player *Player::player_instance{nullptr}; diff --git a/src/player.hpp b/src/player.hpp index 70a741e..bf71c82 100644 --- a/src/player.hpp +++ b/src/player.hpp @@ -13,6 +13,7 @@ class Player : public gd::CharacterBody3D, public DamageableEntity { GDCLASS(Player, gd::CharacterBody3D); static void _bind_methods(); public: + virtual void _enter_tree() override; virtual void _ready() override; virtual void _process(double delta) override; virtual void _physics_process(double delta) override; @@ -29,6 +30,8 @@ public: void _on_switch_shoulder(gd::Ref event, float); gd::Vector2 get_input_directions() const; + + static Player *get_player_instance(); private: PlayerAnimTree *anim_tree{nullptr}; gd::Node3D *camera_parent{nullptr}; @@ -46,6 +49,8 @@ private: float const AIMING_CAMERA_ROTATION_SPEED{1.f}; float const AIM_INPUT_THRESHOLD{-0.9f}; float const WALK_INPUT_THRESHOLD{0.5f}; + + static Player *player_instance; }; #endif // !TR_PLAYER_HPP