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) {
 | 
					void EnemyBody::physics_process(double delta) {
 | 
				
			||||||
	GETSET(velocity, {
 | 
						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()) {
 | 
						if (!this->movement_direction.is_zero_approx()) {
 | 
				
			||||||
		look_at(get_global_position() + Vector3{ this->movement_direction.x, 0.f, this->movement_direction.y });
 | 
							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 "map_region.h"
 | 
				
			||||||
#include "enemy_body.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() {
 | 
					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) {
 | 
					void MapRegion::on_node_entered(Node *node) {
 | 
				
			||||||
| 
						 | 
					@ -43,3 +45,22 @@ void MapRegion::remove_unit(NpcUnit *unit) {
 | 
				
			||||||
		this->units.erase(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:
 | 
					public:
 | 
				
			||||||
	void register_unit(NpcUnit *unit);
 | 
						void register_unit(NpcUnit *unit);
 | 
				
			||||||
	void remove_unit(NpcUnit *unit);
 | 
						void remove_unit(NpcUnit *unit);
 | 
				
			||||||
 | 
						void raise_difficulty(double amount);
 | 
				
			||||||
 | 
						int get_current_difficulty() const;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
private:
 | 
					private:
 | 
				
			||||||
	float awareness{ 0.f };
 | 
						double difficulty{ 0.f };
 | 
				
			||||||
	bool hunt_phase{ false };
 | 
						bool hunt_phase{ false };
 | 
				
			||||||
	HashSet<NpcUnit *> units{ nullptr };
 | 
						HashSet<NpcUnit *> units{ nullptr };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
public:
 | 
					public:
 | 
				
			||||||
 | 
						static String const sig_difficulty_increased;
 | 
				
			||||||
	static String const sig_phase_changed;
 | 
						static String const sig_phase_changed;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,6 +4,7 @@
 | 
				
			||||||
#include "wave_survival/damage_box.h"
 | 
					#include "wave_survival/damage_box.h"
 | 
				
			||||||
#include "wave_survival/enemies/enemy_wretched.h"
 | 
					#include "wave_survival/enemies/enemy_wretched.h"
 | 
				
			||||||
#include "wave_survival/enemy_body.h"
 | 
					#include "wave_survival/enemy_body.h"
 | 
				
			||||||
 | 
					#include "wave_survival/enemy_spawner.h"
 | 
				
			||||||
#include "wave_survival/heads_up_display.h"
 | 
					#include "wave_survival/heads_up_display.h"
 | 
				
			||||||
#include "wave_survival/health_status.h"
 | 
					#include "wave_survival/health_status.h"
 | 
				
			||||||
#include "wave_survival/hitbox.h"
 | 
					#include "wave_survival/hitbox.h"
 | 
				
			||||||
| 
						 | 
					@ -59,6 +60,8 @@ void initialize_wave_survival_module(ModuleInitializationLevel p_level) {
 | 
				
			||||||
	GDREGISTER_CLASS(MuzzleEffect);
 | 
						GDREGISTER_CLASS(MuzzleEffect);
 | 
				
			||||||
	GDREGISTER_RUNTIME_CLASS(HeadsUpDisplay);
 | 
						GDREGISTER_RUNTIME_CLASS(HeadsUpDisplay);
 | 
				
			||||||
	GDREGISTER_RUNTIME_CLASS(MapRegion);
 | 
						GDREGISTER_RUNTIME_CLASS(MapRegion);
 | 
				
			||||||
 | 
						GDREGISTER_RUNTIME_CLASS(SpawnData);
 | 
				
			||||||
 | 
						GDREGISTER_RUNTIME_CLASS(EnemySpawner);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	memnew(SoundEventPatchboard);
 | 
						memnew(SoundEventPatchboard);
 | 
				
			||||||
	Engine::get_singleton()->add_singleton(Engine::Singleton(SoundEventPatchboard::get_class_static(), SoundEventPatchboard::get_singleton()));
 | 
						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