资产系统侧线第一波:ResolveProcedural 提为公共静态(键格式唯一定义点);Inspector 程序化模型入口;修 Prefab hover 误导性 not loaded;Content Browser 模型拖拽 → Outliner 创建实体
This commit is contained in:
@@ -315,6 +315,16 @@ void AssetBrowserPanel::DrawFileList(const EditorContext& ctx,
|
|||||||
ImGui::EndDragDropSource();
|
ImGui::EndDragDropSource();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 模型拖拽源:拖到 Outliner 落点创建带 MeshRenderer 的实体
|
||||||
|
//(kModelDragPayload;实体名 = 文件名)
|
||||||
|
if (item.kind == Kind::Model && ImGui::BeginDragDropSource())
|
||||||
|
{
|
||||||
|
ImGui::SetDragDropPayload(kModelDragPayload,
|
||||||
|
assetPath.c_str(), assetPath.size() + 1);
|
||||||
|
ImGui::TextUnformatted(name.c_str());
|
||||||
|
ImGui::EndDragDropSource();
|
||||||
|
}
|
||||||
|
|
||||||
// 双击:.material 打开 Material Editor;.animctrl 打开
|
// 双击:.material 打开 Material Editor;.animctrl 打开
|
||||||
// Controller Editor;model/HDR 进缓存预热(路径去重,重复
|
// Controller Editor;model/HDR 进缓存预热(路径去重,重复
|
||||||
// 双击零成本);场景加载含环境恢复/选中清理流程,提示走
|
// 双击零成本);场景加载含环境恢复/选中清理流程,提示走
|
||||||
@@ -404,21 +414,26 @@ void AssetBrowserPanel::DrawFileList(const EditorContext& ctx,
|
|||||||
std::localtime(&tt));
|
std::localtime(&tt));
|
||||||
ImGui::Text("modified: %s", tmBuf);
|
ImGui::Text("modified: %s", tmBuf);
|
||||||
}
|
}
|
||||||
// 加载状态(只查询不触发加载;model/HDR 进过缓存才显示 loaded)
|
// 加载状态:只对"加载"型资产有意义(查询不触发加载)。
|
||||||
|
// Prefab 是实例化模板、Scene 走 File 菜单——显示 "not
|
||||||
|
// loaded" 会误导(曾让用户当成 bug)
|
||||||
if (ctx.assets)
|
if (ctx.assets)
|
||||||
{
|
{
|
||||||
const char* state = "not loaded";
|
if (item.kind == Kind::Model ||
|
||||||
if (item.kind == Kind::Model &&
|
item.kind == Kind::Texture)
|
||||||
ctx.assets->IsModelLoaded(assetPath))
|
|
||||||
{
|
{
|
||||||
state = "loaded";
|
const bool loaded =
|
||||||
|
(item.kind == Kind::Model)
|
||||||
|
? ctx.assets->IsModelLoaded(assetPath)
|
||||||
|
: ctx.assets->IsEnvironmentLoaded(assetPath);
|
||||||
|
ImGui::Text("state: %s", loaded ? "loaded"
|
||||||
|
: "not loaded");
|
||||||
}
|
}
|
||||||
else if (item.kind == Kind::Texture &&
|
else if (item.kind == Kind::Prefab)
|
||||||
ctx.assets->IsEnvironmentLoaded(assetPath))
|
|
||||||
{
|
{
|
||||||
state = "loaded";
|
ImGui::TextDisabled(
|
||||||
|
"template — drag into scene / double-click");
|
||||||
}
|
}
|
||||||
ImGui::Text("state: %s", state);
|
|
||||||
// 已加载的 model 展开资产数据概览(meshes=0 即纯动画
|
// 已加载的 model 展开资产数据概览(meshes=0 即纯动画
|
||||||
// 容器;未加载不显示——查询不触发加载)
|
// 容器;未加载不显示——查询不触发加载)
|
||||||
if (item.kind == Kind::Model)
|
if (item.kind == Kind::Model)
|
||||||
|
|||||||
@@ -23,6 +23,10 @@
|
|||||||
// 常量住源面板头
|
// 常量住源面板头
|
||||||
inline constexpr const char* kPrefabDragPayload = "PREFAB_PATH";
|
inline constexpr const char* kPrefabDragPayload = "PREFAB_PATH";
|
||||||
|
|
||||||
|
// 模型资产拖拽 payload(Content Browser 拖 .glb/.fbx/... → Outliner
|
||||||
|
// 落点创建带 MeshRenderer 的实体;字符串 = 资产相对路径)
|
||||||
|
inline constexpr const char* kModelDragPayload = "MODEL_PATH";
|
||||||
|
|
||||||
class AssetBrowserPanel {
|
class AssetBrowserPanel {
|
||||||
public:
|
public:
|
||||||
void Draw(const EditorContext& ctx);
|
void Draw(const EditorContext& ctx);
|
||||||
|
|||||||
@@ -49,6 +49,30 @@ namespace {
|
|||||||
*ctx.scene, *ctx.assets, path);
|
*ctx.scene, *ctx.assets, path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 模型拖入落点:创建带 MeshRenderer 的单实体(名字 = 文件名
|
||||||
|
// stem,默认位姿;模型句柄 = LoadModel 磁盘路径)。拖到节点下 =
|
||||||
|
// SetParent keepWorld;空白区 = 场景根。加载失败不建空实体
|
||||||
|
entt::entity SpawnModelDrop(const EditorContext& ctx, const char* path)
|
||||||
|
{
|
||||||
|
if (!ctx.scene || !ctx.assets || !path) {
|
||||||
|
return entt::null;
|
||||||
|
}
|
||||||
|
const auto model = ctx.assets->LoadModel(path);
|
||||||
|
if (!model.Valid()) {
|
||||||
|
return entt::null;
|
||||||
|
}
|
||||||
|
std::string name =
|
||||||
|
std::filesystem::path(path).stem().string();
|
||||||
|
if (name.empty()) {
|
||||||
|
name = "model entity";
|
||||||
|
}
|
||||||
|
auto e = ctx.scene->CreateEntity(name);
|
||||||
|
e.AddComponent<Engine::ECS::MeshRendererComponent>().model = model;
|
||||||
|
ENGINE_INFO("Hierarchy: model entity '{}' created from '{}'",
|
||||||
|
name, path);
|
||||||
|
return static_cast<entt::entity>(e);
|
||||||
|
}
|
||||||
|
|
||||||
// 实体名 → 合法文件名(路径分隔符/保留符净化;空名兜底)
|
// 实体名 → 合法文件名(路径分隔符/保留符净化;空名兜底)
|
||||||
std::string PrefabFileName(std::string name)
|
std::string PrefabFileName(std::string name)
|
||||||
{
|
{
|
||||||
@@ -136,6 +160,15 @@ namespace {
|
|||||||
selected = root;
|
selected = root;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (const ImGuiPayload* pl =
|
||||||
|
ImGui::AcceptDragDropPayload(kModelDragPayload)) {
|
||||||
|
if (const entt::entity me = SpawnModelDrop(
|
||||||
|
ctx, static_cast<const char*>(pl->Data));
|
||||||
|
me != entt::null) {
|
||||||
|
ctx.scene->SetParent(me, e); // keepWorld 默认 true
|
||||||
|
selected = me;
|
||||||
|
}
|
||||||
|
}
|
||||||
ImGui::EndDragDropTarget();
|
ImGui::EndDragDropTarget();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -273,6 +306,14 @@ void HierarchyPanel::Draw(const EditorContext& ctx, entt::entity& selected)
|
|||||||
selected = root;
|
selected = root;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (const ImGuiPayload* pl =
|
||||||
|
ImGui::AcceptDragDropPayload(kModelDragPayload)) {
|
||||||
|
if (const entt::entity me = SpawnModelDrop(
|
||||||
|
ctx, static_cast<const char*>(pl->Data));
|
||||||
|
me != entt::null) {
|
||||||
|
selected = me;
|
||||||
|
}
|
||||||
|
}
|
||||||
ImGui::EndDragDropTarget();
|
ImGui::EndDragDropTarget();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
#include "Engine/Physics/PhysicsWorld.h" // Apply Impulse 调试(GetPhysics 门面)
|
#include "Engine/Physics/PhysicsWorld.h" // Apply Impulse 调试(GetPhysics 门面)
|
||||||
#include "Engine/Scripting/Script.h" // 脚本下拉(ScriptRegistry::Names)
|
#include "Engine/Scripting/Script.h" // 脚本下拉(ScriptRegistry::Names)
|
||||||
#include "Engine/Scripting/ScriptRuntime.h" // 参数编辑 → RefreshParams
|
#include "Engine/Scripting/ScriptRuntime.h" // 参数编辑 → RefreshParams
|
||||||
|
#include "Engine/Serialization/SceneSerializer.h" // ResolveProcedural(程序化入口)
|
||||||
#include "Engine/Core/Log.h" // Create Override 失败告警
|
#include "Engine/Core/Log.h" // Create Override 失败告警
|
||||||
|
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
@@ -670,6 +671,67 @@ void InspectorPanel::Draw(const EditorContext& ctx, entt::entity selected)
|
|||||||
mr->materialOverrides.clear();
|
mr->materialOverrides.clear();
|
||||||
}
|
}
|
||||||
ImGui::Spacing();
|
ImGui::Spacing();
|
||||||
|
// 程序化模型入口:伪资产不在磁盘(下拉框列不到),由
|
||||||
|
// 弹窗组键生成 → ResolveProcedural(与场景反解同一
|
||||||
|
// 键格式唯一定义点;确定性键必然命中缓存)
|
||||||
|
if (ImGui::SmallButton("Procedural..."))
|
||||||
|
{
|
||||||
|
ImGui::OpenPopup("##proc_model");
|
||||||
|
}
|
||||||
|
if (ImGui::BeginPopup("##proc_model"))
|
||||||
|
{
|
||||||
|
static int shape = 0; // 0=cube 1=plane 2=transparent plane
|
||||||
|
static float half = 0.5f;
|
||||||
|
static float col[3] = {0.55f, 0.36f, 0.28f};
|
||||||
|
static float alpha = 0.5f;
|
||||||
|
ImGui::RadioButton("Cube", &shape, 0);
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::RadioButton("Plane", &shape, 1);
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::RadioButton("Trans.Plane", &shape, 2);
|
||||||
|
ImGui::SetNextItemWidth(-1.0f);
|
||||||
|
ImGui::SliderFloat("half size", &half, 0.01f, 10.0f,
|
||||||
|
"%.2f");
|
||||||
|
ImGui::ColorEdit3("color", col);
|
||||||
|
if (shape == 2)
|
||||||
|
{
|
||||||
|
ImGui::SetNextItemWidth(-1.0f);
|
||||||
|
ImGui::SliderFloat("alpha", &alpha, 0.0f, 1.0f,
|
||||||
|
"%.2f");
|
||||||
|
}
|
||||||
|
if (ImGui::Button("Create & Assign"))
|
||||||
|
{
|
||||||
|
const int r = int(col[0] * 255.0f + 0.5f);
|
||||||
|
const int g = int(col[1] * 255.0f + 0.5f);
|
||||||
|
const int b = int(col[2] * 255.0f + 0.5f);
|
||||||
|
char key[96];
|
||||||
|
if (shape == 2)
|
||||||
|
{
|
||||||
|
const int a = int(alpha * 255.0f + 0.5f);
|
||||||
|
std::snprintf(
|
||||||
|
key, sizeof(key),
|
||||||
|
"procedural://transparent-plane/%.3g/%d,%d,%d,%d",
|
||||||
|
half, r, g, b, a);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
std::snprintf(
|
||||||
|
key, sizeof(key),
|
||||||
|
"procedural://%s/%.3g/%d,%d,%d",
|
||||||
|
shape == 0 ? "cube" : "plane", half, r, g, b);
|
||||||
|
}
|
||||||
|
if (auto ref = Engine::Serialization::
|
||||||
|
SceneSerializer::ResolveProcedural(
|
||||||
|
*ctx.assets, key);
|
||||||
|
ref.Valid())
|
||||||
|
{
|
||||||
|
mr->model = ref;
|
||||||
|
mr->materialOverrides.clear();
|
||||||
|
ImGui::CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui::EndPopup();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// 无效句柄不进 GetModel(缓存对无效/悬空句柄 FATAL,是 bug 信号)
|
// 无效句柄不进 GetModel(缓存对无效/悬空句柄 FATAL,是 bug 信号)
|
||||||
if (mr->model.Valid())
|
if (mr->model.Valid())
|
||||||
@@ -1951,6 +2013,10 @@ void InspectorPanel::Draw(const EditorContext& ctx, entt::entity selected)
|
|||||||
// CrossFade 与 Direct 的 crossfade 共用 fade)
|
// CrossFade 与 Direct 的 crossfade 共用 fade)
|
||||||
ImGui::SliderFloat("Speed", &anim->speed, -2.0f, 2.0f);
|
ImGui::SliderFloat("Speed", &anim->speed, -2.0f, 2.0f);
|
||||||
ImGui::SliderFloat("Fade", &anim->fadeDuration, 0.0f, 1.0f);
|
ImGui::SliderFloat("Fade", &anim->fadeDuration, 0.0f, 1.0f);
|
||||||
|
// 根运动(Unity Animator 同名开关):关 = 原地播放,
|
||||||
|
// 位移归脚本/物理驱动(CharacterController 角色用)
|
||||||
|
ImGui::Checkbox("Apply Root Motion",
|
||||||
|
&anim->applyRootMotion);
|
||||||
// 动画数据表:每个 clip 的时长与有效轨道数(attach 进来的
|
// 动画数据表:每个 clip 的时长与有效轨道数(attach 进来的
|
||||||
// 外部动画在此核对骨骼匹配结果——轨道数明显偏少 = 骨骼
|
// 外部动画在此核对骨骼匹配结果——轨道数明显偏少 = 骨骼
|
||||||
// 命名不同源被跳过)
|
// 命名不同源被跳过)
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
#define HELLOVULKAN_SCENESERIALIZER_H
|
#define HELLOVULKAN_SCENESERIALIZER_H
|
||||||
|
|
||||||
#include <Engine/ECS/Scene.h> // Entity + glm vec3/quat(Prefab API 签名用)
|
#include <Engine/ECS/Scene.h> // Entity + glm vec3/quat(Prefab API 签名用)
|
||||||
|
#include <Engine/Core/AssetRef.h> // ModelRef(ResolveProcedural 返回值)
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
@@ -93,6 +94,13 @@ namespace Engine::Serialization {
|
|||||||
const std::string& inPath,
|
const std::string& inPath,
|
||||||
const glm::vec3* overridePos = nullptr,
|
const glm::vec3* overridePos = nullptr,
|
||||||
const glm::quat* overrideRot = nullptr);
|
const glm::quat* overrideRot = nullptr);
|
||||||
|
|
||||||
|
// procedural:// 伪路径 → 参数反解重建(cube/plane/
|
||||||
|
// transparent-plane)。确定性键必然命中缓存;绝不走 assimp。
|
||||||
|
// 未知/格式坏伪路径返回无效句柄。场景加载与编辑器程序化
|
||||||
|
// 入口共用——键格式唯一定义点
|
||||||
|
static ModelRef ResolveProcedural(
|
||||||
|
Assets::AssetManager& assets, const std::string& path);
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Engine::Serialization
|
} // namespace Engine::Serialization
|
||||||
|
|||||||
@@ -179,71 +179,17 @@ namespace Engine::Serialization {
|
|||||||
|
|
||||||
// procedural:// 伪路径 → 参数反解重建(确定性键必然命中缓存;
|
// procedural:// 伪路径 → 参数反解重建(确定性键必然命中缓存;
|
||||||
// 绝不对伪路径调 LoadModel——那会走 assimp 必炸)
|
// 绝不对伪路径调 LoadModel——那会走 assimp 必炸)
|
||||||
ModelRef ResolveProcedural(Assets::AssetManager& assets,
|
// 实现见 SceneSerializer::ResolveProcedural(公共静态,编辑器
|
||||||
const std::string& path)
|
// 程序化入口共用)
|
||||||
{
|
|
||||||
constexpr std::string_view kPlane = "procedural://plane/";
|
|
||||||
constexpr std::string_view kTPlane =
|
|
||||||
"procedural://transparent-plane/";
|
|
||||||
constexpr std::string_view kCube = "procedural://cube/";
|
|
||||||
|
|
||||||
if (path.rfind(kCube, 0) == 0) {
|
|
||||||
// "{halfSize}/{r},{g},{b}"
|
|
||||||
const std::string rest = path.substr(kCube.size());
|
|
||||||
const auto slash = rest.find('/');
|
|
||||||
if (slash == std::string::npos) return {};
|
|
||||||
int c[3];
|
|
||||||
if (!ParseCsvInts(rest.substr(slash + 1), c)) return {};
|
|
||||||
try {
|
|
||||||
return assets.CreateCube(
|
|
||||||
std::stof(rest.substr(0, slash)),
|
|
||||||
static_cast<u8>(c[0]), static_cast<u8>(c[1]),
|
|
||||||
static_cast<u8>(c[2]));
|
|
||||||
}
|
|
||||||
catch (const std::exception&) { return {}; }
|
|
||||||
}
|
|
||||||
|
|
||||||
if (path.rfind(kTPlane, 0) == 0) {
|
|
||||||
// "{halfSize}/{r},{g},{b},{a}"
|
|
||||||
const std::string rest = path.substr(kTPlane.size());
|
|
||||||
const auto slash = rest.find('/');
|
|
||||||
if (slash == std::string::npos) return {};
|
|
||||||
int c[4];
|
|
||||||
if (!ParseCsvInts(rest.substr(slash + 1), c)) return {};
|
|
||||||
try {
|
|
||||||
return assets.CreateTransparentPlane(
|
|
||||||
std::stof(rest.substr(0, slash)),
|
|
||||||
static_cast<u8>(c[0]), static_cast<u8>(c[1]),
|
|
||||||
static_cast<u8>(c[2]), static_cast<u8>(c[3]));
|
|
||||||
}
|
|
||||||
catch (const std::exception&) { return {}; }
|
|
||||||
}
|
|
||||||
|
|
||||||
if (path.rfind(kPlane, 0) == 0) {
|
|
||||||
// "{halfSize}/{r},{g},{b}"
|
|
||||||
const std::string rest = path.substr(kPlane.size());
|
|
||||||
const auto slash = rest.find('/');
|
|
||||||
if (slash == std::string::npos) return {};
|
|
||||||
int c[3];
|
|
||||||
if (!ParseCsvInts(rest.substr(slash + 1), c)) return {};
|
|
||||||
try {
|
|
||||||
return assets.CreatePlane(
|
|
||||||
std::stof(rest.substr(0, slash)),
|
|
||||||
static_cast<u8>(c[0]), static_cast<u8>(c[1]),
|
|
||||||
static_cast<u8>(c[2]));
|
|
||||||
}
|
|
||||||
catch (const std::exception&) { return {}; }
|
|
||||||
}
|
|
||||||
|
|
||||||
return {}; // 未知伪路径
|
|
||||||
}
|
|
||||||
|
|
||||||
// 模型引用解析降级链:procedural 反解 → GUID 主键 → Path 兜底。
|
// 模型引用解析降级链:procedural 反解 → GUID 主键 → Path 兜底。
|
||||||
// 加载成功后 GetOrCreateGUID 回写注册表(注册表丢档自愈)
|
// 加载成功后 GetOrCreateGUID 回写注册表(注册表丢档自愈)
|
||||||
ModelRef ResolveModel(Assets::AssetManager& assets, u64 guid,
|
ModelRef ResolveModel(Assets::AssetManager& assets, u64 guid,
|
||||||
const std::string& path)
|
const std::string& path)
|
||||||
{
|
{
|
||||||
if (ModelRef ref = ResolveProcedural(assets, path); ref.Valid()) {
|
if (ModelRef ref =
|
||||||
|
SceneSerializer::ResolveProcedural(assets, path);
|
||||||
|
ref.Valid()) {
|
||||||
return ref;
|
return ref;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1245,6 +1191,67 @@ namespace Engine::Serialization {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// procedural:// 伪路径 → 参数反解重建(键格式唯一定义点;
|
||||||
|
// 绝不对伪路径调 LoadModel——那会走 assimp 必炸)
|
||||||
|
ModelRef SceneSerializer::ResolveProcedural(
|
||||||
|
Assets::AssetManager& assets, const std::string& path)
|
||||||
|
{
|
||||||
|
constexpr std::string_view kPlane = "procedural://plane/";
|
||||||
|
constexpr std::string_view kTPlane =
|
||||||
|
"procedural://transparent-plane/";
|
||||||
|
constexpr std::string_view kCube = "procedural://cube/";
|
||||||
|
|
||||||
|
if (path.rfind(kCube, 0) == 0) {
|
||||||
|
// "{halfSize}/{r},{g},{b}"
|
||||||
|
const std::string rest = path.substr(kCube.size());
|
||||||
|
const auto slash = rest.find('/');
|
||||||
|
if (slash == std::string::npos) return {};
|
||||||
|
int c[3];
|
||||||
|
if (!ParseCsvInts(rest.substr(slash + 1), c)) return {};
|
||||||
|
try {
|
||||||
|
return assets.CreateCube(
|
||||||
|
std::stof(rest.substr(0, slash)),
|
||||||
|
static_cast<u8>(c[0]), static_cast<u8>(c[1]),
|
||||||
|
static_cast<u8>(c[2]));
|
||||||
|
}
|
||||||
|
catch (const std::exception&) { return {}; }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (path.rfind(kTPlane, 0) == 0) {
|
||||||
|
// "{halfSize}/{r},{g},{b},{a}"
|
||||||
|
const std::string rest = path.substr(kTPlane.size());
|
||||||
|
const auto slash = rest.find('/');
|
||||||
|
if (slash == std::string::npos) return {};
|
||||||
|
int c[4];
|
||||||
|
if (!ParseCsvInts(rest.substr(slash + 1), c)) return {};
|
||||||
|
try {
|
||||||
|
return assets.CreateTransparentPlane(
|
||||||
|
std::stof(rest.substr(0, slash)),
|
||||||
|
static_cast<u8>(c[0]), static_cast<u8>(c[1]),
|
||||||
|
static_cast<u8>(c[2]), static_cast<u8>(c[3]));
|
||||||
|
}
|
||||||
|
catch (const std::exception&) { return {}; }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (path.rfind(kPlane, 0) == 0) {
|
||||||
|
// "{halfSize}/{r},{g},{b}"
|
||||||
|
const std::string rest = path.substr(kPlane.size());
|
||||||
|
const auto slash = rest.find('/');
|
||||||
|
if (slash == std::string::npos) return {};
|
||||||
|
int c[3];
|
||||||
|
if (!ParseCsvInts(rest.substr(slash + 1), c)) return {};
|
||||||
|
try {
|
||||||
|
return assets.CreatePlane(
|
||||||
|
std::stof(rest.substr(0, slash)),
|
||||||
|
static_cast<u8>(c[0]), static_cast<u8>(c[1]),
|
||||||
|
static_cast<u8>(c[2]));
|
||||||
|
}
|
||||||
|
catch (const std::exception&) { return {}; }
|
||||||
|
}
|
||||||
|
|
||||||
|
return {}; // 未知伪路径
|
||||||
|
}
|
||||||
|
|
||||||
ECS::Entity SceneSerializer::InstantiatePrefab(
|
ECS::Entity SceneSerializer::InstantiatePrefab(
|
||||||
ECS::Scene& scene, Assets::AssetManager& assets,
|
ECS::Scene& scene, Assets::AssetManager& assets,
|
||||||
const std::string& inPath,
|
const std::string& inPath,
|
||||||
|
|||||||
Reference in New Issue
Block a user