From 2fd2c80aa285be5ca3b147b6cb1df161d1a93e7b Mon Sep 17 00:00:00 2001 From: Sara Date: Tue, 21 May 2024 15:03:36 +0200 Subject: [PATCH] feat: implemented turrets --- src/beam.cpp | 42 +++++++++++ src/beam.hpp | 30 ++++++++ src/register_types.cpp | 2 + src/turret.cpp | 156 +++++++++++++++++++++++++++++++++++++++++ src/turret.hpp | 64 +++++++++++++++++ 5 files changed, 294 insertions(+) create mode 100644 src/beam.cpp create mode 100644 src/beam.hpp create mode 100644 src/turret.cpp create mode 100644 src/turret.hpp diff --git a/src/beam.cpp b/src/beam.cpp new file mode 100644 index 0000000..200050b --- /dev/null +++ b/src/beam.cpp @@ -0,0 +1,42 @@ +#include "beam.hpp" +#include "godot_cpp/variant/utility_functions.hpp" +#include "utils/godot_macros.h" +#include + +namespace godot { +void Beam::_bind_methods() { +#define CLASSNAME Beam +} + +void Beam::_enter_tree() { GDGAMEONLY(); + this->mesh_instance = this->get_node("MeshInstance3D"); + if(this->mesh_instance) + this->mesh = this->mesh_instance->get_mesh(); + this->collision_shape = this->get_node("CollisionShape3D"); + if(this->collision_shape) + this->shape = this->collision_shape->get_shape(); +} + +void Beam::_process(double delta_time) { GDGAMEONLY(); + if(this->end_time != 0.f && Time::get_singleton()->get_ticks_msec() / 1000.f > this->end_time) { + this->queue_free(); + } +} + +void Beam::set_from_to(Vector3 from, Vector3 to) { + Vector3 const z = (to - from).normalized(); + Vector3 const x = z.cross({0.f, 1.f, 0.f}).normalized(); + double const length = (from - to).length(); + + this->set_global_position(from + z * (length / 2.f + 3.f)); + this->set_global_basis({x, z.cross(x).normalized(), z}); + if(this->mesh.is_valid()) + this->mesh->set_height(length); + if(this->shape.is_valid()) + this->shape->set_height(length); +} + +void Beam::set_end_time(float time_from_now) { + this->end_time = Time::get_singleton()->get_ticks_msec() / 1000.f + time_from_now; +} +} diff --git a/src/beam.hpp b/src/beam.hpp new file mode 100644 index 0000000..c6c71fc --- /dev/null +++ b/src/beam.hpp @@ -0,0 +1,30 @@ +#ifndef BEAM_HPP +#define BEAM_HPP + +#include +#include +#include +#include +#include +#include +#include + +namespace godot { +class Beam : public Area3D { + GDCLASS(Beam, Area3D); + static void _bind_methods(); +public: + virtual void _enter_tree() override; + virtual void _process(double delta_time) override; + void set_from_to(Vector3 from, Vector3 to); + void set_end_time(float time_from_now); +private: + MeshInstance3D *mesh_instance{nullptr}; + Ref mesh{}; + CollisionShape3D *collision_shape{nullptr}; + Ref shape{}; + float end_time{0.f}; +}; +} + +#endif // !BEAM_HPP diff --git a/src/register_types.cpp b/src/register_types.cpp index 6850255..48139ff 100644 --- a/src/register_types.cpp +++ b/src/register_types.cpp @@ -1,4 +1,5 @@ #include "register_types.h" +#include "beam.hpp" #include "car_physics.hpp" #include "car_player.hpp" #include "end_screen.hpp" @@ -40,6 +41,7 @@ void initialize_gdextension_types(ModuleInitializationLevel p_level) ClassDB::register_class(); ClassDB::register_class(); ClassDB::register_class(); + ClassDB::register_class(); } extern "C" diff --git a/src/turret.cpp b/src/turret.cpp new file mode 100644 index 0000000..d3abe14 --- /dev/null +++ b/src/turret.cpp @@ -0,0 +1,156 @@ +#include "turret.hpp" +#include "beam.hpp" +#include "utils/godot_macros.h" +#include +#include +#include +#include +#include +#include + +namespace godot { +void Turret::_bind_methods() { +#define CLASSNAME Turret + GDPROPERTY_HINTED(attack_classes, Variant::ARRAY, PROPERTY_HINT_ARRAY_TYPE, "StringName"); + GDPROPERTY_HINTED(beam_scene, Variant::OBJECT, PROPERTY_HINT_RESOURCE_TYPE, "PackedScene"); +} + +void Turret::_enter_tree() { GDGAMEONLY(); + Area3D *awareness_area = this->get_node("AwarenessArea"); + awareness_area->connect("body_entered", callable_mp(this, &Turret::detect_node)); + awareness_area->connect("body_exited", callable_mp(this, &Turret::lose_node)); + this->gun_node = this->get_node("Gun"); +} + +void Turret::_process(double delta_time) { GDGAMEONLY(); + switch(state) { + default: + break; + case TurretState::LOCKING: + this->lead_target(delta_time); + break; + } + this->try_next_state(); +} + +void Turret::awareness_changed() { + if(this->current_target != nullptr) + return; + + this->current_target = this->select_target(); + if(this->current_target == nullptr) + this->state = TurretState::WAITING; + else + this->state = TurretState::LOCKING; + this->last_state_switch = Time::get_singleton()->get_ticks_msec() * 0.001f; +} + +Node3D *Turret::select_target() { + for(Node3D *node : this->awareness) + if(this->attack_classes.has(node->get_class()) ^ this->invert_attack_classes) + return node; + return nullptr; +} + +void Turret::lead_target(double delta_time) { + if(!this->current_target) + return; + Vector3 const target_position = this->current_target->get_global_position(); + Vector3 const target_heading = (target_position - this->last_target_position) / delta_time; + this->aim_position = target_position + (target_heading * this->lead_distance); + + Vector3 const z = (this->aim_position - this->gun_node->get_global_position()).normalized(); + Vector3 const x = z.cross({0.f, 1.f, 0.f}); + Vector3 const y = z.cross(x); + + this->gun_node->set_global_basis({x,y,z}); + this->last_target_position = target_position; +} + +void Turret::detect_node(Node3D *node) { + if(!this->awareness.has(node)) { + this->awareness.insert(node); + } + this->awareness_changed(); + UtilityFunctions::print(this->get_path(), " detected node ", node->get_path()); +} + +void Turret::lose_node(Node3D *node) { + if(this->awareness.has(node)) { + this->awareness.erase(node); + } + if(node == this->current_target) { + this->current_target = nullptr; + } + this->awareness_changed(); + UtilityFunctions::print(this->get_path(), " lost track of node ", node->get_path()); +} + +void Turret::try_next_state() { + TurretState next_state = this->state; + float time = Time::get_singleton()->get_ticks_msec() * 0.001f; + switch(this->state) { + case TurretState::LOCKING: + if(time >= this->last_state_switch + this->lock_time) + next_state = TurretState::CHARGING; + break; + case TurretState::CHARGING: + if(time >= this->last_state_switch + this->charge_time) + next_state = TurretState::FIRING; + break; + case TurretState::WAITING: + case TurretState::FIRING: + return; + } + if(this->state == next_state) + return; + else this->state = next_state; + this->last_state_switch = time; + if(state == TurretState::FIRING) { + this->create_beam(); + } +} + +void Turret::create_beam() { + Vector3 const from = this->gun_node->get_global_position(); + Vector3 const to = from + this->gun_node->get_global_basis().get_column(2) * 1000.f; + Ref query{PhysicsRayQueryParameters3D::create(from, to)}; + Dictionary result = this->get_world_3d()->get_direct_space_state()->intersect_ray(query); + if(result.is_empty()) + return; + Beam *beam = Object::cast_to(this->beam_scene->instantiate()); + this->add_child(beam); + beam->set_from_to(from, result["position"]); + beam->set_end_time(this->fire_time); + beam->connect("tree_exited", callable_mp(this, &Turret::beam_ended)); +} + +void Turret::beam_ended() { + this->state = TurretState::LOCKING; + this->last_state_switch = Time::get_singleton()->get_ticks_msec() * 0.001f; +} + +void Turret::set_attack_classes(Array array) { + this->attack_classes.clear(); + for(int i = 0; i < array.size(); ++i) + if(array[i].get_type() == Variant::STRING_NAME && !this->attack_classes.has(array[i])) + this->attack_classes.push_back(array[i]); + else + this->attack_classes.push_back(""); +} + +Array Turret::get_attack_classes() const { + Array array{}; + for(StringName const &str : this->attack_classes) + array.push_back(str); + return array; +} + +void Turret::set_beam_scene(Ref scene) { + this->beam_scene = scene; +} + +Ref Turret::get_beam_scene() const { + return this->beam_scene; +} +} diff --git a/src/turret.hpp b/src/turret.hpp new file mode 100644 index 0000000..d823be9 --- /dev/null +++ b/src/turret.hpp @@ -0,0 +1,64 @@ +#ifndef TURRET_HPP +#define TURRET_HPP + +#include +#include +#include +#include +#include + +namespace godot { +enum class TurretState { + WAITING = 0x0, + LOCKING = 0x1, + CHARGING, + FIRING +}; +class Turret : public StaticBody3D { + GDCLASS(Turret, StaticBody3D); + static void _bind_methods(); +public: + virtual void _enter_tree() override; + virtual void _process(double delta_time) override; + + void awareness_changed(); + Node3D *select_target(); + + void lead_target(double delta_time); + + void detect_node(Node3D *node); + void lose_node(Node3D *node); + void try_next_state(); + void create_beam(); + + void beam_ended(); + + void set_attack_classes(Array array); + Array get_attack_classes() const; + void set_beam_scene(Ref scene); + Ref get_beam_scene() const; +private: + Node3D *current_target{nullptr}; + bool invert_attack_classes{false}; + Vector3 aim_position{0.f, 0.f, 0.f}; + Vector3 last_target_position{0.f, 0.f, 0.f}; + float last_state_switch{0.f}; + float lead_distance{1.5f}; + + Node3D *gun_node{nullptr}; + + + TurretState state{TurretState::LOCKING}; + + Vector attack_classes{}; + HashSet awareness{}; + + Ref beam_scene{}; + + float fire_time{2.0f}; + float charge_time{0.3f}; + float lock_time{2.0f}; +}; +} + +#endif // !TURRET_HPP