ESSEntityWorldSpawner3DArea initial implementation.

This commit is contained in:
Relintai 2025-04-14 16:08:44 +02:00
parent 12eade8158
commit 2a249c8f25
3 changed files with 382 additions and 97 deletions

View File

@ -65,10 +65,12 @@ WorldSpawner3DSpatialGizmoPlugin::WorldSpawner3DSpatialGizmoPlugin() {
const Color gizmo_outline_color = EDITOR_DEF("editors/ess/gizmo_colors/world_spawner_outline", Color(0.5, 0.5, 1));
const Color gizmo_text_color = EDITOR_DEF("editors/ess/gizmo_colors/world_spawner_text", Color(0.5, 0.5, 1));
const Color gizmo_area_outline_color = EDITOR_DEF("editors/ess/gizmo_colors/world_spawner_area_outline", Color(0.7, 0.3, 0.3));
const Color gizmo_area_spawn_pos_color = EDITOR_DEF("editors/ess/gizmo_colors/world_spawner_area_spawn_position", Color(0.9, 0.5, 0.3));
create_material("outline_material", gizmo_outline_color);
create_material("text_material", gizmo_text_color);
create_material("area_outline_material", gizmo_area_outline_color);
create_material("area_spawn_pos_material", gizmo_area_spawn_pos_color);
create_handle_material("handles");
}
@ -193,6 +195,28 @@ void WorldSpawner3DSpatialGizmoPlugin::redraw(EditorSpatialGizmo *p_gizmo) {
p_gizmo->add_lines(lines, area_outline_material);
p_gizmo->add_collision_segments(lines);
p_gizmo->add_handles(handles, handles_material);
// Spawn positions
Vector<Vector2> spawn_positions = aws->get_spawn_positions();
if (spawn_positions.empty()) {
return;
}
lines.clear();
const Ref<Material> area_spawn_pos_material = get_material("area_spawn_pos_material");
for (int i = 0; i < spawn_positions.size(); ++i) {
Vector2 p = spawn_positions[i];
lines.push_back(Vector3(p.x, -extents.z, p.y));
lines.push_back(Vector3(p.x, extents.z, p.y));
}
p_gizmo->add_lines(lines, area_spawn_pos_material);
p_gizmo->add_collision_segments(lines);
}
String WorldSpawner3DSpatialGizmoPlugin::get_handle_name(const EditorSpatialGizmo *p_gizmo, int p_id, bool p_secondary) const {

View File

@ -48,27 +48,17 @@ Vector3 ESSEntityWorldSpawner3DArea::get_spawn_area_extents() const {
}
void ESSEntityWorldSpawner3DArea::set_spawn_area_extents(const Vector3 &p_extents) {
_spawn_area_extents = p_extents;
_spawn_slots_dirty = true;
}
String ESSEntityWorldSpawner3DArea::get_entity_name() const {
return _entity_name;
}
void ESSEntityWorldSpawner3DArea::set_entity_name(const String &p_name) {
_entity_name = p_name;
int ESSEntityWorldSpawner3DArea::get_spawn_slot_count() const {
return _spawn_slot_count;
}
void ESSEntityWorldSpawner3DArea::set_spawn_slot_count(const int p_spawn_slot_count) {
_spawn_slot_count = p_spawn_slot_count;
Ref<EntityData> ESSEntityWorldSpawner3DArea::get_entity_data() const {
return _entity_data;
}
void ESSEntityWorldSpawner3DArea::set_entity_data(const Ref<EntityData> &p_data) {
_entity_data = p_data;
}
int ESSEntityWorldSpawner3DArea::get_level() const {
return _level;
}
void ESSEntityWorldSpawner3DArea::set_level(const int p_level) {
_level = p_level;
_spawn_slots_dirty = true;
}
float ESSEntityWorldSpawner3DArea::get_respawn_time_min() const {
@ -85,12 +75,89 @@ void ESSEntityWorldSpawner3DArea::set_respawn_time_max(const float p_respawn_tim
_respawn_time_max = p_respawn_time;
}
void ESSEntityWorldSpawner3DArea::_spawn() {
if (Engine::get_singleton()->is_editor_hint()) {
return;
}
String ESSEntityWorldSpawner3DArea::get_entity_name(const int p_index) const {
ERR_FAIL_INDEX_V(p_index, _spawn_entries.size(), String());
if (!_entity_data.is_valid()) {
return _spawn_entries[p_index].entity_name;
}
void ESSEntityWorldSpawner3DArea::set_entity_name(const int p_index, const String &p_name) {
ERR_FAIL_INDEX(p_index, _spawn_entries.size());
_spawn_entries.write[p_index].entity_name = p_name;
_spawn_entries_dirty = true;
}
Ref<EntityData> ESSEntityWorldSpawner3DArea::get_entity_data(const int p_index) const {
ERR_FAIL_INDEX_V(p_index, _spawn_entries.size(), Ref<EntityData>());
return _spawn_entries[p_index].entity_data;
}
void ESSEntityWorldSpawner3DArea::set_entity_data(const int p_index, const Ref<EntityData> &p_data) {
ERR_FAIL_INDEX(p_index, _spawn_entries.size());
_spawn_entries.write[p_index].entity_data = p_data;
_spawn_entries_dirty = true;
}
Vector2i ESSEntityWorldSpawner3DArea::get_entity_level_range(const int p_index) const {
ERR_FAIL_INDEX_V(p_index, _spawn_entries.size(), Vector2i());
return _spawn_entries[p_index].level_range;
}
void ESSEntityWorldSpawner3DArea::set_entity_level_range(const int p_index, const Vector2i &p_level_range) {
ERR_FAIL_INDEX(p_index, _spawn_entries.size());
_spawn_entries.write[p_index].level_range = p_level_range;
_spawn_entries_dirty = true;
}
float ESSEntityWorldSpawner3DArea::get_entity_spawn_chance(const int p_index) const {
ERR_FAIL_INDEX_V(p_index, _spawn_entries.size(), 0);
return _spawn_entries[p_index].spawn_chance;
}
void ESSEntityWorldSpawner3DArea::set_entity_spawn_chance(const int p_index, const float p_spawn_chance) {
ERR_FAIL_INDEX(p_index, _spawn_entries.size());
_spawn_entries.write[p_index].spawn_chance = p_spawn_chance;
_spawn_entries_dirty = true;
}
int ESSEntityWorldSpawner3DArea::get_spawn_entry_count() const {
return _spawn_entries.size();
}
void ESSEntityWorldSpawner3DArea::clear_spawn_entries() {
_spawn_entries.clear();
_spawn_entries_dirty = true;
}
void ESSEntityWorldSpawner3DArea::add_spawn_entry(const String &p_name, const Ref<EntityData> &p_data, const Vector2i &p_level_range, const float p_spawn_chance) {
SpawnEntry e;
e.entity_name = p_name;
e.entity_data = p_data;
e.level_range = p_level_range;
e.spawn_chance = p_spawn_chance;
_spawn_entries.push_back(e);
_spawn_entries_dirty = true;
}
void ESSEntityWorldSpawner3DArea::remove_spawn_entry(const int p_index) {
ERR_FAIL_INDEX(p_index, _spawn_entries.size());
_spawn_entries.remove(p_index);
_spawn_entries_dirty = true;
}
void ESSEntityWorldSpawner3DArea::_spawn() {
// force spawns in all empty slots
if (Engine::get_singleton()->is_editor_hint()) {
return;
}
@ -98,83 +165,218 @@ void ESSEntityWorldSpawner3DArea::_spawn() {
return;
}
Ref<EntityCreateInfo> info;
info.instance();
if (!_entity_name.empty()) {
info->set_entity_name(_entity_name);
} else {
info->set_entity_name(get_name());
if (_spawn_entries_dirty) {
_recalculate_valid_spawn_entries();
}
info->set_entity_controller(EntityEnums::ENITIY_CONTROLLER_AI);
info->set_level(_level);
info->set_transform(get_global_transform());
info->set_entity_data(_entity_data);
info->set_species_instance(_entity_data->get_species_instance());
ESS::get_singleton()->request_entity_spawn(info);
Entity *created_entity = info->get_created_entity();
if (created_entity != NULL) {
created_entity->sets_spawner_object_id(get_instance_id());
_entity = created_entity->get_instance_id();
created_entity->connect("tree_exited", this, "_on_entity_tree_exited");
if (_spawn_slots_dirty) {
_recalculate_slots();
}
emit_on_entity_spawned(info);
for (int i = 0; i < _spawn_slots.size(); ++i) {
const SpawnSlot &e = _spawn_slots.write[i];
if (e.entity == ObjectID()) {
_spawn_slot(i);
}
}
}
ESSEntityWorldSpawner3DArea::ESSEntityWorldSpawner3DArea() {
_level = 1;
_entity = ObjectID();
_spawn_slot_count = 0;
_respawn_time_min = 0;
_respawn_time_max = 0;
_respawn_timer = 0;
_spawn_area_extents = Vector3(2, 2, 2);
_spawn_slots_dirty = true;
_spawn_entries_dirty = true;
_spawn_entries_max_chance = 0;
}
ESSEntityWorldSpawner3DArea::~ESSEntityWorldSpawner3DArea() {
}
void ESSEntityWorldSpawner3DArea::_on_entity_tree_exited() {
_entity = ObjectID();
Vector<Vector2> ESSEntityWorldSpawner3DArea::get_spawn_positions() const {
Vector<Vector2> pos;
if (_respawn_time_min > CMP_EPSILON) {
if (_respawn_time_max > CMP_EPSILON) {
_respawn_timer = Math::random(_respawn_time_min, _respawn_time_max);
} else {
_respawn_timer = _respawn_time_min;
}
for (int i = 0; i < _spawn_slots.size(); ++i) {
const SpawnSlot &e = _spawn_slots[i];
set_process_internal(true);
pos.push_back(e.position);
}
return pos;
}
void ESSEntityWorldSpawner3DArea::_on_entity_tree_exited(const ObjectID p_entity_object_id) {
for (int i = 0; i < _spawn_slots.size(); ++i) {
SpawnSlot &e = _spawn_slots.write[i];
if (e.entity == p_entity_object_id) {
e.entity = ObjectID();
if (_respawn_time_min > CMP_EPSILON) {
if (_respawn_time_max > CMP_EPSILON) {
e.respawn_timer = Math::random(_respawn_time_min, _respawn_time_max);
} else {
e.respawn_timer = _respawn_time_min;
}
set_process_internal(true);
}
return;
}
}
}
void ESSEntityWorldSpawner3DArea::_recalculate_slots() {
_spawn_slots.resize(_spawn_slot_count);
for (int i = 0; i < _spawn_slots.size(); ++i) {
SpawnSlot &e = _spawn_slots.write[i];
e.position = Vector2(Math::random(-_spawn_area_extents.x, _spawn_area_extents.x), Math::random(-_spawn_area_extents.z, _spawn_area_extents.z));
}
_spawn_slots_dirty = false;
}
void ESSEntityWorldSpawner3DArea::_recalculate_valid_spawn_entries() {
_spawn_entries_max_chance = 0;
_valid_spawn_entries.clear();
for (int i = 0; i < _spawn_entries.size(); ++i) {
const SpawnEntry &e = _spawn_entries[i];
if (e.entity_data.is_valid() && e.spawn_chance > CMP_EPSILON) {
_valid_spawn_entries.push_back(e);
_spawn_entries_max_chance += e.spawn_chance;
}
}
_spawn_entries_dirty = false;
}
void ESSEntityWorldSpawner3DArea::_spawn_slot(const int p_index) {
int spawn_index = generate_spawn_index();
if (spawn_index == -1) {
return;
}
const SpawnEntry &e = _spawn_entries[spawn_index];
SpawnSlot &s = _spawn_slots.write[p_index];
Ref<EntityCreateInfo> info;
info.instance();
if (!e.entity_name.empty()) {
info->set_entity_name(e.entity_name);
} else {
info->set_entity_name(get_name());
}
info->set_entity_controller(EntityEnums::ENITIY_CONTROLLER_AI);
int level;
if (e.level_range.y > 0) {
level = Math::randomi(e.level_range.x, e.level_range.y);
} else {
level = e.level_range.x;
}
info->set_level(level);
Transform offset;
offset.translate_local(Vector3(s.position.x, 0, s.position.y));
info->set_transform(get_global_transform() * offset);
info->set_entity_data(e.entity_data);
info->set_species_instance(e.entity_data->get_species_instance());
ESS::get_singleton()->request_entity_spawn(info);
s.respawn_timer = 0;
Entity *created_entity = info->get_created_entity();
if (created_entity != NULL) {
created_entity->sets_spawner_object_id(get_instance_id());
s.entity = created_entity->get_instance_id();
created_entity->connect("tree_exited", this, "_on_entity_tree_exited", varray(s.entity));
}
emit_on_entity_spawned(info);
}
int ESSEntityWorldSpawner3DArea::generate_spawn_index() {
if (_spawn_entries_dirty) {
_recalculate_valid_spawn_entries();
}
float rnd = Math::random(0.0f, _spawn_entries_max_chance);
float current_spawn_chance = 0;
for (int i = 0; i < _spawn_entries.size(); ++i) {
const SpawnEntry &e = _spawn_entries[i];
current_spawn_chance += e.spawn_chance;
if (rnd < current_spawn_chance) {
return i;
}
}
return _spawn_entries.size() - 1;
}
void ESSEntityWorldSpawner3DArea::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE:
case NOTIFICATION_ENTER_TREE: {
call_deferred("spawn");
break;
case NOTIFICATION_EXIT_TREE:
if (_entity != ObjectID()) {
Entity *ent = ObjectDB::get_instance<Entity>(_entity);
} break;
case NOTIFICATION_EXIT_TREE: {
for (int i = 0; i < _spawn_slots.size(); ++i) {
SpawnSlot &s = _spawn_slots.write[i];
if (ent) {
ent->sets_spawner_object_id(ObjectID());
if (s.entity != ObjectID()) {
Entity *ent = ObjectDB::get_instance<Entity>(s.entity);
if (ent) {
ent->sets_spawner_object_id(ObjectID());
}
s.entity = ObjectID();
}
}
break;
case NOTIFICATION_INTERNAL_PROCESS:
_respawn_timer -= get_tree()->get_idle_process_time();
} break;
case NOTIFICATION_INTERNAL_PROCESS: {
float delta = get_tree()->get_idle_process_time();
if (_respawn_timer <= 0) {
_respawn_timer = 0;
set_process_internal(false);
call_deferred("spawn");
bool has_active_timer = false;
for (int i = 0; i < _spawn_slots.size(); ++i) {
SpawnSlot &s = _spawn_slots.write[i];
if (s.respawn_timer > CMP_EPSILON2) {
s.respawn_timer -= delta;
if (s.respawn_timer <= 0) {
s.respawn_timer = 0;
call_deferred("_spawn_slot", i);
} else {
has_active_timer = true;
}
}
}
break;
if (!has_active_timer) {
set_process_internal(false);
}
} break;
default:
break;
}
@ -185,25 +387,41 @@ void ESSEntityWorldSpawner3DArea::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_spawn_area_extents", "value"), &ESSEntityWorldSpawner3DArea::set_spawn_area_extents);
ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "spawn_area_extents"), "set_spawn_area_extents", "get_spawn_area_extents");
ClassDB::bind_method(D_METHOD("get_entity_name"), &ESSEntityWorldSpawner3DArea::get_entity_name);
ClassDB::bind_method(D_METHOD("set_entity_name", "value"), &ESSEntityWorldSpawner3DArea::set_entity_name);
ADD_PROPERTY(PropertyInfo(Variant::STRING, "entity_name"), "set_entity_name", "get_entity_name");
ClassDB::bind_method(D_METHOD("get_entity_data"), &ESSEntityWorldSpawner3DArea::get_entity_data);
ClassDB::bind_method(D_METHOD("set_entity_data", "value"), &ESSEntityWorldSpawner3DArea::set_entity_data);
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "entity_data", PROPERTY_HINT_RESOURCE_TYPE, "EntityData"), "set_entity_data", "get_entity_data");
ClassDB::bind_method(D_METHOD("get_level"), &ESSEntityWorldSpawner3DArea::get_level);
ClassDB::bind_method(D_METHOD("set_level", "value"), &ESSEntityWorldSpawner3DArea::set_level);
ADD_PROPERTY(PropertyInfo(Variant::INT, "level"), "set_level", "get_level");
ClassDB::bind_method(D_METHOD("get_spawn_slot_count"), &ESSEntityWorldSpawner3DArea::get_spawn_slot_count);
ClassDB::bind_method(D_METHOD("set_spawn_slot_count", "respawn_time"), &ESSEntityWorldSpawner3DArea::set_spawn_slot_count);
ADD_PROPERTY(PropertyInfo(Variant::INT, "spawn_slot_count"), "set_spawn_slot_count", "get_spawn_slot_count");
ClassDB::bind_method(D_METHOD("get_respawn_time_min"), &ESSEntityWorldSpawner3DArea::get_respawn_time_min);
ClassDB::bind_method(D_METHOD("set_respawn_time_min", "respawn_time"), &ESSEntityWorldSpawner3DArea::set_respawn_time_min);
ADD_PROPERTY(PropertyInfo(Variant::INT, "respawn_time_min"), "set_respawn_time_min", "get_respawn_time_min");
ADD_PROPERTY(PropertyInfo(Variant::REAL, "respawn_time_min"), "set_respawn_time_min", "get_respawn_time_min");
ClassDB::bind_method(D_METHOD("get_respawn_time_max"), &ESSEntityWorldSpawner3DArea::get_respawn_time_max);
ClassDB::bind_method(D_METHOD("set_respawn_time_max", "respawn_time"), &ESSEntityWorldSpawner3DArea::set_respawn_time_max);
ADD_PROPERTY(PropertyInfo(Variant::INT, "respawn_time_max"), "set_respawn_time_max", "get_respawn_time_max");
ADD_PROPERTY(PropertyInfo(Variant::REAL, "respawn_time_max"), "set_respawn_time_max", "get_respawn_time_max");
ClassDB::bind_method(D_METHOD("get_entity_name", "index"), &ESSEntityWorldSpawner3DArea::get_entity_name);
ClassDB::bind_method(D_METHOD("set_entity_name", "index", "name"), &ESSEntityWorldSpawner3DArea::set_entity_name);
ClassDB::bind_method(D_METHOD("get_entity_data", "index"), &ESSEntityWorldSpawner3DArea::get_entity_data);
ClassDB::bind_method(D_METHOD("set_entity_data", "index", "data"), &ESSEntityWorldSpawner3DArea::set_entity_data);
ClassDB::bind_method(D_METHOD("get_entity_level_range", "index"), &ESSEntityWorldSpawner3DArea::get_entity_level_range);
ClassDB::bind_method(D_METHOD("set_entity_level_range", "index", "level_range"), &ESSEntityWorldSpawner3DArea::set_entity_level_range);
ClassDB::bind_method(D_METHOD("get_entity_spawn_chance", "index"), &ESSEntityWorldSpawner3DArea::get_entity_spawn_chance);
ClassDB::bind_method(D_METHOD("set_entity_spawn_chance", "index", "spawn_chance"), &ESSEntityWorldSpawner3DArea::set_entity_spawn_chance);
ClassDB::bind_method(D_METHOD("get_spawn_entry_count"), &ESSEntityWorldSpawner3DArea::get_spawn_entry_count);
ClassDB::bind_method(D_METHOD("clear_spawn_entries"), &ESSEntityWorldSpawner3DArea::clear_spawn_entries);
ClassDB::bind_method(D_METHOD("add_spawn_entry", "name", "data", "level_range", "spawn_chance"), &ESSEntityWorldSpawner3DArea::add_spawn_entry);
ClassDB::bind_method(D_METHOD("remove_spawn_entry", "index"), &ESSEntityWorldSpawner3DArea::remove_spawn_entry);
ClassDB::bind_method(D_METHOD("_on_entity_tree_exited"), &ESSEntityWorldSpawner3DArea::_on_entity_tree_exited);
ClassDB::bind_method(D_METHOD("_spawn_slot"), &ESSEntityWorldSpawner3DArea::_spawn_slot);
}
ESSEntityWorldSpawner3DArea::SpawnEntry::SpawnEntry() {
spawn_chance = 1;
}
ESSEntityWorldSpawner3DArea::SpawnEntry::~SpawnEntry() {
}

View File

@ -43,14 +43,8 @@ public:
Vector3 get_spawn_area_extents() const;
void set_spawn_area_extents(const Vector3 &p_extents);
String get_entity_name() const;
void set_entity_name(const String &p_name);
Ref<EntityData> get_entity_data() const;
void set_entity_data(const Ref<EntityData> &p_data);
int get_level() const;
void set_level(const int p_level);
int get_spawn_slot_count() const;
void set_spawn_slot_count(const int p_spawn_slot_count);
float get_respawn_time_min() const;
void set_respawn_time_min(const float p_respawn_time);
@ -58,27 +52,76 @@ public:
float get_respawn_time_max() const;
void set_respawn_time_max(const float p_respawn_time);
String get_entity_name(const int p_index) const;
void set_entity_name(const int p_index, const String &p_name);
Ref<EntityData> get_entity_data(const int p_index) const;
void set_entity_data(const int p_index, const Ref<EntityData> &p_data);
Vector2i get_entity_level_range(const int p_index) const;
void set_entity_level_range(const int p_index, const Vector2i &p_level_range);
float get_entity_spawn_chance(const int p_index) const;
void set_entity_spawn_chance(const int p_index, const float p_spawn_chance);
int get_spawn_entry_count() const;
void clear_spawn_entries();
void add_spawn_entry(const String &p_name, const Ref<EntityData> &p_data, const Vector2i &p_level_range, const float p_spawn_chance);
void remove_spawn_entry(const int p_index);
virtual void _spawn();
ESSEntityWorldSpawner3DArea();
~ESSEntityWorldSpawner3DArea();
// For the gizmo
Vector<Vector2> get_spawn_positions() const;
protected:
void _notification(int p_what);
static void _bind_methods();
void _on_entity_tree_exited();
void _on_entity_tree_exited(const ObjectID p_entity_object_id);
void _recalculate_slots();
void _recalculate_valid_spawn_entries();
void _spawn_slot(const int p_index);
int generate_spawn_index();
Vector3 _spawn_area_extents;
int _spawn_slot_count;
bool _spawn_slots_dirty;
String _entity_name;
Ref<EntityData> _entity_data;
int _level;
float _respawn_time_min;
float _respawn_time_max;
real_t _respawn_timer;
ObjectID _entity;
struct SpawnEntry {
String entity_name;
Ref<EntityData> entity_data;
Vector2i level_range;
float spawn_chance;
SpawnEntry();
~SpawnEntry();
};
Vector<SpawnEntry> _spawn_entries;
Vector<SpawnEntry> _valid_spawn_entries;
float _spawn_entries_max_chance;
bool _spawn_entries_dirty;
struct SpawnSlot {
Vector2 position;
float respawn_timer;
ObjectID entity;
SpawnSlot() {
respawn_timer = 0;
entity = ObjectID();
}
};
Vector<SpawnSlot> _spawn_slots;
};
#endif