feat(editor): full Unreal/Unity/Godot studio overhaul with scene tree, component inspector and docked IDE
This commit is contained in:
parent
341966debe
commit
c14ae884aa
6 changed files with 548 additions and 180 deletions
Binary file not shown.
11
editor/project.eblanproj
Normal file
11
editor/project.eblanproj
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
[Project]
|
||||
Title=EBLAN_GAME
|
||||
Author=Developer
|
||||
Version=1.0.0
|
||||
StartupMap=assets/map.dat
|
||||
WindowTitle=EBLAN ENGINE 3D - Standalone Retro Game
|
||||
DefaultWidth=1280
|
||||
DefaultHeight=960
|
||||
GameMode=1
|
||||
ShowWeapons=1
|
||||
ShowCrosshair=1
|
||||
|
|
@ -1104,7 +1104,13 @@ void EditorApp::RenderAssetManagerModal() {
|
|||
|
||||
ImGui::SetNextWindowSize(ImVec2(820, 580), ImGuiCond_FirstUseEver);
|
||||
if (ImGui::Begin("Менеджер ассетов (Texture & Audio Manager)", &showAssetManagerModal)) {
|
||||
if (ImGui::BeginTabBar("AssetManagerTabs")) {
|
||||
RenderAssetManagerContents();
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
void EditorApp::RenderAssetManagerContents() {
|
||||
if (ImGui::BeginTabBar("AssetManagerTabs")) {
|
||||
if (ImGui::BeginTabItem("Текстурный пакет (16 слотов)")) {
|
||||
ImGui::Text("Файл пакета: %s", GetTexturePackPath().c_str());
|
||||
ImGui::SameLine();
|
||||
|
|
@ -1225,8 +1231,6 @@ void EditorApp::RenderAssetManagerModal() {
|
|||
|
||||
ImGui::EndTabBar();
|
||||
}
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
|
@ -1334,6 +1338,51 @@ void EditorApp::FloodFill(int startX, int startY, uint8_t targetType, uint8_t fi
|
|||
for (const auto& [x, y] : cells) SetCell(x, y, fillType);
|
||||
}
|
||||
|
||||
void EditorApp::DuplicateSelectedEntity() {
|
||||
if (selectedSpriteIdx >= 0 && selectedSpriteIdx < static_cast<int>(sprites.size())) {
|
||||
if (sprites.size() >= MAX_LEVEL_SPRITES) {
|
||||
statusMessage = "Лимит спрайтов на карте достигнут (макс. 32).";
|
||||
return;
|
||||
}
|
||||
PushUndo();
|
||||
SpriteEntry copy = sprites[selectedSpriteIdx];
|
||||
copy.x = (std::min)(copy.x + 64, (MAP_GRID_W - 2) * 64);
|
||||
copy.y = (std::min)(copy.y + 64, (MAP_GRID_H - 2) * 64);
|
||||
sprites.push_back(copy);
|
||||
selectedSpriteIdx = static_cast<int>(sprites.size()) - 1;
|
||||
statusMessage = "Сущность дублирована (Ctrl+D).";
|
||||
}
|
||||
}
|
||||
|
||||
void EditorApp::DeleteSelectedEntity() {
|
||||
if (selectedSpriteIdx >= 0 && selectedSpriteIdx < static_cast<int>(sprites.size())) {
|
||||
PushUndo();
|
||||
sprites.erase(sprites.begin() + selectedSpriteIdx);
|
||||
selectedSpriteIdx = -1;
|
||||
statusMessage = "Сущность удалена (Del).";
|
||||
} else if (selectedTransitionIdx >= 0 && selectedTransitionIdx < static_cast<int>(transitions.size())) {
|
||||
PushUndo();
|
||||
RemoveCutsceneTransition(selectedTransitionIdx);
|
||||
transitions.erase(transitions.begin() + selectedTransitionIdx);
|
||||
selectedTransitionIdx = -1;
|
||||
statusMessage = "Портал перехода удален (Del).";
|
||||
}
|
||||
}
|
||||
|
||||
void EditorApp::FocusSelectedEntity() {
|
||||
if (selectedSpriteIdx >= 0 && selectedSpriteIdx < static_cast<int>(sprites.size())) {
|
||||
int gx = sprites[selectedSpriteIdx].x / 64;
|
||||
int gy = sprites[selectedSpriteIdx].y / 64;
|
||||
canvasPanX = 150.0f - (gx * canvasZoom);
|
||||
canvasPanY = 150.0f - (gy * canvasZoom);
|
||||
statusMessage = "Камера сфокусирована на объекте (F).";
|
||||
} else {
|
||||
canvasPanX = 150.0f - (playerStartX * canvasZoom);
|
||||
canvasPanY = 150.0f - (playerStartY * canvasZoom);
|
||||
statusMessage = "Камера сфокусирована на игроке (F).";
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Hotkeys
|
||||
// -----------------------------------------------------------------------------
|
||||
|
|
@ -1363,14 +1412,17 @@ void EditorApp::HandleHotkeys() {
|
|||
SaveLevel(currentFilePath);
|
||||
}
|
||||
}
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_D, false)) DuplicateSelectedEntity();
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_Z, false)) Undo();
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_Y, false)) Redo();
|
||||
return; // no single-letter tool hotkeys while Ctrl held
|
||||
}
|
||||
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_F4, false)) {
|
||||
showAssetManagerModal = !showAssetManagerModal;
|
||||
}
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_Delete, false)) DeleteSelectedEntity();
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_F, false)) FocusSelectedEntity();
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_1, false)) viewportMode = 2; // 2D Map
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_2, false)) viewportMode = 1; // 3D View
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_3, false)) viewportMode = 0; // Split
|
||||
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_B, false)) currentTool = TOOL_BRUSH;
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_E, false)) currentTool = TOOL_ERASER;
|
||||
|
|
@ -1410,54 +1462,111 @@ void EditorApp::RenderUI() {
|
|||
ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize |
|
||||
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoBringToFrontOnFocus);
|
||||
|
||||
// Columns: Left = Palette & Hierarchy, Middle = Grid Canvas, Right = Inspector
|
||||
float leftWidth = 220.0f;
|
||||
float rightWidth = 280.0f;
|
||||
// Columns: Left = Hierarchy & Tile Palette, Middle = Workspace Viewport/IDE, Right = Inspector
|
||||
float leftWidth = 260.0f;
|
||||
float rightWidth = 320.0f;
|
||||
float canvasWidth = ImGui::GetContentRegionAvail().x - leftWidth - rightWidth - 20.0f;
|
||||
|
||||
// Left Panel
|
||||
// Left Panel (Unity / Unreal Docked Tabs)
|
||||
ImGui::BeginChild("LeftPanel", ImVec2(leftWidth, 0), true);
|
||||
RenderTilePalette();
|
||||
ImGui::Separator();
|
||||
RenderHierarchy();
|
||||
if (ImGui::BeginTabBar("LeftPanelTabs", ImGuiTabBarFlags_None)) {
|
||||
if (ImGui::BeginTabItem("🗂 Сцена")) {
|
||||
RenderHierarchy();
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
if (ImGui::BeginTabItem("🎨 Палитра")) {
|
||||
RenderTilePalette();
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
if (ImGui::BeginTabItem("📁 Файлы")) {
|
||||
ImGui::TextColored(ImVec4(0.2f, 0.8f, 1.0f, 1.0f), "Браузер ассетов");
|
||||
ImGui::Separator();
|
||||
RenderAssetManagerContents();
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
ImGui::EndTabBar();
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
// Center Panel (Grid Canvas)
|
||||
// Center Panel (Grid Canvas / 3D Scene Viewport / MASM32 IDE / Asset Browser)
|
||||
ImGui::BeginChild("CenterPanel", ImVec2(canvasWidth, 0), false);
|
||||
float timelineHeight = showCutsceneTimeline ? (std::min)(340.0f, ImGui::GetContentRegionAvail().y * 0.48f) : 0.0f;
|
||||
ImGui::BeginChild("CanvasPanel", ImVec2(0, showCutsceneTimeline ? -timelineHeight - 6 : 0), true);
|
||||
if (ImGui::RadioButton("Рядом", viewportMode == 0)) viewportMode = 0;
|
||||
ImGui::SameLine(); if (ImGui::RadioButton("Игровой вид", viewportMode == 1)) viewportMode = 1;
|
||||
ImGui::SameLine(); if (ImGui::RadioButton("Карта", viewportMode == 2)) viewportMode = 2;
|
||||
ImGui::SameLine(); ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical); ImGui::SameLine();
|
||||
if (ImGui::SmallButton(" - ")) { canvasZoom = (std::max)(8.0f, canvasZoom - 4.0f); }
|
||||
ImGui::SameLine(); if (ImGui::SmallButton(" 100% ")) { canvasZoom = 20.0f; canvasPanX = 20.0f; canvasPanY = 20.0f; }
|
||||
ImGui::SameLine(); if (ImGui::SmallButton(" + ")) { canvasZoom = (std::min)(48.0f, canvasZoom + 4.0f); }
|
||||
float sceneWidth = viewportMode == 0 ? ImGui::GetContentRegionAvail().x * 0.5f : 0;
|
||||
if (viewportMode != 2) {
|
||||
ImGui::BeginChild("SceneViewport", ImVec2(sceneWidth,0), true);
|
||||
RenderSceneViewport();
|
||||
ImGui::EndChild();
|
||||
if (viewportMode == 0) ImGui::SameLine();
|
||||
|
||||
// Workspace Mode Tabs (Godot 4 / Unreal Engine 5 style)
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 4.0f);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4.0f, 4.0f));
|
||||
|
||||
auto ModeTabButton = [&](const char* label, int modeId) {
|
||||
bool active = (viewportMode == modeId);
|
||||
if (active) {
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.06f, 0.48f, 0.88f, 1.0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.12f, 0.58f, 0.98f, 1.0f));
|
||||
} else {
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.16f, 0.18f, 0.22f, 1.0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.24f, 0.27f, 0.33f, 1.0f));
|
||||
}
|
||||
if (ImGui::Button(label)) {
|
||||
viewportMode = modeId;
|
||||
}
|
||||
ImGui::PopStyleColor(2);
|
||||
};
|
||||
|
||||
ModeTabButton("🗺 2D Карта", 2);
|
||||
ImGui::SameLine();
|
||||
ModeTabButton("🎮 3D Сцена", 1);
|
||||
ImGui::SameLine();
|
||||
ModeTabButton("🪟 2D + 3D", 0);
|
||||
ImGui::SameLine();
|
||||
ModeTabButton("💻 Скрипты (IDE)", 3);
|
||||
ImGui::SameLine();
|
||||
ModeTabButton("📦 Ассеты", 4);
|
||||
|
||||
if (viewportMode == 0 || viewportMode == 2) {
|
||||
ImGui::SameLine();
|
||||
ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical);
|
||||
ImGui::SameLine();
|
||||
if (ImGui::SmallButton(" - ")) { canvasZoom = (std::max)(8.0f, canvasZoom - 4.0f); }
|
||||
ImGui::SameLine(); if (ImGui::SmallButton(" 100% ")) { canvasZoom = 20.0f; canvasPanX = 20.0f; canvasPanY = 20.0f; }
|
||||
ImGui::SameLine(); if (ImGui::SmallButton(" + ")) { canvasZoom = (std::min)(48.0f, canvasZoom + 4.0f); }
|
||||
}
|
||||
if (viewportMode != 1) {
|
||||
ImGui::BeginChild("MapViewport", ImVec2(0,0), false);
|
||||
RenderGridCanvas();
|
||||
ImGui::EndChild();
|
||||
}
|
||||
ImGui::EndChild();
|
||||
if (showCutsceneTimeline) {
|
||||
ImGui::BeginChild("CutsceneTimeline", ImVec2(0, 0), true);
|
||||
RenderCutsceneTimeline();
|
||||
ImGui::PopStyleVar(2);
|
||||
|
||||
if (viewportMode == 3) {
|
||||
// Integrated MASM32 IDE docked directly in center workspace!
|
||||
std::string root = fs::path(currentFilePath).parent_path().parent_path().string();
|
||||
if (root.empty()) root = ".";
|
||||
RenderCodeEditorContents(root);
|
||||
} else if (viewportMode == 4) {
|
||||
// Texture & Audio Asset Browser docked in center workspace!
|
||||
RenderAssetManagerContents();
|
||||
} else {
|
||||
float timelineHeight = showCutsceneTimeline ? (std::min)(340.0f, ImGui::GetContentRegionAvail().y * 0.48f) : 0.0f;
|
||||
ImGui::BeginChild("CanvasPanel", ImVec2(0, showCutsceneTimeline ? -timelineHeight - 6 : 0), true);
|
||||
float sceneWidth = viewportMode == 0 ? ImGui::GetContentRegionAvail().x * 0.5f : 0;
|
||||
if (viewportMode != 2) {
|
||||
ImGui::BeginChild("SceneViewport", ImVec2(sceneWidth,0), true);
|
||||
RenderSceneViewport();
|
||||
ImGui::EndChild();
|
||||
if (viewportMode == 0) ImGui::SameLine();
|
||||
}
|
||||
if (viewportMode != 1) {
|
||||
ImGui::BeginChild("MapViewport", ImVec2(0,0), false);
|
||||
RenderGridCanvas();
|
||||
ImGui::EndChild();
|
||||
}
|
||||
ImGui::EndChild();
|
||||
if (showCutsceneTimeline) {
|
||||
ImGui::BeginChild("CutsceneTimeline", ImVec2(0, 0), true);
|
||||
RenderCutsceneTimeline();
|
||||
ImGui::EndChild();
|
||||
}
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
// Right Panel (Inspector)
|
||||
// Right Panel (Unity / Unreal Component Inspector)
|
||||
ImGui::BeginChild("RightPanel", ImVec2(rightWidth, 0), true);
|
||||
RenderInspector();
|
||||
ImGui::EndChild();
|
||||
|
|
@ -2058,46 +2167,197 @@ void EditorApp::RenderTilePalette() {
|
|||
}
|
||||
|
||||
void EditorApp::RenderHierarchy() {
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.2f, 1.0f), "СУЩНОСТИ (ИЕРАРХИЯ)");
|
||||
ImGui::TextColored(ImVec4(0.2f, 0.8f, 1.0f, 1.0f), "ИЕРАРХИЯ СЦЕНЫ (SCENE TREE)");
|
||||
ImGui::Separator();
|
||||
|
||||
ImGui::Text("Игрок: Spawn (%d, %d)", playerStartX, playerStartY);
|
||||
// Search filter + Quick Add button (Unity / Unreal style)
|
||||
ImGui::InputTextWithHint("##HierarchySearch", "🔍 Поиск...", searchHierarchyBuf, sizeof(searchHierarchyBuf));
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("+ Добавить")) {
|
||||
ImGui::OpenPopup("QuickEntitySpawnPopup");
|
||||
}
|
||||
if (ImGui::BeginPopup("QuickEntitySpawnPopup")) {
|
||||
auto AddEntityItem = [&](const char* label, uint8_t archetype) {
|
||||
if (ImGui::MenuItem(label)) {
|
||||
if (sprites.size() < MAX_LEVEL_SPRITES) {
|
||||
PushUndo();
|
||||
SpriteEntry spr{};
|
||||
spr.x = (playerStartX + 1) * 64 + 32;
|
||||
spr.y = (playerStartY + 1) * 64 + 32;
|
||||
spr.scale = 65536;
|
||||
uint32_t defaultTex = 11;
|
||||
if (archetype == ENTITY_ARCHETYPE_GUARD) defaultTex = 14;
|
||||
else if (archetype == ENTITY_ARCHETYPE_MEDKIT) defaultTex = 12;
|
||||
else if (archetype == ENTITY_ARCHETYPE_AMMO) defaultTex = 13;
|
||||
else if (archetype == ENTITY_ARCHETYPE_KEY) defaultTex = 15;
|
||||
else if (archetype == ENTITY_ARCHETYPE_SWITCH) defaultTex = 10;
|
||||
else if (archetype == ENTITY_ARCHETYPE_BARREL) defaultTex = 11;
|
||||
spr.textureId = defaultTex;
|
||||
SetSpriteEntityData(spr, archetype, (archetype == ENTITY_ARCHETYPE_GUARD ? 60 : 100), (archetype == ENTITY_ARCHETYPE_GUARD ? 2 : 0), (archetype == ENTITY_ARCHETYPE_GUARD ? 6 : 0));
|
||||
sprites.push_back(spr);
|
||||
selectedSpriteIdx = static_cast<int>(sprites.size()) - 1;
|
||||
selectedTransitionIdx = -1;
|
||||
statusMessage = "Сущность создана на сцене.";
|
||||
} else {
|
||||
statusMessage = "Лимит спрайтов достигнут (макс. 32).";
|
||||
}
|
||||
}
|
||||
};
|
||||
AddEntityItem("⚔ Враг (Guard AI)", ENTITY_ARCHETYPE_GUARD);
|
||||
AddEntityItem("🩹 Аптечка (+25 HP)", ENTITY_ARCHETYPE_MEDKIT);
|
||||
AddEntityItem("📦 Патроны (+20)", ENTITY_ARCHETYPE_AMMO);
|
||||
AddEntityItem("🔑 Ключ от двери", ENTITY_ARCHETYPE_KEY);
|
||||
AddEntityItem("🕹 Рычаг / Переключатель", ENTITY_ARCHETYPE_SWITCH);
|
||||
AddEntityItem("💥 Взрывная бочка", ENTITY_ARCHETYPE_BARREL);
|
||||
AddEntityItem("🏺 Декорация / Проп", ENTITY_ARCHETYPE_PROP);
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
ImGui::Text("Спрайты на карте (%zu / %d):", sprites.size(), MAX_LEVEL_SPRITES);
|
||||
auto MatchesSearch = [&](const char* text) {
|
||||
if (!searchHierarchyBuf[0]) return true;
|
||||
std::string query = searchHierarchyBuf;
|
||||
std::string target = text;
|
||||
for (auto& c : query) c = (char)tolower(c);
|
||||
for (auto& c : target) c = (char)tolower(c);
|
||||
return target.find(query) != std::string::npos;
|
||||
};
|
||||
|
||||
// 1. Player Spawn (Always at top of hierarchy)
|
||||
char playerLabel[96];
|
||||
snprintf(playerLabel, sizeof(playerLabel), "👤 Игрок [Spawn: (%d, %d)]", playerStartX, playerStartY);
|
||||
if (MatchesSearch(playerLabel) || MatchesSearch("player") || MatchesSearch("игрок")) {
|
||||
bool isPlayerSelected = (selectedSpriteIdx == -1 && selectedTransitionIdx == -1);
|
||||
if (ImGui::Selectable(playerLabel, isPlayerSelected)) {
|
||||
selectedSpriteIdx = -1;
|
||||
selectedTransitionIdx = -1;
|
||||
}
|
||||
if (ImGui::BeginPopupContextItem("PlayerContextMenu")) {
|
||||
if (ImGui::MenuItem("🎯 Сфокусировать камеру", "F")) {
|
||||
FocusSelectedEntity();
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Sprite Entities grouped by categories
|
||||
size_t guardCount = 0, pickupCount = 0, objectCount = 0;
|
||||
for (size_t i = 0; i < sprites.size(); ++i) {
|
||||
uint8_t a = GetSpriteEntityType(sprites[i]);
|
||||
if (a == ENTITY_ARCHETYPE_GUARD) guardCount++;
|
||||
else if (a == ENTITY_ARCHETYPE_MEDKIT || a == ENTITY_ARCHETYPE_AMMO || a == ENTITY_ARCHETYPE_KEY) pickupCount++;
|
||||
else objectCount++;
|
||||
}
|
||||
|
||||
auto RenderSpriteItem = [&](size_t i) {
|
||||
int gx = sprites[i].x / 64;
|
||||
int gy = sprites[i].y / 64;
|
||||
uint8_t aType = GetSpriteEntityType(sprites[i]);
|
||||
const char* typeTag = "Проп";
|
||||
if (aType == ENTITY_ARCHETYPE_GUARD) typeTag = "Враг";
|
||||
else if (aType == ENTITY_ARCHETYPE_MEDKIT) typeTag = "+HP";
|
||||
else if (aType == ENTITY_ARCHETYPE_AMMO) typeTag = "+Ammo";
|
||||
else if (aType == ENTITY_ARCHETYPE_MEDKIT) typeTag = "Аптечка";
|
||||
else if (aType == ENTITY_ARCHETYPE_AMMO) typeTag = "Патроны";
|
||||
else if (aType == ENTITY_ARCHETYPE_KEY) typeTag = "Ключ";
|
||||
else if (aType == ENTITY_ARCHETYPE_SWITCH) typeTag = "Рычаг";
|
||||
else if (aType == ENTITY_ARCHETYPE_BARREL) typeTag = "Бочка";
|
||||
|
||||
char label[64];
|
||||
snprintf(label, sizeof(label), "[%s] #%zu (%d, %d)", typeTag, i + 1, gx, gy);
|
||||
char label[96];
|
||||
snprintf(label, sizeof(label), "[%s] #%zu (%d, %d)##spr_%zu", typeTag, i + 1, gx, gy, i);
|
||||
|
||||
if (ImGui::Selectable(label, selectedSpriteIdx == static_cast<int>(i))) {
|
||||
if (!MatchesSearch(label)) return;
|
||||
|
||||
bool isSel = (selectedSpriteIdx == static_cast<int>(i));
|
||||
if (ImGui::Selectable(label, isSel)) {
|
||||
selectedSpriteIdx = static_cast<int>(i);
|
||||
selectedTransitionIdx = -1;
|
||||
}
|
||||
if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
|
||||
selectedSpriteIdx = static_cast<int>(i);
|
||||
FocusSelectedEntity();
|
||||
}
|
||||
if (ImGui::BeginPopupContextItem()) {
|
||||
if (ImGui::MenuItem("📋 Дублировать", "Ctrl+D")) {
|
||||
selectedSpriteIdx = static_cast<int>(i);
|
||||
DuplicateSelectedEntity();
|
||||
}
|
||||
if (ImGui::MenuItem("🎯 Сфокусировать", "F")) {
|
||||
selectedSpriteIdx = static_cast<int>(i);
|
||||
FocusSelectedEntity();
|
||||
}
|
||||
ImGui::Separator();
|
||||
if (ImGui::MenuItem("🗑 Удалить", "Del")) {
|
||||
selectedSpriteIdx = static_cast<int>(i);
|
||||
DeleteSelectedEntity();
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
};
|
||||
|
||||
// Enemies Category
|
||||
char enemyHdr[64];
|
||||
snprintf(enemyHdr, sizeof(enemyHdr), "⚔ Враги и Guard AI (%zu)", guardCount);
|
||||
if (ImGui::TreeNodeEx(enemyHdr, ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||
for (size_t i = 0; i < sprites.size(); ++i) {
|
||||
if (GetSpriteEntityType(sprites[i]) == ENTITY_ARCHETYPE_GUARD) RenderSpriteItem(i);
|
||||
}
|
||||
ImGui::TreePop();
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
ImGui::Text("Порталы перехода (%zu / %d):", transitions.size(), MAX_LEVEL_TRANSITIONS);
|
||||
for (size_t i = 0; i < transitions.size(); ++i) {
|
||||
char label[96];
|
||||
snprintf(label, sizeof(label), "Портал #%zu (%d, %d) -> %s",
|
||||
i + 1, transitions[i].triggerCellX, transitions[i].triggerCellY,
|
||||
transitions[i].targetMap[0] ? transitions[i].targetMap : "<?>");
|
||||
|
||||
if (ImGui::Selectable(label, selectedTransitionIdx == static_cast<int>(i))) {
|
||||
selectedTransitionIdx = static_cast<int>(i);
|
||||
selectedSpriteIdx = -1;
|
||||
// Pickups Category
|
||||
char pickupHdr[64];
|
||||
snprintf(pickupHdr, sizeof(pickupHdr), "📦 Лут и предметы (%zu)", pickupCount);
|
||||
if (ImGui::TreeNodeEx(pickupHdr, ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||
for (size_t i = 0; i < sprites.size(); ++i) {
|
||||
uint8_t a = GetSpriteEntityType(sprites[i]);
|
||||
if (a == ENTITY_ARCHETYPE_MEDKIT || a == ENTITY_ARCHETYPE_AMMO || a == ENTITY_ARCHETYPE_KEY) RenderSpriteItem(i);
|
||||
}
|
||||
ImGui::TreePop();
|
||||
}
|
||||
|
||||
// Interactive & Props Category
|
||||
char objHdr[64];
|
||||
snprintf(objHdr, sizeof(objHdr), "⚙ Объекты и окружение (%zu)", objectCount);
|
||||
if (ImGui::TreeNodeEx(objHdr, ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||
for (size_t i = 0; i < sprites.size(); ++i) {
|
||||
uint8_t a = GetSpriteEntityType(sprites[i]);
|
||||
if (a != ENTITY_ARCHETYPE_GUARD && a != ENTITY_ARCHETYPE_MEDKIT && a != ENTITY_ARCHETYPE_AMMO && a != ENTITY_ARCHETYPE_KEY) RenderSpriteItem(i);
|
||||
}
|
||||
ImGui::TreePop();
|
||||
}
|
||||
|
||||
// 3. Sublevel Transitions Category
|
||||
char transHdr[64];
|
||||
snprintf(transHdr, sizeof(transHdr), "🌀 Порталы перехода (%zu)", transitions.size());
|
||||
if (ImGui::TreeNodeEx(transHdr, ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||
for (size_t i = 0; i < transitions.size(); ++i) {
|
||||
char label[96];
|
||||
snprintf(label, sizeof(label), "Портал #%zu (%d, %d) -> %s##trans_%zu",
|
||||
i + 1, transitions[i].triggerCellX, transitions[i].triggerCellY,
|
||||
transitions[i].targetMap[0] ? transitions[i].targetMap : "<?>", i);
|
||||
|
||||
if (!MatchesSearch(label)) continue;
|
||||
|
||||
bool isSel = (selectedTransitionIdx == static_cast<int>(i));
|
||||
if (ImGui::Selectable(label, isSel)) {
|
||||
selectedTransitionIdx = static_cast<int>(i);
|
||||
selectedSpriteIdx = -1;
|
||||
}
|
||||
if (ImGui::BeginPopupContextItem()) {
|
||||
if (ImGui::MenuItem("🎯 Сфокусировать", "F")) {
|
||||
selectedTransitionIdx = static_cast<int>(i);
|
||||
canvasPanX = 150.0f - (transitions[i].triggerCellX * canvasZoom);
|
||||
canvasPanY = 150.0f - (transitions[i].triggerCellY * canvasZoom);
|
||||
}
|
||||
ImGui::Separator();
|
||||
if (ImGui::MenuItem("🗑 Удалить портал", "Del")) {
|
||||
selectedTransitionIdx = static_cast<int>(i);
|
||||
DeleteSelectedEntity();
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
ImGui::TreePop();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2106,96 +2366,68 @@ void EditorApp::RenderInspector() {
|
|||
RenderCutsceneInspector();
|
||||
ImGui::Separator();
|
||||
}
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.2f, 1.0f), "ИНСПЕКТОР СВОЙСТВ");
|
||||
|
||||
ImGui::TextColored(ImVec4(0.2f, 0.8f, 1.0f, 1.0f), "ИНСПЕКТОР СВОЙСТВ (INSPECTOR)");
|
||||
ImGui::Separator();
|
||||
|
||||
if (ImGui::CollapsingHeader("Спавн игрока", ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||
ImGui::SliderInt("Spawn X", &playerStartX, 0, MAP_GRID_W - 1);
|
||||
ImGui::SliderInt("Spawn Y", &playerStartY, 0, MAP_GRID_H - 1);
|
||||
|
||||
float degrees = (playerStartAngle * 360.0f) / 512.0f;
|
||||
if (ImGui::SliderFloat("Угол обзора (°)", °rees, 0.0f, 360.0f, "%.0f°")) {
|
||||
playerStartAngle = static_cast<int>((degrees * 512.0f) / 360.0f) & 511;
|
||||
}
|
||||
|
||||
const char* dirName = "Восток (East)";
|
||||
if (playerStartAngle >= 64 && playerStartAngle < 192) dirName = "Юг (South)";
|
||||
else if (playerStartAngle >= 192 && playerStartAngle < 320) dirName = "Запад (West)";
|
||||
else if (playerStartAngle >= 320 && playerStartAngle < 448) dirName = "Север (North)";
|
||||
ImGui::Text("Направление: %s", dirName);
|
||||
}
|
||||
|
||||
if (ImGui::CollapsingHeader("Окружение сцены", ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||
// Floor & Ceiling Color Pickers
|
||||
float ceilCol[3] = {
|
||||
((ceilingColor >> 16) & 0xFF) / 255.0f,
|
||||
((ceilingColor >> 8) & 0xFF) / 255.0f,
|
||||
(ceilingColor & 0xFF) / 255.0f
|
||||
};
|
||||
if (ImGui::ColorEdit3("Цвет потолка", ceilCol)) {
|
||||
ceilingColor = (static_cast<uint32_t>(ceilCol[0] * 255.0f) << 16) |
|
||||
(static_cast<uint32_t>(ceilCol[1] * 255.0f) << 8) |
|
||||
static_cast<uint32_t>(ceilCol[2] * 255.0f);
|
||||
}
|
||||
|
||||
float floorCol[3] = {
|
||||
((floorColor >> 16) & 0xFF) / 255.0f,
|
||||
((floorColor >> 8) & 0xFF) / 255.0f,
|
||||
(floorColor & 0xFF) / 255.0f
|
||||
};
|
||||
if (ImGui::ColorEdit3("Цвет пола", floorCol)) {
|
||||
floorColor = (static_cast<uint32_t>(floorCol[0] * 255.0f) << 16) |
|
||||
(static_cast<uint32_t>(floorCol[1] * 255.0f) << 8) |
|
||||
static_cast<uint32_t>(floorCol[2] * 255.0f);
|
||||
}
|
||||
}
|
||||
|
||||
// Case 1: Sprite Entity Selected
|
||||
if (selectedSpriteIdx >= 0 && selectedSpriteIdx < static_cast<int>(sprites.size())) {
|
||||
if (ImGui::CollapsingHeader("Выбранный спрайт", ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||
auto& spr = sprites[selectedSpriteIdx];
|
||||
auto& spr = sprites[selectedSpriteIdx];
|
||||
uint8_t aType = GetSpriteEntityType(spr);
|
||||
const char* typeName = "Декорация (Проп)";
|
||||
if (aType == ENTITY_ARCHETYPE_GUARD) typeName = "Враг (Guard AI)";
|
||||
else if (aType == ENTITY_ARCHETYPE_MEDKIT) typeName = "Аптечка (+25 HP)";
|
||||
else if (aType == ENTITY_ARCHETYPE_AMMO) typeName = "Патроны (+20)";
|
||||
else if (aType == ENTITY_ARCHETYPE_KEY) typeName = "Ключ от двери";
|
||||
else if (aType == ENTITY_ARCHETYPE_SWITCH) typeName = "Рычаг [E]";
|
||||
else if (aType == ENTITY_ARCHETYPE_BARREL) typeName = "Взрывная бочка";
|
||||
|
||||
// Position (raw world units -> cell coords for UI)
|
||||
// Entity Header Card (Unity / Unreal style)
|
||||
ImGui::TextColored(ImVec4(0.4f, 0.9f, 1.0f, 1.0f), "Сущность #%d: %s", selectedSpriteIdx + 1, typeName);
|
||||
if (ImGui::SmallButton("📋 Дублировать (Ctrl+D)")) { DuplicateSelectedEntity(); }
|
||||
ImGui::SameLine();
|
||||
if (ImGui::SmallButton("🎯 Фокус (F)")) { FocusSelectedEntity(); }
|
||||
ImGui::SameLine();
|
||||
if (ImGui::SmallButton("🗑 Удалить (Del)")) { DeleteSelectedEntity(); return; }
|
||||
ImGui::Separator();
|
||||
|
||||
// Component 1: Transform
|
||||
if (ImGui::CollapsingHeader("📍 Transform (Координаты и масштаб)", ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||
float sx = spr.x / 64.0f;
|
||||
float sy = spr.y / 64.0f;
|
||||
if (ImGui::SliderFloat("Sprite X", &sx, 0.0f, MAP_GRID_W - 0.01f)) {
|
||||
if (ImGui::SliderFloat("Позиция X", &sx, 0.0f, MAP_GRID_W - 0.01f, "%.2f ячеек")) {
|
||||
spr.x = static_cast<int32_t>(sx * 64.0f);
|
||||
}
|
||||
if (ImGui::SliderFloat("Sprite Y", &sy, 0.0f, MAP_GRID_H - 0.01f)) {
|
||||
if (ImGui::SliderFloat("Позиция Y", &sy, 0.0f, MAP_GRID_H - 0.01f, "%.2f ячеек")) {
|
||||
spr.y = static_cast<int32_t>(sy * 64.0f);
|
||||
}
|
||||
ImGui::TextDisabled("Мировые единицы (x86 ASM): X=%d, Y=%d", spr.x, spr.y);
|
||||
|
||||
// Texture slot
|
||||
float scaleF = spr.scale / 65536.0f;
|
||||
if (ImGui::SliderFloat("Масштаб", &scaleF, 0.25f, 4.0f, "%.2fx")) {
|
||||
spr.scale = static_cast<int32_t>(scaleF * 65536.0f);
|
||||
}
|
||||
}
|
||||
|
||||
// Component 2: Sprite Renderer
|
||||
if (ImGui::CollapsingHeader("🖼 Sprite Renderer (Текстура)", ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||
int tid = static_cast<int>(spr.textureId & 15);
|
||||
if (ImGui::Combo("Текстура", &tid, g_TexSlotNames, 16)) {
|
||||
if (ImGui::Combo("Слот текстуры", &tid, g_TexSlotNames, 16)) {
|
||||
spr.textureId = static_cast<uint32_t>(tid);
|
||||
}
|
||||
|
||||
if (tid >= 0 && tid < 16 && textureSRVs[tid]) {
|
||||
ImGui::Text("Превью спрайта:");
|
||||
ImGui::Image(reinterpret_cast<ImTextureID>(textureSRVs[tid]), ImVec2(64, 64));
|
||||
ImGui::SameLine();
|
||||
ImGui::BeginGroup();
|
||||
if (ImGui::Button("Заменить PNG...")) {
|
||||
ImportTextureImage(tid, true);
|
||||
}
|
||||
if (ImGui::Button("Экспорт PNG")) {
|
||||
ExportTextureImage(tid);
|
||||
}
|
||||
if (ImGui::Button("Сброс")) {
|
||||
ResetTextureSlotToDefault(tid);
|
||||
}
|
||||
if (ImGui::Button("Заменить PNG...")) { ImportTextureImage(tid, true); }
|
||||
if (ImGui::Button("Экспорт PNG")) { ExportTextureImage(tid); }
|
||||
if (ImGui::Button("Сброс слота")) { ResetTextureSlotToDefault(tid); }
|
||||
ImGui::EndGroup();
|
||||
}
|
||||
}
|
||||
|
||||
// Scale (16.16 fixed -> float for UI)
|
||||
float scaleF = spr.scale / 65536.0f;
|
||||
if (ImGui::SliderFloat("Масштаб", &scaleF, 0.25f, 4.0f, "%.2f")) {
|
||||
spr.scale = static_cast<int32_t>(scaleF * 65536.0f);
|
||||
}
|
||||
|
||||
// Entity Archetype & Logic
|
||||
ImGui::Separator();
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.2f, 1.0f), "Геймплей / Архетип сущности:");
|
||||
// Component 3: Gameplay & AI Controller
|
||||
if (ImGui::CollapsingHeader("🧠 Gameplay & AI Controller", ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||
static const char* s_ArchetypeNames[7] = {
|
||||
"0: Декорация / Проп (Static)",
|
||||
"1: Враг (Enemy Guard AI)",
|
||||
|
|
@ -2210,7 +2442,7 @@ void EditorApp::RenderInspector() {
|
|||
int spd = static_cast<int>(GetSpriteSpeed(spr));
|
||||
int agg = static_cast<int>(GetSpriteAggro(spr));
|
||||
|
||||
if (ImGui::Combo("Тип сущности", &archType, s_ArchetypeNames, 7)) {
|
||||
if (ImGui::Combo("Архетип сущности", &archType, s_ArchetypeNames, 7)) {
|
||||
PushUndo();
|
||||
SetSpriteEntityData(spr, static_cast<uint8_t>(archType), static_cast<uint8_t>(hp), static_cast<uint8_t>(spd), static_cast<uint8_t>(agg));
|
||||
}
|
||||
|
|
@ -2219,39 +2451,85 @@ void EditorApp::RenderInspector() {
|
|||
if (ImGui::SliderInt("Здоровье (HP)", &hp, 10, 500)) {
|
||||
SetSpriteEntityData(spr, static_cast<uint8_t>(archType), static_cast<uint8_t>(hp), static_cast<uint8_t>(spd), static_cast<uint8_t>(agg));
|
||||
}
|
||||
if (ImGui::SliderInt("Скорость", &spd, 1, 8)) {
|
||||
if (ImGui::SliderInt("Скорость бега", &spd, 1, 8)) {
|
||||
SetSpriteEntityData(spr, static_cast<uint8_t>(archType), static_cast<uint8_t>(hp), static_cast<uint8_t>(spd), static_cast<uint8_t>(agg));
|
||||
}
|
||||
if (ImGui::SliderInt("Радиус агрессии (ячейки)", &agg, 1, 15)) {
|
||||
if (ImGui::SliderInt("Радиус агро (ячейки)", &agg, 1, 15)) {
|
||||
SetSpriteEntityData(spr, static_cast<uint8_t>(archType), static_cast<uint8_t>(hp), static_cast<uint8_t>(spd), static_cast<uint8_t>(agg));
|
||||
}
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.4f, 0.4f, 1.0f), "● Отображается красным кругом на 2D карте");
|
||||
} else if (archType == ENTITY_ARCHETYPE_MEDKIT) {
|
||||
ImGui::TextColored(ImVec4(0.2f, 1.0f, 0.4f, 1.0f), "Восстанавливает +25 HP игроку при подборе");
|
||||
ImGui::TextColored(ImVec4(0.2f, 1.0f, 0.4f, 1.0f), "● Восстанавливает +25 HP игроку при подборе");
|
||||
} else if (archType == ENTITY_ARCHETYPE_AMMO) {
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.9f, 0.2f, 1.0f), "Пополняет +20 патронов игроку при подборе");
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.9f, 0.2f, 1.0f), "● Пополняет +20 патронов игроку при подборе");
|
||||
} else if (archType == ENTITY_ARCHETYPE_KEY) {
|
||||
ImGui::TextColored(ImVec4(0.4f, 0.8f, 1.0f, 1.0f), "Ключ для открытия запертых дверей");
|
||||
ImGui::TextColored(ImVec4(0.4f, 0.8f, 1.0f, 1.0f), "● Ключ для открытия запертых дверей");
|
||||
} else if (archType == ENTITY_ARCHETYPE_SWITCH) {
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.5f, 0.2f, 1.0f), "Активируется нажатием клавиши [E] рядом с объектом");
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.5f, 0.2f, 1.0f), "● Активируется нажатием [E] рядом с объектом");
|
||||
} else if (archType == ENTITY_ARCHETYPE_BARREL) {
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.45f, 0.15f, 1.0f), "Взрывается от выстрелов, нанося 60 AOE урона всем вокруг!");
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.45f, 0.15f, 1.0f), "● Взрывается от пуль, нанося 60 AOE урона вокруг!");
|
||||
}
|
||||
|
||||
if (ImGui::Button("Редактировать логику в Редакторе кода (F7)")) {
|
||||
showCodeEditorModal = true;
|
||||
}
|
||||
ImGui::Separator();
|
||||
|
||||
if (ImGui::Button("Удалить спрайт")) {
|
||||
PushUndo();
|
||||
sprites.erase(sprites.begin() + selectedSpriteIdx);
|
||||
selectedSpriteIdx = -1;
|
||||
if (ImGui::Button("💻 Открыть логику в MASM32 IDE (F7)", ImVec2(-1, 28))) {
|
||||
viewportMode = 3;
|
||||
}
|
||||
}
|
||||
|
||||
// Component 4: Interaction & Lore Notes
|
||||
if (ImGui::CollapsingHeader("💬 Интерактивный диалог / Записка", ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||
ImGui::TextDisabled("При взаимодействии [E] вызывается ShowGameDialog:");
|
||||
static char szSpeaker[32] = "ПЕРСОНАЖ";
|
||||
static char szDialog[128] = "ПРИВЕТСТВУЮ В EBLAN ENGINE 3D!";
|
||||
ImGui::InputText("Говорящий", szSpeaker, sizeof(szSpeaker));
|
||||
ImGui::InputTextMultiline("Текст", szDialog, sizeof(szDialog), ImVec2(-1, 45));
|
||||
if (ImGui::Button("Вставить хук диалога в код (F7)")) {
|
||||
viewportMode = 3;
|
||||
}
|
||||
}
|
||||
|
||||
// Unity style [+ Сменить компонент...]
|
||||
ImGui::Spacing();
|
||||
ImGui::Separator();
|
||||
if (ImGui::Button("+ Добавить / Сменить компонент...", ImVec2(-1, 28))) {
|
||||
ImGui::OpenPopup("AddComponentPopup");
|
||||
}
|
||||
if (ImGui::BeginPopup("AddComponentPopup")) {
|
||||
if (ImGui::MenuItem("🧠 Guard AI Controller")) {
|
||||
PushUndo();
|
||||
SetSpriteEntityData(spr, ENTITY_ARCHETYPE_GUARD, 60, 2, 6);
|
||||
}
|
||||
if (ImGui::MenuItem("🩹 Medkit Heal Component")) {
|
||||
PushUndo();
|
||||
SetSpriteEntityData(spr, ENTITY_ARCHETYPE_MEDKIT, 100, 0, 0);
|
||||
}
|
||||
if (ImGui::MenuItem("📦 Ammo Pickup Component")) {
|
||||
PushUndo();
|
||||
SetSpriteEntityData(spr, ENTITY_ARCHETYPE_AMMO, 100, 0, 0);
|
||||
}
|
||||
if (ImGui::MenuItem("🔑 Keycard Door Component")) {
|
||||
PushUndo();
|
||||
SetSpriteEntityData(spr, ENTITY_ARCHETYPE_KEY, 100, 0, 0);
|
||||
}
|
||||
if (ImGui::MenuItem("🕹 Switch Trigger Component")) {
|
||||
PushUndo();
|
||||
SetSpriteEntityData(spr, ENTITY_ARCHETYPE_SWITCH, 100, 0, 0);
|
||||
}
|
||||
if (ImGui::MenuItem("💥 Explosive Barrel Component")) {
|
||||
PushUndo();
|
||||
SetSpriteEntityData(spr, ENTITY_ARCHETYPE_BARREL, 100, 0, 0);
|
||||
}
|
||||
if (ImGui::MenuItem("🏺 Static Prop Decorator")) {
|
||||
PushUndo();
|
||||
SetSpriteEntityData(spr, ENTITY_ARCHETYPE_PROP, 100, 0, 0);
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Case 2: Portal Selected
|
||||
if (selectedTransitionIdx >= 0 && selectedTransitionIdx < static_cast<int>(transitions.size())) {
|
||||
if (ImGui::CollapsingHeader("Свойства портала перехода", ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||
if (ImGui::CollapsingHeader("🌀 Портал перехода (Sublevel Portal)", ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||
auto& trans = transitions[selectedTransitionIdx];
|
||||
|
||||
ImGui::TextColored(ImVec4(0.7f, 0.4f, 1.0f, 1.0f), "Триггер-ячейка: (%d, %d)", trans.triggerCellX, trans.triggerCellY);
|
||||
|
|
@ -2314,6 +2592,71 @@ void EditorApp::RenderInspector() {
|
|||
selectedTransitionIdx = -1;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Case 3: Level / Player Spawn Selected (Default)
|
||||
if (ImGui::CollapsingHeader("👤 Спавн игрока (Player Transform)", ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||
ImGui::SliderInt("Spawn X", &playerStartX, 0, MAP_GRID_W - 1);
|
||||
ImGui::SliderInt("Spawn Y", &playerStartY, 0, MAP_GRID_H - 1);
|
||||
|
||||
float degrees = (playerStartAngle * 360.0f) / 512.0f;
|
||||
if (ImGui::SliderFloat("Угол обзора (°)", °rees, 0.0f, 360.0f, "%.0f°")) {
|
||||
playerStartAngle = static_cast<int>((degrees * 512.0f) / 360.0f) & 511;
|
||||
}
|
||||
|
||||
const char* dirName = "Восток (East)";
|
||||
if (playerStartAngle >= 64 && playerStartAngle < 192) dirName = "Юг (South)";
|
||||
else if (playerStartAngle >= 192 && playerStartAngle < 320) dirName = "Запад (West)";
|
||||
else if (playerStartAngle >= 320 && playerStartAngle < 448) dirName = "Север (North)";
|
||||
ImGui::Text("Направление: %s", dirName);
|
||||
if (ImGui::SmallButton("🎯 Сфокусировать камеру на игроке (F)")) {
|
||||
FocusSelectedEntity();
|
||||
}
|
||||
}
|
||||
|
||||
if (ImGui::CollapsingHeader("🌐 Окружение сцены (Environment)", ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||
// Floor & Ceiling Color Pickers
|
||||
float ceilCol[3] = {
|
||||
((ceilingColor >> 16) & 0xFF) / 255.0f,
|
||||
((ceilingColor >> 8) & 0xFF) / 255.0f,
|
||||
(ceilingColor & 0xFF) / 255.0f
|
||||
};
|
||||
if (ImGui::ColorEdit3("Цвет потолка", ceilCol)) {
|
||||
ceilingColor = (static_cast<uint32_t>(ceilCol[0] * 255.0f) << 16) |
|
||||
(static_cast<uint32_t>(ceilCol[1] * 255.0f) << 8) |
|
||||
static_cast<uint32_t>(ceilCol[2] * 255.0f);
|
||||
}
|
||||
|
||||
float floorCol[3] = {
|
||||
((floorColor >> 16) & 0xFF) / 255.0f,
|
||||
((floorColor >> 8) & 0xFF) / 255.0f,
|
||||
(floorColor & 0xFF) / 255.0f
|
||||
};
|
||||
if (ImGui::ColorEdit3("Цвет пола", floorCol)) {
|
||||
floorColor = (static_cast<uint32_t>(floorCol[0] * 255.0f) << 16) |
|
||||
(static_cast<uint32_t>(floorCol[1] * 255.0f) << 8) |
|
||||
static_cast<uint32_t>(floorCol[2] * 255.0f);
|
||||
}
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::TextColored(ImVec4(0.8f, 0.8f, 0.2f, 1.0f), "Быстрые пресеты освещения:");
|
||||
if (ImGui::SmallButton("Подземелье")) { ceilingColor = 0x00222222; floorColor = 0x00151515; }
|
||||
ImGui::SameLine();
|
||||
if (ImGui::SmallButton("Бункер")) { ceilingColor = 0x00383838; floorColor = 0x00242424; }
|
||||
ImGui::SameLine();
|
||||
if (ImGui::SmallButton("Неон")) { ceilingColor = 0x000F172A; floorColor = 0x00022C22; }
|
||||
ImGui::SameLine();
|
||||
if (ImGui::SmallButton("Хоррор")) { ceilingColor = 0x003B0710; floorColor = 0x001C1917; }
|
||||
}
|
||||
|
||||
if (ImGui::CollapsingHeader("⚙ Настройки проекта & Сборка", ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||
if (ImGui::Button("Настройки проекта (Жанр, Оружие)...", ImVec2(-1, 26))) {
|
||||
showProjectSettingsModal = true;
|
||||
}
|
||||
if (ImGui::Button("Экспорт автономной игры (.exe)...", ImVec2(-1, 26))) {
|
||||
showExportGameModal = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -67,10 +67,18 @@ public:
|
|||
void StopAudioSound();
|
||||
|
||||
void RenderAssetManagerModal();
|
||||
void RenderAssetManagerContents();
|
||||
void RenderCodeEditorWindow();
|
||||
void RenderCodeEditorContents(const std::string& root);
|
||||
void RenderProjectSettingsModal();
|
||||
void RenderExportGameModal();
|
||||
|
||||
void DuplicateSelectedEntity();
|
||||
void DeleteSelectedEntity();
|
||||
void FocusSelectedEntity();
|
||||
char searchHierarchyBuf[64] = "";
|
||||
int leftPanelTab = 0; // 0 = Hierarchy, 1 = Tile Palette, 2 = Project Assets
|
||||
|
||||
bool showAssetManagerModal = false;
|
||||
bool showCutsceneTimeline = false;
|
||||
bool showCodeEditorModal = false;
|
||||
|
|
|
|||
|
|
@ -449,6 +449,13 @@ void EditorApp::RenderCodeEditorWindow() {
|
|||
ImGui::EndMenuBar();
|
||||
}
|
||||
|
||||
RenderCodeEditorContents(root);
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
void EditorApp::RenderCodeEditorContents(const std::string& root) {
|
||||
g_CodeEditor.PollBuild();
|
||||
|
||||
// Top Toolbar
|
||||
ImGui::BeginGroup();
|
||||
if (ImGui::Button("Сохранить (Ctrl+S)")) {
|
||||
|
|
@ -661,5 +668,4 @@ void EditorApp::RenderCodeEditorWindow() {
|
|||
ImGui::EndTabBar();
|
||||
|
||||
ImGui::Columns(1);
|
||||
ImGui::End();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,51 +30,51 @@ void SetupGodotStyle() {
|
|||
ImGuiStyle& style = ImGui::GetStyle();
|
||||
ImVec4* colors = style.Colors;
|
||||
|
||||
style.WindowRounding = 6.0f;
|
||||
style.ChildRounding = 5.0f;
|
||||
style.FrameRounding = 4.0f;
|
||||
style.PopupRounding = 6.0f;
|
||||
style.ScrollbarRounding = 6.0f;
|
||||
style.GrabRounding = 4.0f;
|
||||
style.TabRounding = 5.0f;
|
||||
style.WindowRounding = 8.0f;
|
||||
style.ChildRounding = 6.0f;
|
||||
style.FrameRounding = 5.0f;
|
||||
style.PopupRounding = 8.0f;
|
||||
style.ScrollbarRounding = 7.0f;
|
||||
style.GrabRounding = 5.0f;
|
||||
style.TabRounding = 6.0f;
|
||||
style.FrameBorderSize = 1.0f;
|
||||
style.WindowBorderSize = 1.0f;
|
||||
style.WindowPadding = ImVec2(10.0f, 10.0f);
|
||||
style.FramePadding = ImVec2(7.0f, 4.0f);
|
||||
style.FramePadding = ImVec2(8.0f, 5.0f);
|
||||
style.ItemSpacing = ImVec2(8.0f, 6.0f);
|
||||
style.ItemInnerSpacing = ImVec2(6.0f, 4.0f);
|
||||
|
||||
// Modern Game Studio Dark Slate Palette with Electric Cyan & Neon Amber accents
|
||||
colors[ImGuiCol_Text] = ImVec4(0.95f, 0.96f, 0.98f, 1.00f);
|
||||
colors[ImGuiCol_TextDisabled] = ImVec4(0.52f, 0.55f, 0.60f, 1.00f);
|
||||
// Modern Game Studio Dark Palette (Unreal / Unity / Godot Pro)
|
||||
colors[ImGuiCol_Text] = ImVec4(0.96f, 0.97f, 0.99f, 1.00f);
|
||||
colors[ImGuiCol_TextDisabled] = ImVec4(0.50f, 0.54f, 0.60f, 1.00f);
|
||||
colors[ImGuiCol_WindowBg] = ImVec4(0.11f, 0.12f, 0.14f, 1.00f);
|
||||
colors[ImGuiCol_ChildBg] = ImVec4(0.13f, 0.14f, 0.17f, 1.00f);
|
||||
colors[ImGuiCol_PopupBg] = ImVec4(0.14f, 0.15f, 0.18f, 0.98f);
|
||||
colors[ImGuiCol_Border] = ImVec4(0.22f, 0.24f, 0.28f, 1.00f);
|
||||
colors[ImGuiCol_ChildBg] = ImVec4(0.14f, 0.15f, 0.18f, 1.00f);
|
||||
colors[ImGuiCol_PopupBg] = ImVec4(0.14f, 0.16f, 0.20f, 0.98f);
|
||||
colors[ImGuiCol_Border] = ImVec4(0.24f, 0.26f, 0.31f, 1.00f);
|
||||
colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
|
||||
colors[ImGuiCol_FrameBg] = ImVec4(0.18f, 0.20f, 0.24f, 1.00f);
|
||||
colors[ImGuiCol_FrameBgHovered] = ImVec4(0.24f, 0.28f, 0.34f, 1.00f);
|
||||
colors[ImGuiCol_FrameBgActive] = ImVec4(0.28f, 0.33f, 0.40f, 1.00f);
|
||||
colors[ImGuiCol_FrameBg] = ImVec4(0.18f, 0.20f, 0.25f, 1.00f);
|
||||
colors[ImGuiCol_FrameBgHovered] = ImVec4(0.25f, 0.29f, 0.36f, 1.00f);
|
||||
colors[ImGuiCol_FrameBgActive] = ImVec4(0.30f, 0.36f, 0.45f, 1.00f);
|
||||
colors[ImGuiCol_TitleBg] = ImVec4(0.09f, 0.10f, 0.12f, 1.00f);
|
||||
colors[ImGuiCol_TitleBgActive] = ImVec4(0.14f, 0.16f, 0.20f, 1.00f);
|
||||
colors[ImGuiCol_TitleBgActive] = ImVec4(0.13f, 0.15f, 0.19f, 1.00f);
|
||||
colors[ImGuiCol_MenuBarBg] = ImVec4(0.10f, 0.11f, 0.13f, 1.00f);
|
||||
colors[ImGuiCol_ScrollbarBg] = ImVec4(0.09f, 0.10f, 0.12f, 0.80f);
|
||||
colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.24f, 0.27f, 0.33f, 1.00f);
|
||||
colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.32f, 0.37f, 0.45f, 1.00f);
|
||||
colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.40f, 0.46f, 0.56f, 1.00f);
|
||||
colors[ImGuiCol_CheckMark] = ImVec4(0.20f, 0.75f, 1.00f, 1.00f);
|
||||
colors[ImGuiCol_SliderGrab] = ImVec4(0.20f, 0.70f, 0.98f, 1.00f);
|
||||
colors[ImGuiCol_SliderGrabActive] = ImVec4(0.35f, 0.82f, 1.00f, 1.00f);
|
||||
colors[ImGuiCol_Button] = ImVec4(0.20f, 0.23f, 0.28f, 1.00f);
|
||||
colors[ImGuiCol_ButtonHovered] = ImVec4(0.28f, 0.34f, 0.42f, 1.00f);
|
||||
colors[ImGuiCol_ButtonActive] = ImVec4(0.34f, 0.42f, 0.52f, 1.00f);
|
||||
colors[ImGuiCol_Header] = ImVec4(0.20f, 0.24f, 0.30f, 1.00f);
|
||||
colors[ImGuiCol_HeaderHovered] = ImVec4(0.28f, 0.34f, 0.42f, 1.00f);
|
||||
colors[ImGuiCol_HeaderActive] = ImVec4(0.34f, 0.42f, 0.52f, 1.00f);
|
||||
colors[ImGuiCol_Separator] = ImVec4(0.20f, 0.22f, 0.26f, 1.00f);
|
||||
colors[ImGuiCol_Tab] = ImVec4(0.14f, 0.16f, 0.19f, 1.00f);
|
||||
colors[ImGuiCol_TabHovered] = ImVec4(0.28f, 0.33f, 0.40f, 1.00f);
|
||||
colors[ImGuiCol_TabActive] = ImVec4(0.22f, 0.26f, 0.33f, 1.00f);
|
||||
colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.25f, 0.28f, 0.35f, 1.00f);
|
||||
colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.34f, 0.39f, 0.48f, 1.00f);
|
||||
colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.42f, 0.48f, 0.60f, 1.00f);
|
||||
colors[ImGuiCol_CheckMark] = ImVec4(0.05f, 0.65f, 0.95f, 1.00f);
|
||||
colors[ImGuiCol_SliderGrab] = ImVec4(0.05f, 0.60f, 0.92f, 1.00f);
|
||||
colors[ImGuiCol_SliderGrabActive] = ImVec4(0.20f, 0.75f, 1.00f, 1.00f);
|
||||
colors[ImGuiCol_Button] = ImVec4(0.20f, 0.23f, 0.29f, 1.00f);
|
||||
colors[ImGuiCol_ButtonHovered] = ImVec4(0.28f, 0.34f, 0.43f, 1.00f);
|
||||
colors[ImGuiCol_ButtonActive] = ImVec4(0.35f, 0.43f, 0.55f, 1.00f);
|
||||
colors[ImGuiCol_Header] = ImVec4(0.20f, 0.25f, 0.32f, 1.00f);
|
||||
colors[ImGuiCol_HeaderHovered] = ImVec4(0.28f, 0.35f, 0.45f, 1.00f);
|
||||
colors[ImGuiCol_HeaderActive] = ImVec4(0.35f, 0.44f, 0.56f, 1.00f);
|
||||
colors[ImGuiCol_Separator] = ImVec4(0.22f, 0.24f, 0.29f, 1.00f);
|
||||
colors[ImGuiCol_Tab] = ImVec4(0.14f, 0.16f, 0.20f, 1.00f);
|
||||
colors[ImGuiCol_TabHovered] = ImVec4(0.28f, 0.34f, 0.42f, 1.00f);
|
||||
colors[ImGuiCol_TabActive] = ImVec4(0.22f, 0.27f, 0.35f, 1.00f);
|
||||
}
|
||||
|
||||
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue