feat: start of firing enemies
parent
7a6aefc44c
commit
2054474c01
|
@ -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"]
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
BIN
models/character.blend (Stored with Git LFS)
BIN
models/character.blend (Stored with Git LFS)
Binary file not shown.
Binary file not shown.
118
src/enemy.cpp
118
src/enemy.cpp
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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};
|
||||
};
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue