feat(gameplay): add creature manager for resource resolution

Introduce CreatureManager to load per-creature JSON definitions and centralize
model, animation, and collision resource locations. Refactor model and hitbox
managers to use CreatureManager lookups instead of hardcoded asset path
patterns. Also update ResourceLocation asset path helpers to return
std::filesystem::path and use shared JSON parsing utilities.
This commit is contained in:
2026-08-08 18:33:32 +08:00
parent 4268dc6047
commit bbba25e365
10 changed files with 188 additions and 50 deletions

View File

@@ -0,0 +1,40 @@
#pragma once
#include "Cubed/tools/resource_location.hpp"
#include <optional>
#include <string_view>
#include <tbb/concurrent_hash_map.h>
namespace Cubed {
struct CreatureData {
ResourceLocation name{};
std::optional<ResourceLocation> model{};
std::optional<ResourceLocation> animation{};
std::optional<ResourceLocation> collision{};
};
class CreatureManager {
public:
CreatureManager();
~CreatureManager();
static CreatureManager& instance();
void init();
const CreatureData& get_creature_data(std::string_view name) const;
const CreatureData&
get_creature_data(const ResourceLocation& location) const;
static const CreatureData& data(std::string_view name);
static const CreatureData& data(const ResourceLocation& location);
private:
using CreatureMap = tbb::concurrent_hash_map<ResourceLocation, CreatureData,
ResourceLocation::Hash>;
using cacc = CreatureMap::const_accessor;
using acc = CreatureMap::accessor;
static inline const CreatureData EMPTY;
CreatureMap m_creature_map;
};
} // namespace Cubed

View File

@@ -12,6 +12,8 @@ struct Hitbox {
Hitbox(glm::vec3 center_point, glm::vec3 half_size)
: center(center_point), half(half_size) {}
Hitbox() {};
glm::vec3 min() const { return center - half; }
glm::vec3 max() const { return center + half; }

View File

@@ -6,7 +6,7 @@ namespace Cubed {
class HitboxManager {
public:
struct Handle {
Hitbox box;
Hitbox box{};
HitboxID id = 0;
};
HitboxManager();
@@ -28,6 +28,7 @@ private:
using HitboxMap = tbb::concurrent_hash_map<HitboxID, Hitbox>;
using IDMap = tbb::concurrent_hash_map<std::string, HitboxID>;
using NameMap = tbb::concurrent_hash_map<HitboxID, std::string>;
HitboxID m_next = 0;
IDMap m_id_map;
NameMap m_name_map;

View File

@@ -5,6 +5,7 @@
#include <tbb/concurrent_hash_map.h>
namespace Cubed {
class CreatureData;
class ModelManager {
public:
struct Handle {
@@ -41,6 +42,6 @@ private:
IDMap m_id_map;
NameMap m_name_map;
Handle load_model(std::string_view model_name);
void load_anim_config(ModelNode& node, const std::string& path);
void load_anim_config(ModelNode& node, const CreatureData& data);
};
} // namespace Cubed

View File

@@ -1,6 +1,7 @@
#pragma once
#include <cstddef>
#include <filesystem>
#include <functional>
#include <optional>
#include <regex>
@@ -28,7 +29,7 @@ struct ResourceLocation {
std::string(str.substr(it + 1))};
}
static std::string get_assets_path(std::string_view ns) {
static std::filesystem::path get_assets_path_prefix(std::string_view ns) {
if (ns == "cubed") {
return ASSETS_PATH "cubed";
}
@@ -51,7 +52,9 @@ struct ResourceLocation {
return std::hash<std::string>()(p.to_string());
}
std::string assets_path() const { return get_assets_path(ns); }
std::filesystem::path assets_path_prefix() const {
return get_assets_path_prefix(ns);
}
};
} // namespace Cubed

View File

@@ -109,4 +109,5 @@ target_sources(${PROJECT_NAME}
ui/screenshot_ui.cpp
scene/screenshot_scene.cpp
ui/scroll_view.cpp
gameplay/creatures/creature_manager.cpp
)

View File

@@ -109,8 +109,8 @@ float BlockManager::roughness(BlockType id) {
}
void BlockManager::init() {
fs::path root_path{
ResourceLocation::get_assets_path(ResourceLocation::DEFAULT_NAMESPACE)};
fs::path root_path{ResourceLocation::get_assets_path_prefix(
ResourceLocation::DEFAULT_NAMESPACE)};
fs::path block_path = root_path / "blocks";
fs::create_directories(block_path);

View File

@@ -0,0 +1,94 @@
#include "Cubed/gameplay/creatures/creature_manager.hpp"
#include "Cubed/tools/cubed_assert.hpp"
#include "Cubed/tools/json_utils.hpp"
#include <filesystem>
#include <rapidjson/document.h>
namespace fs = std::filesystem;
using namespace rapidjson;
namespace Cubed {
CreatureManager::CreatureManager() {}
CreatureManager::~CreatureManager() {}
CreatureManager& CreatureManager::instance() {
static CreatureManager inst;
return inst;
}
void CreatureManager::init() {
fs::path root_path{ResourceLocation::get_assets_path_prefix(
ResourceLocation::DEFAULT_NAMESPACE)};
fs::path creature_path = root_path / "creatures";
fs::create_directories(creature_path);
for (auto& entry : fs::recursive_directory_iterator(creature_path)) {
if (!entry.is_regular_file()) {
continue;
}
if (entry.path().extension() != ".json") {
continue;
}
Document doc;
if (!Tools::parse_json(doc, entry.path())) {
continue;
}
std::string name;
if (!Tools::get_json_value(doc, "name", name)) {
Logger::error("creature json {} doesn't have name",
entry.path().string());
continue;
}
CreatureData data;
auto n = ResourceLocation::parse(name);
if (!n) {
continue;
}
data.name = *n;
std::string s;
if (Tools::get_json_value(doc, "model", s)) {
data.model = ResourceLocation::parse(s);
}
if (Tools::get_json_value(doc, "animation", s)) {
data.animation = ResourceLocation::parse(s);
}
if (Tools::get_json_value(doc, "collision", s)) {
data.collision = ResourceLocation::parse(s);
}
}
}
const CreatureData&
CreatureManager::get_creature_data(std::string_view name) const {
auto l = ResourceLocation::parse(name);
if (l) {
return get_creature_data(*l);
} else {
Logger::error("Can't get creature {} data", name);
ASSERT(false);
return EMPTY;
}
}
const CreatureData&
CreatureManager::get_creature_data(const ResourceLocation& location) const {
cacc c;
if (m_creature_map.find(c, location)) {
return c->second;
}
Logger::error("Can't get creature {} data", location.to_string());
return EMPTY;
}
const CreatureData& CreatureManager::data(std::string_view name) {
return instance().get_creature_data(name);
}
const CreatureData& CreatureManager::data(const ResourceLocation& location) {
return instance().get_creature_data(location);
}
} // namespace Cubed

View File

@@ -1,6 +1,8 @@
#include "Cubed/gameplay/hitbox_manager.hpp"
#include "Cubed/gameplay/creatures/creature_manager.hpp"
#include "Cubed/tools/cubed_assert.hpp"
#include "Cubed/tools/json_utils.hpp"
#include "Cubed/tools/log.hpp"
#include "Cubed/tools/resource_location.hpp"
@@ -9,6 +11,11 @@
namespace fs = std::filesystem;
using namespace rapidjson;
namespace Cubed {
namespace {
const HitboxManager::Handle EMPTY{};
}
HitboxManager::HitboxManager() { HitboxMap::accessor a; }
HitboxManager::~HitboxManager() {}
@@ -57,33 +64,24 @@ HitboxManager::Handle HitboxManager::get_hitbox(const std::string& name) {
}
HitboxManager::Handle HitboxManager::load(std::string_view name) {
auto space = ResourceLocation::parse(name);
if (!space) {
Logger::error("Can't load hitbox {}", name);
auto& location = CreatureManager::data(name);
if (!location.collision) {
Logger::error("Can't find {} collision.json", name);
ASSERT(false);
return EMPTY;
}
fs::path p;
if (space->ns == "cubed") {
p = std::format("{}model/creature/{}/collision.json", ASSETS_PATH,
space->path);
} else {
p = std::format("{}/model/creature/{}/collision.json", space->ns,
space->path);
}
fs::path p =
location.collision->assets_path_prefix() / location.collision->path;
try {
glm::vec3 center;
glm::vec3 half;
std::ifstream s{p};
IStreamWrapper isw(s);
Document doc;
doc.ParseStream(isw);
if (doc.HasParseError()) {
auto code = doc.GetParseError();
throw std::runtime_error(
std::format("Parse {} failed, error code {}", p.string(),
static_cast<int>(code)));
Document doc;
if (!Tools::parse_json(doc, p)) {
Logger::error("Can't parse hitbox {}", name);
ASSERT(false);
return EMPTY;
}
if (doc.HasMember("boxes")) {
const Value& box = doc["boxes"];

View File

@@ -1,6 +1,8 @@
#include "Cubed/render/model_manager.hpp"
#include "Cubed/gameplay/creatures/creature_manager.hpp"
#include "Cubed/tools/cubed_assert.hpp"
#include "Cubed/tools/json_utils.hpp"
#include "Cubed/tools/log.hpp"
#include "Cubed/tools/resource_location.hpp"
@@ -68,24 +70,20 @@ const std::string& ModelManager::get_model_name(ModelID id) {
ModelManager::Handle ModelManager::load_model(std::string_view model_name) {
auto space = ResourceLocation::parse(model_name);
if (!space) {
Logger::error("Can't Parse Model name {}", model_name);
auto& location = CreatureManager::data(model_name);
if (!location.model) {
Logger::error("Can't find {} model key", model_name);
ASSERT(false);
}
std::string path;
if (space->ns == "cubed") {
path = std::format("{}model/creature/{}/{}.glb", ASSETS_PATH,
space->path, space->path);
} else {
path = std::format("./{}/model/creature/{}/{}.glb", space->ns,
space->path, space->path);
}
fs::path path = location.model->assets_path_prefix() / location.model->path;
auto model = m_loader.load(path);
fs::path anim_path = path;
anim_path = anim_path.parent_path() / "animation.json";
load_anim_config(model, anim_path.string());
load_anim_config(model, location);
ModelMap::accessor acc;
if (m_models.insert(acc, m_next++)) {
acc->second = std::move(model);
} else {
@@ -95,26 +93,26 @@ ModelManager::Handle ModelManager::load_model(std::string_view model_name) {
return {cacc->second, cacc->first};
}
}
m_id_map.emplace(model_name, acc->first);
m_name_map.emplace(acc->first, model_name);
return {acc->second, acc->first};
}
void ModelManager::load_anim_config(ModelNode& node, const std::string& path) {
if (!fs::is_regular_file(path)) {
void ModelManager::load_anim_config(ModelNode& node, const CreatureData& data) {
if (!data.animation) {
return;
}
std::ifstream s(path);
if (!s.is_open()) {
return;
}
rapidjson::IStreamWrapper isw(s);
fs::path path = data.animation->assets_path_prefix() / data.animation->path;
rapidjson::Document doc;
doc.ParseStream(isw);
if (doc.HasParseError()) {
Logger::warn("Can't parse anim config {}", path);
if (!Tools::parse_json(doc, path)) {
return;
}
ModelAnimConfig cfg;
if (doc.HasMember("walk")) {
cfg.walk_speed = doc["walk"]["speed"].GetFloat();