feat(gameplay): spawn creatures during chunk generation

Add creature spawning to chunk generation with a configurable
SpawnConfig, including a default pig spawn. Spawning occurs in
the final generation phase and registers entities through the
server world's entity manager.

Also destroy entities when their chunk is unloaded to prevent
orphaned entities and expose the entity manager from ServerWorld.
This commit is contained in:
2026-08-05 16:56:02 +08:00
parent 322fb2fea0
commit 8ff789c576
8 changed files with 62 additions and 4 deletions

View File

@@ -50,6 +50,8 @@ public:
void generate_cave(); void generate_cave();
void generate_river(); void generate_river();
void spawn_creature();
private: private:
static inline std::atomic<bool> is_init{false}; static inline std::atomic<bool> is_init{false};
static inline unsigned m_generator_seed{0}; static inline unsigned m_generator_seed{0};

View File

@@ -0,0 +1,20 @@
#pragma once
#include "Cubed/gameplay/biome.hpp"
#include <array>
#include <span>
#include <string_view>
namespace Cubed {
struct SpawnConfig {
std::string_view name; // factory key, e.g. "cubed:pig"
std::span<const BiomeType> biomes; // allowed biomes
float probability = 0.0f; // per chunk spawn probability
unsigned max_spawn_count = 0; // per chunk_max_spawn_sum
};
namespace SpawnDefaults {
constexpr std::array<BiomeType, 2> PIG_BIOMES{BiomeType::PLAIN,
BiomeType::FOREST};
constexpr SpawnConfig PIG{"cubed:pig", PIG_BIOMES, 0.02f, 3};
} // namespace SpawnDefaults
} // namespace Cubed

View File

@@ -15,7 +15,7 @@ public:
void init(); void init();
void update(); void update();
// not thread safe // not thread safe
void add_entity(std::string_view name, const glm::vec3& pos); void add_entity(std::string_view name, const glm::vec3& world_pos);
void destory(EntityID id); void destory(EntityID id);
void handle_player_login(std::shared_ptr<Session> session); void handle_player_login(std::shared_ptr<Session> session);

View File

@@ -95,6 +95,7 @@ public:
std::vector<std::shared_ptr<Session>> get_all_session() const; std::vector<std::shared_ptr<Session>> get_all_session() const;
uint32_t get_chunk_ref_count(const glm::vec3& pos) const; uint32_t get_chunk_ref_count(const glm::vec3& pos) const;
ServerEntityManager& entity_manager();
int get_block(const glm::ivec3& block_pos) const override; int get_block(const glm::ivec3& block_pos) const override;
bool is_solid(const glm::ivec3& block_pos) const override; bool is_solid(const glm::ivec3& block_pos) const override;

View File

@@ -9,6 +9,7 @@
#include "Cubed/gameplay/builders/river_builder.hpp" #include "Cubed/gameplay/builders/river_builder.hpp"
#include "Cubed/gameplay/builders/snowy_plain_builder.hpp" #include "Cubed/gameplay/builders/snowy_plain_builder.hpp"
#include "Cubed/gameplay/cave_path.hpp" #include "Cubed/gameplay/cave_path.hpp"
#include "Cubed/gameplay/creatures/spawn.hpp"
#include "Cubed/gameplay/river.path.hpp" #include "Cubed/gameplay/river.path.hpp"
#include "Cubed/gameplay/server_chunk.hpp" #include "Cubed/gameplay/server_chunk.hpp"
#include "Cubed/gameplay/server_world.hpp" #include "Cubed/gameplay/server_world.hpp"
@@ -17,6 +18,8 @@
#include "Cubed/tools/cubed_hash.hpp" #include "Cubed/tools/cubed_hash.hpp"
#include "Cubed/tools/math_tools.hpp" #include "Cubed/tools/math_tools.hpp"
#include "Cubed/tools/perlin_noise.hpp" #include "Cubed/tools/perlin_noise.hpp"
#include <algorithm>
namespace Cubed { namespace Cubed {
namespace { namespace {
@@ -805,6 +808,35 @@ void ChunkGenerator::generate_river() {
} }
} }
void ChunkGenerator::spawn_creature() {
auto biome = m_chunk.biome();
const auto& blocks = m_chunk.blocks();
const auto& heightmap = m_chunk.heightmap();
const auto& chunk_pos = m_chunk.chunk_pos();
if (std::ranges::contains(SpawnDefaults::PIG.biomes, biome)) {
if (m_random.random_bool(SpawnDefaults::PIG.probability)) {
int want =
m_random.random_int(0, SpawnDefaults::PIG.max_spawn_count);
for (int i = 0; i < want; ++i) {
int x = m_random.random_int(0, CHUNK_SIZE - 1);
int z = m_random.random_int(0, CHUNK_SIZE - 1);
int y = static_cast<int>(heightmap[x][z]);
glm::vec3 pos(x, y + 1, z);
auto type = blocks[Chunk::index(pos)];
if (type != 0) {
continue;
}
auto [world_x, world_y, world_z] = Chunk::block_to_world(
x, y + 1, z, chunk_pos.x, chunk_pos.z);
m_chunk.world().entity_manager().add_entity(
SpawnDefaults::PIG.name,
glm::vec3{world_x, world_y, world_z});
}
}
}
}
ServerChunk& ChunkGenerator::chunk() { return m_chunk; } ServerChunk& ChunkGenerator::chunk() { return m_chunk; }
Random& ChunkGenerator::random() { return m_random; } Random& ChunkGenerator::random() { return m_random; }

View File

@@ -141,6 +141,7 @@ void ServerChunk::gen_phase_five() {
m_generator->generate_cave(); m_generator->generate_cave();
m_generator->generate_vegetation(); m_generator->generate_vegetation();
m_generator->spawn_creature();
m_generator = nullptr; m_generator = nullptr;
} }

View File

@@ -37,6 +37,8 @@ void ServerEntityManager::update() {
for (auto e : view) { for (auto e : view) {
const auto& c = view.get<BaseServerCreature>(e); const auto& c = view.get<BaseServerCreature>(e);
if (!m_world.get_chunk_ref_count(c.transform.position.value)) { if (!m_world.get_chunk_ref_count(c.transform.position.value)) {
const auto& entity = m_registry.get<Entity>(e);
destory(entity.id);
continue; continue;
} }
update_ai(e); update_ai(e);
@@ -102,9 +104,9 @@ void ServerEntityManager::handle_task() {
} }
void ServerEntityManager::add_entity(std::string_view name, void ServerEntityManager::add_entity(std::string_view name,
const glm::vec3& pos) { const glm::vec3& world_pos) {
m_tasks.emplace(Command::CREATE, m_tasks.emplace(Command::CREATE,
EntityCreateElement{std::string(name), pos}); EntityCreateElement{std::string(name), world_pos});
} }
void ServerEntityManager::destory(EntityID id) { void ServerEntityManager::destory(EntityID id) {

View File

@@ -1114,5 +1114,5 @@ BlockType ServerWorld::get_block_tpye(const glm::ivec3& block_pos) const {
} }
int ServerWorld::get_per_tick_time() const { return m_per_tick_time; } int ServerWorld::get_per_tick_time() const { return m_per_tick_time; }
ServerEntityManager& ServerWorld::entity_manager() { return m_entity_manager; }
} // namespace Cubed } // namespace Cubed