feat: implemented turrets
This commit is contained in:
parent
f34a0eaf95
commit
2fd2c80aa2
42
src/beam.cpp
Normal file
42
src/beam.cpp
Normal 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
30
src/beam.hpp
Normal 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
|
|
@ -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
156
src/turret.cpp
Normal 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
64
src/turret.hpp
Normal 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
|
Loading…
Reference in a new issue