feat: implemented enemies spawning based on current region difficulty
This commit is contained in:
parent
96b5be405c
commit
9b07c70b11
|
@ -22,7 +22,7 @@ void EnemyBody::ready() {
|
|||
|
||||
void EnemyBody::physics_process(double delta) {
|
||||
GETSET(velocity, {
|
||||
velocity = Vector3{ this->movement_direction.x * this->movement_speed, velocity.y, this->movement_direction.y * this->movement_speed };
|
||||
velocity = Vector3{ this->movement_direction.x * this->movement_speed, velocity.y, this->movement_direction.y * this->movement_speed } + (velocity * delta);
|
||||
});
|
||||
if (!this->movement_direction.is_zero_approx()) {
|
||||
look_at(get_global_position() + Vector3{ this->movement_direction.x, 0.f, this->movement_direction.y });
|
||||
|
|
111
modules/wave_survival/enemy_spawner.cpp
Normal file
111
modules/wave_survival/enemy_spawner.cpp
Normal file
|
@ -0,0 +1,111 @@
|
|||
#include "enemy_spawner.h"
|
||||
#include "core/io/resource.h"
|
||||
#include "core/object/object.h"
|
||||
#include "macros.h"
|
||||
#include "map_region.h"
|
||||
#include "npc_unit.h"
|
||||
#include "patrol_path.h"
|
||||
#include "scene/resources/packed_scene.h"
|
||||
|
||||
void SpawnData::_bind_methods() {
|
||||
BIND_HPROPERTY(Variant::DICTIONARY, difficulty_spawns, PROPERTY_HINT_DICTIONARY_TYPE, vformat("int;%s/%s:PackedScene", Variant::OBJECT, PROPERTY_HINT_RESOURCE_TYPE));
|
||||
}
|
||||
|
||||
void SpawnData::set_difficulty_spawns(Dictionary dict) {
|
||||
this->difficulty_spawns.clear();
|
||||
for (KeyValue<Variant, Variant> const &kvp : dict) {
|
||||
Ref<PackedScene> scene{ kvp.value };
|
||||
if (scene.is_null() || !scene.is_valid()) {
|
||||
continue;
|
||||
} else if (!kvp.key.is_num()) {
|
||||
continue;
|
||||
} else {
|
||||
this->difficulty_spawns.insert(kvp.key, scene);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Dictionary SpawnData::get_difficulty_spawns() const {
|
||||
Dictionary r{};
|
||||
for (KeyValue<int, Ref<PackedScene>> const &kvp : this->difficulty_spawns) {
|
||||
r.set(kvp.key, kvp.value);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
void EnemySpawner::_bind_methods() {
|
||||
BIND_HPROPERTY(Variant::OBJECT, spawn_data, PROPERTY_HINT_RESOURCE_TYPE, "SpawnData");
|
||||
BIND_HPROPERTY(Variant::OBJECT, patrol_path, PROPERTY_HINT_NODE_TYPE, "PatrolPath");
|
||||
}
|
||||
|
||||
void EnemySpawner::on_phase_change(bool hunt) {
|
||||
Ref<PackedScene> scene{ Ref<PackedScene>() };
|
||||
int const idx{ this->region->get_current_difficulty() };
|
||||
if (this->spawn_data->difficulty_spawns.has(idx)) {
|
||||
scene = this->spawn_data->difficulty_spawns[idx];
|
||||
}
|
||||
if (!scene.is_valid()) {
|
||||
print_error("EnemySpawner::on_phase_change: Spawn data scene is invalid");
|
||||
return;
|
||||
}
|
||||
Node *instantiated{ scene->instantiate() };
|
||||
if (instantiated == nullptr) {
|
||||
print_error("EnemySpawner::on_phase_change: Spawn data instantiated a nullptr node");
|
||||
return;
|
||||
}
|
||||
NpcUnit *unit_3d{ cast_to<NpcUnit>(instantiated) };
|
||||
if (unit_3d == nullptr) {
|
||||
instantiated->queue_free();
|
||||
print_error("EnemySpawner::on_phase_change: Spawn data instantiated an invalid unit that cannot be cast to NpcUnit");
|
||||
return;
|
||||
}
|
||||
unit_3d->set_patrol_path(this->patrol_path);
|
||||
add_child(unit_3d);
|
||||
unit_3d->set_global_transform(this->get_global_transform());
|
||||
}
|
||||
|
||||
void EnemySpawner::ready() {
|
||||
if (MapRegion * region{ cast_to<MapRegion>(this->get_parent()) }) {
|
||||
this->region = region;
|
||||
region->connect(MapRegion::sig_phase_changed, callable_mp(this, &self_type::on_phase_change));
|
||||
} else {
|
||||
print_error("EnemySpawner::ready: Failed to find region in parent");
|
||||
}
|
||||
if (!this->spawn_data.is_valid()) {
|
||||
this->spawn_data = ResourceLoader::load("res://data/spawn_data/default.tres");
|
||||
}
|
||||
if (this->spawn_data.is_valid()) {
|
||||
on_phase_change(false);
|
||||
} else {
|
||||
print_error("EnemySpawner::ready: No spawn data found, default spawn data is invalid, consider setting up a default spawn data");
|
||||
}
|
||||
}
|
||||
|
||||
void EnemySpawner::_notification(int what) {
|
||||
if (Engine::get_singleton()->is_editor_hint()) {
|
||||
return;
|
||||
}
|
||||
switch (what) {
|
||||
default:
|
||||
return;
|
||||
case NOTIFICATION_READY:
|
||||
ready();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void EnemySpawner::set_spawn_data(Ref<SpawnData> data) {
|
||||
this->spawn_data = data;
|
||||
}
|
||||
|
||||
Ref<SpawnData> EnemySpawner::get_spawn_data() const {
|
||||
return this->spawn_data;
|
||||
}
|
||||
|
||||
void EnemySpawner::set_patrol_path(PatrolPath *path) {
|
||||
this->patrol_path = path;
|
||||
}
|
||||
|
||||
PatrolPath *EnemySpawner::get_patrol_path() const {
|
||||
return this->patrol_path;
|
||||
}
|
43
modules/wave_survival/enemy_spawner.h
Normal file
43
modules/wave_survival/enemy_spawner.h
Normal file
|
@ -0,0 +1,43 @@
|
|||
#ifndef ENEMY_SPAWNER_H
|
||||
#define ENEMY_SPAWNER_H
|
||||
|
||||
#include "core/io/resource.h"
|
||||
#include "core/templates/hash_map.h"
|
||||
#include "scene/3d/node_3d.h"
|
||||
class MapRegion;
|
||||
class PatrolPath;
|
||||
|
||||
class SpawnData : public Resource {
|
||||
GDCLASS(SpawnData, Resource);
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
void set_difficulty_spawns(Dictionary dict);
|
||||
Dictionary get_difficulty_spawns() const;
|
||||
|
||||
public:
|
||||
HashMap<int, Ref<PackedScene>> difficulty_spawns{};
|
||||
};
|
||||
|
||||
class EnemySpawner : public Node3D {
|
||||
GDCLASS(EnemySpawner, Node3D);
|
||||
static void _bind_methods();
|
||||
void on_phase_change(bool hunt);
|
||||
void ready();
|
||||
|
||||
protected:
|
||||
void _notification(int what);
|
||||
|
||||
public:
|
||||
void set_spawn_data(Ref<SpawnData> data);
|
||||
Ref<SpawnData> get_spawn_data() const;
|
||||
void set_patrol_path(PatrolPath *path);
|
||||
PatrolPath *get_patrol_path() const;
|
||||
|
||||
private:
|
||||
Ref<SpawnData> spawn_data{};
|
||||
PatrolPath *patrol_path{ nullptr };
|
||||
MapRegion *region{ nullptr };
|
||||
};
|
||||
|
||||
#endif // !ENEMY_SPAWNER_H
|
|
@ -1,10 +1,12 @@
|
|||
#include "map_region.h"
|
||||
#include "enemy_body.h"
|
||||
|
||||
String const MapRegion::sig_phase_changed{ "phase_changed" };
|
||||
String const MapRegion::sig_difficulty_increased{ "difficulty_increased" };
|
||||
String const MapRegion::sig_phase_changed{ "hunt_phase" };
|
||||
|
||||
void MapRegion::_bind_methods() {
|
||||
ADD_SIGNAL(MethodInfo(sig_phase_changed, PropertyInfo(Variant::BOOL, "hunt_phase")));
|
||||
ADD_SIGNAL(MethodInfo(sig_difficulty_increased));
|
||||
ADD_SIGNAL(MethodInfo(sig_phase_changed, PropertyInfo(Variant::BOOL, "hunt")));
|
||||
}
|
||||
|
||||
void MapRegion::on_node_entered(Node *node) {
|
||||
|
@ -43,3 +45,22 @@ void MapRegion::remove_unit(NpcUnit *unit) {
|
|||
this->units.erase(unit);
|
||||
}
|
||||
}
|
||||
|
||||
void MapRegion::raise_difficulty(double amount) {
|
||||
if (this->hunt_phase) {
|
||||
return;
|
||||
}
|
||||
double const new_difficulty{ this->difficulty + amount };
|
||||
int const new_trunc{ (int)Math::floor(new_difficulty) };
|
||||
int const old_trunc{ (int)Math::floor(this->difficulty) };
|
||||
if (new_trunc != old_trunc) {
|
||||
emit_signal(sig_difficulty_increased);
|
||||
emit_signal(sig_phase_changed, true);
|
||||
this->hunt_phase = true;
|
||||
}
|
||||
}
|
||||
|
||||
int MapRegion::get_current_difficulty() const {
|
||||
int difficulty{ (int)Math::floor(this->difficulty) };
|
||||
return difficulty;
|
||||
}
|
||||
|
|
|
@ -17,13 +17,16 @@ protected:
|
|||
public:
|
||||
void register_unit(NpcUnit *unit);
|
||||
void remove_unit(NpcUnit *unit);
|
||||
void raise_difficulty(double amount);
|
||||
int get_current_difficulty() const;
|
||||
|
||||
private:
|
||||
float awareness{ 0.f };
|
||||
double difficulty{ 0.f };
|
||||
bool hunt_phase{ false };
|
||||
HashSet<NpcUnit *> units{ nullptr };
|
||||
|
||||
public:
|
||||
static String const sig_difficulty_increased;
|
||||
static String const sig_phase_changed;
|
||||
};
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#include "wave_survival/damage_box.h"
|
||||
#include "wave_survival/enemies/enemy_wretched.h"
|
||||
#include "wave_survival/enemy_body.h"
|
||||
#include "wave_survival/enemy_spawner.h"
|
||||
#include "wave_survival/heads_up_display.h"
|
||||
#include "wave_survival/health_status.h"
|
||||
#include "wave_survival/hitbox.h"
|
||||
|
@ -59,6 +60,8 @@ void initialize_wave_survival_module(ModuleInitializationLevel p_level) {
|
|||
GDREGISTER_CLASS(MuzzleEffect);
|
||||
GDREGISTER_RUNTIME_CLASS(HeadsUpDisplay);
|
||||
GDREGISTER_RUNTIME_CLASS(MapRegion);
|
||||
GDREGISTER_RUNTIME_CLASS(SpawnData);
|
||||
GDREGISTER_RUNTIME_CLASS(EnemySpawner);
|
||||
|
||||
memnew(SoundEventPatchboard);
|
||||
Engine::get_singleton()->add_singleton(Engine::Singleton(SoundEventPatchboard::get_class_static(), SoundEventPatchboard::get_singleton()));
|
||||
|
|
8
project/data/spawn_data/default.tres
Normal file
8
project/data/spawn_data/default.tres
Normal file
|
@ -0,0 +1,8 @@
|
|||
[gd_resource type="SpawnData" load_steps=2 format=3 uid="uid://jya2iftfk0f6"]
|
||||
|
||||
[ext_resource type="PackedScene" uid="uid://5hg5eirw7v8n" path="res://objects/units/unit_4_wretched.tscn" id="1_0y836"]
|
||||
|
||||
[resource]
|
||||
difficulty_spawns = {
|
||||
0: ExtResource("1_0y836")
|
||||
}
|
File diff suppressed because one or more lines are too long
Loading…
Reference in a new issue