diff --git a/godot/boot.tscn b/godot/boot.tscn index 70242bf..10b63e7 100644 --- a/godot/boot.tscn +++ b/godot/boot.tscn @@ -1,18 +1,41 @@ -[gd_scene load_steps=4 format=3 uid="uid://c2kiuk2yxdlfr"] +[gd_scene load_steps=6 format=3 uid="uid://c2kiuk2yxdlfr"] [ext_resource type="PackedScene" uid="uid://1kr3qqan3trk" path="res://objects/player.tscn" id="1_7kr4i"] [ext_resource type="PackedScene" uid="uid://dm3355tbkvx24" path="res://objects/section_2.tscn" id="2_og5hb"] [ext_resource type="PackedScene" uid="uid://0fykl1mw3c12" path="res://objects/enemy.tscn" id="3_ppe0x"] +[sub_resource type="GDScript" id="GDScript_peyat"] +script/source = "extends Area3D + + +func _on_body_entered(body: Node3D) -> void: + if body is Player: + $\"../Enemy2\".notice_player(body as Player) + $\"../Enemy\".notice_player(body as Player) +" + +[sub_resource type="BoxShape3D" id="BoxShape3D_ggysn"] +size = Vector3(3.69043, 1, 8.0553) + [node name="Boot" type="Node3D"] [node name="Environment" parent="." instance=ExtResource("2_og5hb")] [node name="Enemy" parent="." instance=ExtResource("3_ppe0x")] -transform = Transform3D(-1, 0, -8.74228e-08, 0, 1, 0, 8.74228e-08, 0, -1, 9.02629, 0.21507, -7.46576) +transform = Transform3D(-1, 0, -8.74228e-08, 0, 1, 0, 8.74228e-08, 0, -1, 9.02629, 0.258895, -7.46576) + +[node name="Area3D" type="Area3D" parent="."] +transform = Transform3D(0.734015, 0, -0.679134, 0, 1, 0, 0.679134, 0, 0.734015, 2.09421, 0.536154, -14.1446) +script = SubResource("GDScript_peyat") + +[node name="CollisionShape3D" type="CollisionShape3D" parent="Area3D"] +transform = Transform3D(1, 0, -4.91743e-07, 0, 1, 0, 4.91743e-07, 0, 1, 0, 0, 0) +shape = SubResource("BoxShape3D_ggysn") [node name="Enemy2" parent="." instance=ExtResource("3_ppe0x")] transform = Transform3D(0.139795, 0, -0.990181, 0, 1, 0, 0.990181, 0, 0.139795, 8.69408, 0.536154, -15.4052) [node name="Player" parent="." instance=ExtResource("1_7kr4i")] transform = Transform3D(0.0816776, 0, 0.996659, 0, 1, 0, -0.996659, 0, 0.0816776, -2.58966, 0.891191, -2.77265) + +[connection signal="body_entered" from="Area3D" to="Area3D" method="_on_body_entered"] diff --git a/godot/models/animations/character_tree.tres b/godot/models/animations/character_tree.tres index 550a46f..79528e2 100644 --- a/godot/models/animations/character_tree.tres +++ b/godot/models/animations/character_tree.tres @@ -172,9 +172,9 @@ advance_mode = 2 [sub_resource type="AnimationNodeStateMachineTransition" id="AnimationNodeStateMachineTransition_g0i3x"] xfade_time = 0.4 +priority = 2 advance_mode = 2 -advance_expression = "get_is_running() -" +advance_expression = "get_is_running() && is_walking()" [sub_resource type="AnimationNodeStateMachineTransition" id="AnimationNodeStateMachineTransition_ocffv"] xfade_time = 0.3 @@ -185,7 +185,7 @@ advance_expression = "get_is_running() [sub_resource type="AnimationNodeStateMachineTransition" id="AnimationNodeStateMachineTransition_h0my5"] xfade_time = 0.4 advance_mode = 2 -advance_expression = "!get_is_running()" +advance_expression = "!get_is_running() || !is_walking()" [sub_resource type="AnimationNodeStateMachineTransition" id="AnimationNodeStateMachineTransition_nymcr"] xfade_time = 0.3 @@ -205,6 +205,7 @@ advance_expression = "get_is_running() " [sub_resource type="AnimationNodeStateMachineTransition" id="AnimationNodeStateMachineTransition_4q0ot"] +priority = 2 advance_mode = 2 advance_expression = "get_stab()" @@ -244,7 +245,7 @@ states/Start/position = Vector2(572.26, 14.954) "states/Walk [turn]/node" = SubResource("AnimationNodeBlendTree_2mbyh") "states/Walk [turn]/position" = Vector2(572.26, 258.767) transitions = ["Aim [aim] [turn]", "Aim Down [aim]", SubResource("AnimationNodeStateMachineTransition_4lybd"), "Aim Down [aim]", "Walk [turn]", SubResource("AnimationNodeStateMachineTransition_5aafd"), "Aim [aim] [turn]", "Fire [aim]", SubResource("AnimationNodeStateMachineTransition_8f4gl"), "Fire [aim]", "Aim [aim] [turn]", SubResource("AnimationNodeStateMachineTransition_xnat3"), "Walk [turn]", "Aim [aim] [turn]", SubResource("AnimationNodeStateMachineTransition_12bos"), "Aim Down [aim]", "Stationary [turn]", SubResource("AnimationNodeStateMachineTransition_n0ndr"), "Walk [turn]", "Stationary [turn]", SubResource("AnimationNodeStateMachineTransition_0wc5e"), "Stationary [turn]", "Walk [turn]", SubResource("AnimationNodeStateMachineTransition_4hisb"), "Start", "Stationary [turn]", SubResource("AnimationNodeStateMachineTransition_bmty6"), "Stationary [turn]", "RESET To Aim", SubResource("AnimationNodeStateMachineTransition_kyd6p"), "RESET To Aim", "Aim [aim] [turn]", SubResource("AnimationNodeStateMachineTransition_w5kob"), "Stationary [turn]", "Run", SubResource("AnimationNodeStateMachineTransition_g0i3x"), "Walk [turn]", "Run", SubResource("AnimationNodeStateMachineTransition_ocffv"), "Run", "Stationary [turn]", SubResource("AnimationNodeStateMachineTransition_h0my5"), "Run", "Walk [turn]", SubResource("AnimationNodeStateMachineTransition_nymcr"), "Run", "Stab", SubResource("AnimationNodeStateMachineTransition_g5qf0"), "Stab", "Run", SubResource("AnimationNodeStateMachineTransition_27kmb"), "Stationary [turn]", "Stab", SubResource("AnimationNodeStateMachineTransition_4q0ot"), "Stab", "Stationary [turn]", SubResource("AnimationNodeStateMachineTransition_h6ujc"), "Walk [turn]", "Stab", SubResource("AnimationNodeStateMachineTransition_3bu8l"), "Stab", "Walk [turn]", SubResource("AnimationNodeStateMachineTransition_cpibo")] -graph_offset = Vector2(-172.078, -8.655) +graph_offset = Vector2(-87.4971, -116.108) [sub_resource type="AnimationNodeAnimation" id="AnimationNodeAnimation_au62i"] animation = &"Fall_die" diff --git a/godot/objects/enemy.tscn b/godot/objects/enemy.tscn index efc1631..e737572 100644 --- a/godot/objects/enemy.tscn +++ b/godot/objects/enemy.tscn @@ -98,4 +98,7 @@ aim_weapon = true transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.19104, 0) shape = SubResource("CapsuleShape3D_3tduq") +[node name="NavigationAgent3D" type="NavigationAgent3D" parent="."] +unique_name_in_owner = true + [editable path="CharacterModel"] diff --git a/godot/objects/player.tscn b/godot/objects/player.tscn index f001071..f4e014a 100644 --- a/godot/objects/player.tscn +++ b/godot/objects/player.tscn @@ -11,7 +11,7 @@ radius = 0.1 [node name="Player" type="Player"] collision_layer = 3 collision_mask = 3 -floor_snap_length = 0.2 +floor_snap_length = 1.0 [node name="CharacterModel" parent="." instance=ExtResource("1_cwt7u")] unique_name_in_owner = true diff --git a/godot/objects/section_2.tscn b/godot/objects/section_2.tscn index 22bdfbb..7ce3b02 100644 --- a/godot/objects/section_2.tscn +++ b/godot/objects/section_2.tscn @@ -353,7 +353,16 @@ frequency_max = 5.0 transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 4.66028, 2.51555, -0.763191) [node name="ArtilleryTarget3" parent="." instance=ExtResource("13_rl1f8")] -transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2.00799, 2.51555, -22.162) +frequency_max = 8.0 +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.977764, 2.51555, -22.0148) + +[node name="ArtilleryTarget6" parent="." instance=ExtResource("13_rl1f8")] +frequency_max = 8.0 +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -7.26403, 2.51555, -11.5654) + +[node name="ArtilleryTarget7" parent="." instance=ExtResource("13_rl1f8")] +frequency_max = 8.0 +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 17.9765, 2.51555, -24.0753) [node name="ArtilleryTarget4" parent="." instance=ExtResource("13_rl1f8")] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 18.9532, 2.51555, -6.60339) diff --git a/models/character.blend b/models/character.blend index a0fda81..d864349 100644 --- a/models/character.blend +++ b/models/character.blend @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:855235acb748c32a4816fb40be67f05709bc96651bd5a1a956fb9f6cda49ea59 -size 3281052 +oid sha256:1d872d2473ac5ed618ad4f3a9c59135714066fcf4640e800c4672dafb13bc28c +size 3286484 diff --git a/models/character.blend1 b/models/character.blend1 index 24a945e..e4406da 100644 Binary files a/models/character.blend1 and b/models/character.blend1 differ diff --git a/src/enemy.cpp b/src/enemy.cpp index 5d8253f..449b593 100644 --- a/src/enemy.cpp +++ b/src/enemy.cpp @@ -1,9 +1,102 @@ #include "enemy.hpp" +#include "utils/godot_macros.hpp" +#include +#include +#include +#include +#include -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("CharacterModel/AnimationTree"); + this->agent = this->get_node("%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 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(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; +} diff --git a/src/enemy.hpp b/src/enemy.hpp index b564be6..ee7c3a8 100644 --- a/src/enemy.hpp +++ b/src/enemy.hpp @@ -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 +#include 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}; }; diff --git a/src/player_anim_tree.cpp b/src/player_anim_tree.cpp index 89bb332..ded7b72 100644 --- a/src/player_anim_tree.cpp +++ b/src/player_anim_tree.cpp @@ -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; diff --git a/src/player_anim_tree.hpp b/src/player_anim_tree.hpp index a3bd587..68324f6 100644 --- a/src/player_anim_tree.hpp +++ b/src/player_anim_tree.hpp @@ -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. diff --git a/src/register_types.cpp b/src/register_types.cpp index c869e28..1d33ec0 100644 --- a/src/register_types.cpp +++ b/src/register_types.cpp @@ -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);