feat: start of firing enemies

This commit is contained in:
Sara 2024-12-09 23:55:11 +01:00
parent 7a6aefc44c
commit 2054474c01
12 changed files with 212 additions and 14 deletions

View file

@ -1,9 +1,102 @@
#include "enemy.hpp"
#include "utils/godot_macros.hpp"
#include <godot_cpp/classes/physics_direct_space_state3d.hpp>
#include <godot_cpp/classes/physics_ray_query_parameters3d.hpp>
#include <godot_cpp/classes/timer.hpp>
#include <godot_cpp/classes/world3d.hpp>
#include <godot_cpp/variant/utility_functions.hpp>
void Enemy::_bind_methods() {}
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<PlayerAnimTree>("CharacterModel/AnimationTree");
this->agent = this->get_node<gd::NavigationAgent3D>("%NavigationAgent3D");
gd::Timer *timer{memnew(gd::Timer)};
this->add_child(timer);
timer->start(this->update_interval);
timer->connect("timeout", callable_mp(this, &Enemy::update));
}
void Enemy::update() {
if(this->current_action_fn != nullptr)
this->current_action_fn = (ActionFn)(this->*current_action_fn)();
}
void Enemy::chase_enter() {
if(this->player != nullptr)
this->agent->set_target_position(this->player->get_global_position());
}
void Enemy::chase() {
bool const is_chasing{!this->agent->is_navigation_finished()};
this->anim_tree->set_lock_running(is_chasing);
this->anim_tree->set_aim_weapon(!is_chasing);
if(is_chasing) {
gd::Vector3 const global_pos{this->get_global_position()};
gd::Vector3 const target{global_pos * 2 - this->agent->get_next_path_position()};
this->look_at({target.x, global_pos.y, target.z});
}
if(this->get_global_position().distance_to(this->player->get_global_position()) >= this->agent->get_target_desired_distance())
this->chase_enter(); // repath
}
Enemy::ActionFn Enemy::miss_enter() {
this->anim_tree->set_aim_weapon(true);
return (ActionFn)&Enemy::miss;
}
Enemy::ActionFn Enemy::miss() {
if(this->anim_tree->get_current_state().begins_with("Fire") || this->anim_tree->get_fire_weapon()) // last shot still going
return (ActionFn)&Enemy::miss;
if(this->can_see_player) {
gd::Basis const basis{this->get_global_basis()};
this->look_at(this->get_global_position() * 2 - this->player->get_global_position() + basis.get_column(0));
this->anim_tree->set_aim_weapon(true);
this->anim_tree->set_fire_weapon();
if(this->anim_tree->get_fire_weapon()) {
gd::UtilityFunctions::print("!!! miss fired");
return ++this->missed_shots > SHOTS_BEFORE_HIT ? (ActionFn)&Enemy::hit_enter : (ActionFn)&Enemy::miss_enter;
}
} else {
this->chase();
}
return (ActionFn)&Enemy::miss;
}
Enemy::ActionFn Enemy::hit_enter() {
this->anim_tree->set_aim_weapon(true);
return (ActionFn)&Enemy::hit;
}
Enemy::ActionFn Enemy::hit() {
if(this->anim_tree->get_current_state().begins_with("Fire") || this->anim_tree->get_fire_weapon()) // last shot still going
return (ActionFn)&Enemy::hit;
else if(this->can_see_player) {
this->look_at(this->get_global_position() * 2 - this->player->get_global_position());
this->anim_tree->set_aim_weapon(true);
this->anim_tree->set_fire_weapon();
this->missed_shots = 0;
return (ActionFn)&Enemy::miss_enter;
} else {
this->chase();
}
return (ActionFn)&Enemy::hit;
}
void Enemy::_physics_process(double delta) {
this->update_can_see_player();
gd::Basis const basis{this->get_global_basis()};
gd::Vector3 const motion{this->anim_tree->get_root_motion_position()};
this->set_velocity({
basis.get_column(0) * motion.x +
basis.get_column(1) * motion.y +
basis.get_column(2) * motion.z
});
this->move_and_slide();
}
void Enemy::damage() {
@ -11,3 +104,26 @@ void Enemy::damage() {
this->set_collision_mask(0x0);
this->set_collision_layer(0x0);
}
void Enemy::notice_player(Player *player) {
this->player = player;
this->current_action_fn = (ActionFn)&Enemy::miss_enter;
}
void Enemy::update_can_see_player() {
if(this->player == nullptr)
return;
gd::Vector3 origin{this->get_global_position() + gd::Vector3{0.f, 1.8f, 0.f}};
gd::PhysicsDirectSpaceState3D *space{this->get_world_3d()->get_direct_space_state()};
gd::Ref<gd::PhysicsRayQueryParameters3D> query{gd::PhysicsRayQueryParameters3D::create(origin, this->player->get_global_position() + gd::Vector3{0.f, 1.8f, 0.f})};
gd::Dictionary dict{space->intersect_ray(query)};
this->can_see_player = (dict.is_empty() || gd::Object::cast_to<Node>(dict["collider"]) == this->player);
}
void Enemy::set_update_interval(float time) {
this->update_interval = time;
}
float Enemy::get_update_interval() const {
return this->update_interval;
}

View file

@ -2,17 +2,46 @@
#define ENEMY_HPP
#include "damageable_entity.hpp"
#include "player.hpp"
#include "player_anim_tree.hpp"
#include "utils/godot_macros.hpp"
#include <godot_cpp/classes/character_body3d.hpp>
#include <godot_cpp/classes/navigation_agent3d.hpp>
namespace gd = godot;
class Enemy : public gd::CharacterBody3D, public DamageableEntity {
GDCLASS(Enemy, gd::CharacterBody3D);
static void _bind_methods();
typedef void *(Enemy::*ActionFn_)();
typedef ActionFn_ (Enemy::*ActionFn)();
public:
virtual void _ready() override;
void update();
void chase_enter();
void chase();
ActionFn miss_enter();
ActionFn miss();
ActionFn hit_enter();
ActionFn hit();
ActionFn stab_enter();
ActionFn stab();
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;
private:
int const SHOTS_BEFORE_HIT{2};
int missed_shots{0};
double update_interval{0.2};
ActionFn current_action_fn{nullptr};
bool can_see_player{false};
Player *player{nullptr};
gd::NavigationAgent3D *agent{nullptr};
PlayerAnimTree *anim_tree{nullptr};
};

View file

@ -7,6 +7,7 @@ void PlayerAnimTree::_bind_methods() {
GDPROPERTY(target_turn_speed, gd::Variant::FLOAT);
GDPROPERTY(is_walking, gd::Variant::FLOAT);
GDPROPERTY(walk_speed, gd::Variant::FLOAT);
GDPROPERTY(lock_running, gd::Variant::BOOL);
GDFUNCTION(get_is_running);
GDPROPERTY(aim_weapon, gd::Variant::BOOL);
GDFUNCTION(get_fire_weapon);
@ -66,12 +67,20 @@ float PlayerAnimTree::get_walk_speed() const {
return this->walk_speed;
}
void PlayerAnimTree::set_lock_running(bool value) {
this->lock_running = value;
}
bool PlayerAnimTree::get_lock_running() const {
return this->lock_running;
}
void PlayerAnimTree::set_is_running() {
this->running_time = this->RUN_PARAM_DECAY;
}
bool PlayerAnimTree::get_is_running() const {
return this->running_time > 0.0;
return this->lock_running || this->running_time > 0.0;
}
void PlayerAnimTree::set_aim_weapon(bool value) {
@ -97,7 +106,7 @@ void PlayerAnimTree::set_stab() {
}
bool PlayerAnimTree::get_stab() {
bool const is_set{this->fire_weapon > 0.0};
bool const is_set{this->stab > 0.0};
this->stab = 0.0;
return is_set;
}
@ -111,6 +120,10 @@ bool PlayerAnimTree::match_tags(Tags tags) const {
return (this->current_tags & tags) != Tags::None;
}
gd::StringName const &PlayerAnimTree::get_current_state() const {
return this->last_known_anim;
}
void PlayerAnimTree::update_tags(gd::StringName const &anim) {
if(anim != this->last_known_anim && this->fsm->get_travel_path().size() <= 1) {
this->last_known_anim = anim;

View file

@ -26,6 +26,8 @@ public:
bool get_is_walking() const;
void set_walk_speed(float value);
float get_walk_speed() const;
void set_lock_running(bool value);
bool get_lock_running() const;
void set_is_running();
bool get_is_running() const;
void set_aim_weapon(bool value);
@ -36,6 +38,7 @@ public:
bool get_stab();
void death_animation();
bool match_tags(Tags tags) const;
gd::StringName const &get_current_state() const;
private:
void update_tags(gd::StringName const &anim);
void commit_turn_speed();
@ -50,6 +53,7 @@ private:
float turn_speed{0.f}; //!< blend position of turn animation (-1 to 1). Moved towards target_turn_speed every frame.
float target_turn_speed{0.f}; //!< target blend position of turn animation.
bool is_walking{false}; //!< set to true if the walk animation should be playing.
bool lock_running{false}; //!< lock animation into running instead of walking.
float walk_speed{0.f}; //!< blend amount between RESET/Rest animation and walk animation in walk state.
double running_time{0.0}; //!< time in seconds to keep running for.
bool aim_weapon{false}; //!< set to true to play the aim animation.

View file

@ -23,7 +23,7 @@ void initialize_gdextension_types(ModuleInitializationLevel p_level)
utils::godot_cpp_utils_register_types();
GDREGISTER_CLASS(Player);
GDREGISTER_CLASS(PlayerAnimTree);
GDREGISTER_CLASS(Enemy);
GDREGISTER_RUNTIME_CLASS(Enemy);
GDREGISTER_CLASS(HitscanMuzzle);
GDREGISTER_RUNTIME_CLASS(CameraEffects);
GDREGISTER_RUNTIME_CLASS(CameraEffectSource);