feat: implemented turrets

This commit is contained in:
Sara 2024-05-21 15:03:36 +02:00
parent f34a0eaf95
commit 2fd2c80aa2
5 changed files with 294 additions and 0 deletions

42
src/beam.cpp Normal file
View file

@ -0,0 +1,42 @@
#include "beam.hpp"
#include "godot_cpp/variant/utility_functions.hpp"
#include "utils/godot_macros.h"
#include <godot_cpp/classes/time.hpp>
namespace godot {
void Beam::_bind_methods() {
#define CLASSNAME Beam
}
void Beam::_enter_tree() { GDGAMEONLY();
this->mesh_instance = this->get_node<MeshInstance3D>("MeshInstance3D");
if(this->mesh_instance)
this->mesh = this->mesh_instance->get_mesh();
this->collision_shape = this->get_node<CollisionShape3D>("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;
}
}

30
src/beam.hpp Normal file
View file

@ -0,0 +1,30 @@
#ifndef BEAM_HPP
#define BEAM_HPP
#include <godot_cpp/classes/area3d.hpp>
#include <godot_cpp/classes/capsule_mesh.hpp>
#include <godot_cpp/classes/capsule_shape3d.hpp>
#include <godot_cpp/classes/collision_shape3d.hpp>
#include <godot_cpp/classes/mesh.hpp>
#include <godot_cpp/classes/mesh_instance3d.hpp>
#include <godot_cpp/classes/shape3d.hpp>
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<CapsuleMesh> mesh{};
CollisionShape3D *collision_shape{nullptr};
Ref<CapsuleShape3D> shape{};
float end_time{0.f};
};
}
#endif // !BEAM_HPP

View file

@ -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<MenuUI>();
ClassDB::register_class<EndScreen>();
ClassDB::register_class<Turret>();
ClassDB::register_class<Beam>();
}
extern "C"

156
src/turret.cpp Normal file
View file

@ -0,0 +1,156 @@
#include "turret.hpp"
#include "beam.hpp"
#include "utils/godot_macros.h"
#include <godot_cpp/classes/global_constants.hpp>
#include <godot_cpp/classes/physics_direct_space_state3d.hpp>
#include <godot_cpp/classes/physics_ray_query_parameters3d.hpp>
#include <godot_cpp/classes/time.hpp>
#include <godot_cpp/classes/world3d.hpp>
#include <godot_cpp/variant/utility_functions.hpp>
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<Area3D>("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<Node3D>("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<PhysicsRayQueryParameters3D> 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<Beam>(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<PackedScene> scene) {
this->beam_scene = scene;
}
Ref<PackedScene> Turret::get_beam_scene() const {
return this->beam_scene;
}
}

64
src/turret.hpp Normal file
View file

@ -0,0 +1,64 @@
#ifndef TURRET_HPP
#define TURRET_HPP
#include <godot_cpp/classes/area3d.hpp>
#include <godot_cpp/classes/packed_scene.hpp>
#include <godot_cpp/classes/static_body3d.hpp>
#include <godot_cpp/templates/hash_set.hpp>
#include <godot_cpp/templates/vector.hpp>
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<PackedScene> scene);
Ref<PackedScene> 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<StringName> attack_classes{};
HashSet<Node3D*> awareness{};
Ref<PackedScene> beam_scene{};
float fire_time{2.0f};
float charge_time{0.3f};
float lock_time{2.0f};
};
}
#endif // !TURRET_HPP