diff --git a/modules/wave_survival/hitscan_muzzle.cpp b/modules/wave_survival/hitscan_muzzle.cpp index 54518ceb..df255f5a 100644 --- a/modules/wave_survival/hitscan_muzzle.cpp +++ b/modules/wave_survival/hitscan_muzzle.cpp @@ -2,6 +2,7 @@ #include "health_status.h" #include "hitbox.h" #include "macros.h" +#include "muzzle_effect.h" #include "scene/resources/packed_scene.h" void HitscanMuzzle::_bind_methods() { @@ -9,6 +10,7 @@ void HitscanMuzzle::_bind_methods() { BIND_PROPERTY(Variant::FLOAT, spread); BIND_PROPERTY(Variant::INT, damage); BIND_PROPERTY(Variant::INT, ray_count); + BIND_HPROPERTY(Variant::OBJECT, muzzle_effect, PROPERTY_HINT_NODE_TYPE, "MuzzleEffect"); } void HitscanMuzzle::instantiate_impact_effect() { @@ -45,6 +47,7 @@ void HitscanMuzzle::ready() { } else { print_error("HitscanMuzzle::ready: impact effect is invalid"); } + set_enabled(false); } @@ -62,6 +65,9 @@ void HitscanMuzzle::_notification(int what) { } void HitscanMuzzle::shoot() { + if (this->muzzle_effect != nullptr) { + this->muzzle_effect->GDVIRTUAL_CALL(trigger); + } for (int i{ this->ray_count }; i > 0; --i) { set_transform(this->home_transform); rotate_object_local(Vector3(0.f, 1.f, 0.f), Math::random(-Math::PI, Math::PI)); @@ -95,3 +101,11 @@ void HitscanMuzzle::set_ray_count(int amount) { int HitscanMuzzle::get_ray_count() const { return this->ray_count; } + +void HitscanMuzzle::set_muzzle_effect(MuzzleEffect *effect) { + this->muzzle_effect = effect; +} + +MuzzleEffect *HitscanMuzzle::get_muzzle_effect() const { + return this->muzzle_effect; +} diff --git a/modules/wave_survival/hitscan_muzzle.h b/modules/wave_survival/hitscan_muzzle.h index 357dd3f4..ac040a73 100644 --- a/modules/wave_survival/hitscan_muzzle.h +++ b/modules/wave_survival/hitscan_muzzle.h @@ -2,6 +2,7 @@ #define HITSCAN_MUZZLE_H #include "scene/3d/physics/ray_cast_3d.h" +class MuzzleEffect; class HitscanMuzzle : public RayCast3D { GDCLASS(HitscanMuzzle, RayCast3D); @@ -21,11 +22,14 @@ public: int get_damage() const; void set_ray_count(int amount); int get_ray_count() const; + void set_muzzle_effect(MuzzleEffect *effect); + MuzzleEffect *get_muzzle_effect() const; private: float spread{ 0.001f }; int damage{ 1 }; int ray_count{ 1 }; + MuzzleEffect *muzzle_effect{ nullptr }; Transform3D home_transform{}; Ref impact_effect{}; diff --git a/modules/wave_survival/muzzle_effect.cpp b/modules/wave_survival/muzzle_effect.cpp new file mode 100644 index 00000000..e7306c8a --- /dev/null +++ b/modules/wave_survival/muzzle_effect.cpp @@ -0,0 +1,5 @@ +#include "muzzle_effect.h" + +void MuzzleEffect::_bind_methods() { + GDVIRTUAL_BIND(trigger); +} diff --git a/modules/wave_survival/muzzle_effect.h b/modules/wave_survival/muzzle_effect.h new file mode 100644 index 00000000..fd257f96 --- /dev/null +++ b/modules/wave_survival/muzzle_effect.h @@ -0,0 +1,14 @@ +#ifndef MUZZLE_EFFECT_H +#define MUZZLE_EFFECT_H + +#include "scene/3d/node_3d.h" + +class MuzzleEffect : public Node3D { + GDCLASS(MuzzleEffect, Node3D); + static void _bind_methods(); + +public: + GDVIRTUAL0_REQUIRED(trigger); +}; + +#endif // !MUZZLE_EFFECT_H diff --git a/modules/wave_survival/player_detector.cpp b/modules/wave_survival/player_detector.cpp index 831e2502..5e005e2f 100644 --- a/modules/wave_survival/player_detector.cpp +++ b/modules/wave_survival/player_detector.cpp @@ -1,4 +1,5 @@ #include "player_detector.h" +#include "sound_event_patchboard.h" String PlayerDetector::sig_awareness_changed{ "awareness_changed" }; @@ -6,6 +7,17 @@ void PlayerDetector::_bind_methods() { ADD_SIGNAL(MethodInfo(sig_awareness_changed, PropertyInfo(Variant::BOOL, "aware"))); } +// check if there is geometry between detector and player +bool PlayerDetector::line_of_sight_exists() const { + PhysicsDirectSpaceState3D::RayParameters params{ this->ray_params }; + params.from = get_global_position(); + params.to = PlayerBody::get_singleton()->get_global_position(); + PhysicsDirectSpaceState3D *space{ get_world_3d()->get_direct_space_state() }; + PhysicsDirectSpaceState3D::RayResult result{}; + space->intersect_ray(params, result); + return result.collider == nullptr; +} + // Check if the player is in a bounded area in front of the detector and unobscured. // As all tests are required to pass, we do them in increasing order of complexity, to minimize unneeded resource use. bool PlayerDetector::check() const { @@ -13,24 +25,18 @@ bool PlayerDetector::check() const { Vector3 const position{ get_global_position() }; Vector3 const target{ PlayerBody::get_singleton()->get_global_position() + Vector3{ 0.f, 1.5f, 0.f } }; // check if the target is in a view cone - if (forward.dot(target - position) < this->min_dot) { + if (forward.dot(target - position) < this->min_sight_dot) { return false; } // check if the target is in range - if (position.distance_squared_to(target) > this->max_distance * this->max_distance) { + if (position.distance_squared_to(target) > this->max_sight_range * this->max_sight_range) { return false; } - // check if the target is obscured - PhysicsDirectSpaceState3D::RayParameters params{ this->ray_params }; - params.from = position; - params.to = target; - PhysicsDirectSpaceState3D *space{ get_world_3d()->get_direct_space_state() }; - PhysicsDirectSpaceState3D::RayResult result{}; - space->intersect_ray(params, result); - return result.collider == nullptr; + return line_of_sight_exists(); } void PlayerDetector::ready() { + SoundEventPatchboard::get_singleton()->connect(SoundEventPatchboard::sig_sound_triggered, callable_mp(this, &self_type::on_player_sound)); this->ray_params.exclude.insert(PlayerBody::get_singleton()->get_rid()); } @@ -46,6 +52,12 @@ void PlayerDetector::process(double delta) { } } +void PlayerDetector::on_player_sound(Vector3 at, float range) { + if (get_global_position().distance_squared_to(at) <= range * range && line_of_sight_exists()) { + set_aware_of_player(true); + } +} + void PlayerDetector::set_aware_of_player(bool value) { emit_signal(sig_awareness_changed, value); this->aware_of_player = value; diff --git a/modules/wave_survival/player_detector.h b/modules/wave_survival/player_detector.h index 839e5b87..c21dd6f3 100644 --- a/modules/wave_survival/player_detector.h +++ b/modules/wave_survival/player_detector.h @@ -7,9 +7,11 @@ class PlayerDetector : public Node3D { GDCLASS(PlayerDetector, Node3D); static void _bind_methods(); + bool line_of_sight_exists() const; bool check() const; void ready(); void process(double delta); + void on_player_sound(Vector3 at, float range); void set_aware_of_player(bool value); protected: @@ -20,10 +22,15 @@ public: private: bool aware_of_player{ false }; - float max_distance{ 100.f }; - float min_dot{ 0.1f }; + + float max_hearing_range{ 40.f }; + + float max_sight_range{ 100.f }; + float min_sight_dot{ 0.1f }; + double query_time{ 0.3 }; double query_timer{ 0.0 }; + PhysicsDirectSpaceState3D::RayParameters ray_params{}; public: diff --git a/modules/wave_survival/register_types.cpp b/modules/wave_survival/register_types.cpp index 7f8dc63d..c9c8304e 100644 --- a/modules/wave_survival/register_types.cpp +++ b/modules/wave_survival/register_types.cpp @@ -8,6 +8,7 @@ #include "wave_survival/hitbox.h" #include "wave_survival/hitscan_muzzle.h" #include "wave_survival/interactable.h" +#include "wave_survival/muzzle_effect.h" #include "wave_survival/npc_unit.h" #include "wave_survival/patrol_path.h" #include "wave_survival/player_body.h" @@ -15,6 +16,8 @@ #include "wave_survival/player_detector.h" #include "wave_survival/player_input.h" #include "wave_survival/player_interactor.h" +#include "wave_survival/sound_event_node.h" +#include "wave_survival/sound_event_patchboard.h" #include "wave_survival/state.h" #include "wave_survival/state_machine.h" #include "wave_survival/weapon_base.h" @@ -49,6 +52,12 @@ void initialize_wave_survival_module(ModuleInitializationLevel p_level) { GDREGISTER_CLASS(PlayerInteractor); GDREGISTER_CLASS(Interactable); GDREGISTER_CLASS(Revolver); + GDREGISTER_CLASS(SoundEventPatchboard); + GDREGISTER_CLASS(SoundEventNode); + GDREGISTER_CLASS(MuzzleEffect); + + memnew(SoundEventPatchboard); + Engine::get_singleton()->add_singleton(Engine::Singleton("SoundEventPatchboard", SoundEventPatchboard::get_singleton())); } void uninitialize_wave_survival_module(ModuleInitializationLevel p_level) { diff --git a/modules/wave_survival/sound_event_node.cpp b/modules/wave_survival/sound_event_node.cpp new file mode 100644 index 00000000..45ef167c --- /dev/null +++ b/modules/wave_survival/sound_event_node.cpp @@ -0,0 +1,44 @@ +#include "sound_event_node.h" +#include "macros.h" +#include "sound_event_patchboard.h" + +void SoundEventNode::_bind_methods() { + ClassDB::bind_method(D_METHOD("trigger_event"), &self_type::trigger_event); + BIND_PROPERTY(Variant::BOOL, trigger_on_ready); +} + +void SoundEventNode::ready() { + if (this->trigger_on_ready) { + trigger_event(); + } +} + +void SoundEventNode::_notification(int what) { + if (Engine::get_singleton()->is_editor_hint()) { + return; + } + + if (what == NOTIFICATION_READY) { + ready(); + } +} + +void SoundEventNode::trigger_event() { + SoundEventPatchboard::get_singleton()->trigger_sound(get_global_position(), this->range); +} + +void SoundEventNode::set_trigger_on_ready(bool value) { + this->trigger_on_ready = value; +} + +bool SoundEventNode::get_trigger_on_ready() const { + return this->trigger_on_ready; +} + +void SoundEventNode::set_range(float value) { + this->range = value; +} + +float SoundEventNode::get_range() const { + return this->range; +} diff --git a/modules/wave_survival/sound_event_node.h b/modules/wave_survival/sound_event_node.h new file mode 100644 index 00000000..417dbd40 --- /dev/null +++ b/modules/wave_survival/sound_event_node.h @@ -0,0 +1,26 @@ +#ifndef SOUND_EVENT_NODE_H +#define SOUND_EVENT_NODE_H + +#include "scene/3d/node_3d.h" + +class SoundEventNode : public Node3D { + GDCLASS(SoundEventNode, Node3D); + static void _bind_methods(); + void ready(); + +protected: + void _notification(int what); + +public: + void trigger_event(); + void set_trigger_on_ready(bool value); + bool get_trigger_on_ready() const; + void set_range(float value); + float get_range() const; + +private: + float range{ 20.f }; + bool trigger_on_ready{ true }; +}; + +#endif // !SOUND_EVENT_NODE_H diff --git a/modules/wave_survival/sound_event_patchboard.cpp b/modules/wave_survival/sound_event_patchboard.cpp new file mode 100644 index 00000000..0fd2beb5 --- /dev/null +++ b/modules/wave_survival/sound_event_patchboard.cpp @@ -0,0 +1,27 @@ +#include "sound_event_patchboard.h" + +SoundEventPatchboard *SoundEventPatchboard::singleton_instance{ nullptr }; +String const SoundEventPatchboard::sig_sound_triggered{ "sound_triggered" }; + +void SoundEventPatchboard::_bind_methods() { + ClassDB::bind_method(D_METHOD("trigger_sound", "at", "range"), &self_type::trigger_sound); + ADD_SIGNAL(MethodInfo(sig_sound_triggered, PropertyInfo(Variant::VECTOR3, "at"), PropertyInfo(Variant::FLOAT, "range"))); +} + +SoundEventPatchboard::SoundEventPatchboard() { + singleton_instance = this; +} + +SoundEventPatchboard::~SoundEventPatchboard() { + if (singleton_instance == this) { + singleton_instance = nullptr; + } +} + +SoundEventPatchboard *SoundEventPatchboard::get_singleton() { + return singleton_instance; +} + +void SoundEventPatchboard::trigger_sound(Vector3 at, float range) { + emit_signal(sig_sound_triggered, at, range); +} diff --git a/modules/wave_survival/sound_event_patchboard.h b/modules/wave_survival/sound_event_patchboard.h new file mode 100644 index 00000000..1709edfa --- /dev/null +++ b/modules/wave_survival/sound_event_patchboard.h @@ -0,0 +1,22 @@ +#ifndef SOUND_EVENT_PATCHBOARD_H +#define SOUND_EVENT_PATCHBOARD_H + +#include "core/object/class_db.h" +#include "core/object/object.h" + +class SoundEventPatchboard : public Object { + GDCLASS(SoundEventPatchboard, Object); + static void _bind_methods(); + static SoundEventPatchboard *singleton_instance; + +public: + SoundEventPatchboard(); + virtual ~SoundEventPatchboard(); + static SoundEventPatchboard *get_singleton(); + void trigger_sound(Vector3 at, float range); + +public: + static String const sig_sound_triggered; +}; + +#endif // !SOUND_EVENT_PATCHBOARD_H diff --git a/project/assets/models/weapons/revolver.blend b/project/assets/models/weapons/revolver.blend index 734841e9..20ba1ca0 100644 Binary files a/project/assets/models/weapons/revolver.blend and b/project/assets/models/weapons/revolver.blend differ diff --git a/project/assets/models/weapons/revolver.blend1 b/project/assets/models/weapons/revolver.blend1 index 35415d08..ba75f7fe 100644 Binary files a/project/assets/models/weapons/revolver.blend1 and b/project/assets/models/weapons/revolver.blend1 differ diff --git a/project/objects/weapons/revolver.tscn b/project/objects/weapons/revolver.tscn index cc21da69..7262dc90 100644 --- a/project/objects/weapons/revolver.tscn +++ b/project/objects/weapons/revolver.tscn @@ -1,7 +1,14 @@ -[gd_scene load_steps=2 format=3 uid="uid://cfgwif53qypko"] +[gd_scene load_steps=3 format=3 uid="uid://cfgwif53qypko"] [ext_resource type="PackedScene" uid="uid://bkw6pt33nbn2" path="res://assets/models/weapons/revolver.blend" id="1_5ynga"] +[sub_resource type="GDScript" id="GDScript_5ynga"] +script/source = "extends MuzzleEffect + +func trigger() -> void: + SoundEventPatchboard.trigger_sound(global_position, 100) +" + [node name="Revolver" type="Revolver" node_paths=PackedStringArray("anim")] anim = NodePath("revolver/AnimationPlayer") single_action_spread = 0.003 @@ -15,12 +22,22 @@ layers = 2 [node name="Cube" parent="revolver/Character/Skeleton3D" index="1"] layers = 2 -[node name="HitscanMuzzle" type="HitscanMuzzle" parent="."] +[node name="BoneAttachment3D" type="BoneAttachment3D" parent="revolver/Character/Skeleton3D" index="2"] +transform = Transform3D(1, -6.350722e-17, 4.732016e-17, 4.732016e-17, 0.95822614, 0.28601173, -6.350722e-17, -0.28601173, 0.95822614, -1.1196792e-16, -0.03667751, 0.009908612) +bone_name = "root" +bone_idx = 39 + +[node name="MuzzleEffect" type="MuzzleEffect" parent="revolver/Character/Skeleton3D/BoneAttachment3D"] +transform = Transform3D(1, 0, 0, 0, 0.95768696, -0.28781185, 0, 0.28781185, 0.95768696, 2.4018701e-17, 0.19591203, -0.24464998) +script = SubResource("GDScript_5ynga") + +[node name="HitscanMuzzle" type="HitscanMuzzle" parent="." node_paths=PackedStringArray("muzzle_effect")] unique_name_in_owner = true transform = Transform3D(1, 0, 0, 0, -4.3711385e-08, 0.9999999, 0, -0.9999999, -4.3711385e-08, 0, 0, 0) target_position = Vector3(0, 200, 0) collision_mask = 6 collide_with_areas = true spread = 0.02 +muzzle_effect = NodePath("../revolver/Character/Skeleton3D/BoneAttachment3D/MuzzleEffect") [editable path="revolver"] diff --git a/project/objects/weapons/rifle.tscn b/project/objects/weapons/rifle.tscn index 4e6f1b16..01250baa 100644 --- a/project/objects/weapons/rifle.tscn +++ b/project/objects/weapons/rifle.tscn @@ -1,7 +1,14 @@ -[gd_scene load_steps=2 format=3 uid="uid://ce40pq785yoyi"] +[gd_scene load_steps=3 format=3 uid="uid://ce40pq785yoyi"] [ext_resource type="PackedScene" uid="uid://bodfxp36t6v36" path="res://assets/models/weapons/rifle.blend" id="1_afgyw"] +[sub_resource type="GDScript" id="GDScript_afgyw"] +script/source = "extends MuzzleEffect + +func trigger() -> void: + SoundEventPatchboard.trigger_sound(global_position, 100) +" + [node name="Rifle" type="Rifle" node_paths=PackedStringArray("anim")] anim = NodePath("rifle/AnimationPlayer") @@ -13,10 +20,19 @@ layers = 2 [node name="mesh" parent="rifle/Character/Skeleton3D" index="1"] layers = 2 +[node name="BoneAttachment3D" type="BoneAttachment3D" parent="rifle/Character/Skeleton3D" index="2"] +transform = Transform3D(1, -2.0932122e-15, 2.3524223e-18, 2.3525281e-18, 0.0022477505, 0.99999744, -2.0932126e-15, -0.9999974, 0.0022477508, 0.07988295, -0.13953947, -0.33976445) +bone_name = "root" +bone_idx = 39 + +[node name="MuzzleEffect" type="MuzzleEffect" parent="rifle/Character/Skeleton3D/BoneAttachment3D"] +transform = Transform3D(1, 0, 0, 0, 0.95768696, -0.28781185, 0, 0.28781185, 0.95768696, 2.4018701e-17, 0.19591203, -0.24464998) +script = SubResource("GDScript_afgyw") + [node name="AnimationPlayer" parent="rifle" index="2"] playback_default_blend_time = 0.1 -[node name="HitscanMuzzle" type="HitscanMuzzle" parent="."] +[node name="HitscanMuzzle" type="HitscanMuzzle" parent="." node_paths=PackedStringArray("muzzle_effect")] unique_name_in_owner = true transform = Transform3D(1, -2.0932111e-15, -2.7987948e-18, 2.3525281e-18, -0.00021316158, 0.99999976, -2.0932126e-15, -0.9999997, -0.00021316111, 0, 0, 0) target_position = Vector3(0, 200, 0) @@ -24,5 +40,6 @@ collision_mask = 6 collide_with_areas = true spread = 0.003 damage = 3 +muzzle_effect = NodePath("../rifle/Character/Skeleton3D/BoneAttachment3D/MuzzleEffect") [editable path="rifle"]