π¬ Scene Editor
Fase: 6 β O Olimpo
Namespace: Caffeine::Editor
Status: π
Planejado
RFs: RF6.3, RF6.4
VisΓ£o Geral
O Scene Editor Γ© o coraΓ§Γ£o do Caffeine Studio IDE. Permite criar, inspecionar e modificar entidades ECS visualmente, com suporte a drag-and-drop de assets, hierarquia de entidades, painel de inspeΓ§Γ£o de componentes e gizmos de transformaΓ§Γ£o 2D/3D.
O editor nΓ£o substitui o cΓ³digo C++ β ele Γ© uma ferramenta de produtividade que lΓͺ e escreve os mesmos dados ECS usados em runtime.
Componentes do editor:
| Painel |
Responsabilidade |
HierarchyPanel |
Γrvore de todas as entidades da cena |
InspectorPanel |
EdiΓ§Γ£o de componentes da entidade selecionada |
SceneViewport |
Viewport com gizmos de transformaΓ§Γ£o |
AssetBrowser |
Browser de assets do projeto com drag-and-drop |
SceneEditor |
Orquestrador dos 4 painΓ©is acima |
API Planejada
namespace Caffeine::Editor {
// ============================================================================
// @brief Estado global de seleΓ§Γ£o no editor.
// ============================================================================
struct EditorContext {
ECS::Entity selectedEntity = ECS::INVALID_ENTITY;
ECS::Entity hoveredEntity = ECS::INVALID_ENTITY;
bool isDirty = false; // cena modificada sem salvar
enum class GizmoMode { Translate, Rotate, Scale } gizmoMode = GizmoMode::Translate;
enum class GizmoSpace { Local, World } gizmoSpace = GizmoSpace::World;
};
// ============================================================================
// @brief Painel de hierarquia β exibe todas as entidades da cena em Γ‘rvore.
//
// Suporta:
// - Hierarquia Parent/Child via componente Caffeine::Scene::Parent
// - Renomear entidades (componente NameComponent)
// - Criar entidade vazia (botΓ£o +)
// - Deletar entidade selecionada (tecla Delete)
// - Drag-and-drop para reorganizar hierarquia
// ============================================================================
class HierarchyPanel {
public:
void render(ECS::World& world, EditorContext& ctx);
private:
void renderEntityNode(ECS::World& world, ECS::Entity entity,
EditorContext& ctx);
void renderContextMenu(ECS::World& world, ECS::Entity entity,
EditorContext& ctx);
};
// ============================================================================
// @brief Painel de inspeΓ§Γ£o β edita componentes da entidade selecionada.
//
// Cada tipo de componente tem um "drawer" registrado que sabe como
// renderizar campos editΓ‘veis via ImGui.
//
// Suporta:
// - Adicionar/remover componentes
// - EdiΓ§Γ£o inline de f32, Vec2, Vec3, bool, strings
// - Reset de campos ao valor padrΓ£o
// ============================================================================
class InspectorPanel {
public:
void render(ECS::World& world, EditorContext& ctx);
// Registro de drawers para tipos de componentes customizados
using ComponentDrawer = std::function<void(void* componentData)>;
void registerDrawer(ECS::ComponentID id, ComponentDrawer drawer);
private:
HashMap<ECS::ComponentID, ComponentDrawer> m_drawers;
// Drawers built-in para componentes da Caffeine:
void drawTransform(Scene::Transform& t);
void drawSprite(Render::SpriteRenderer& s);
void drawRigidBody2D(Physics2D::RigidBody& rb);
void drawAudioSource(Audio::AudioSource& as);
void drawCamera(Render::Camera2D& cam);
};
// ============================================================================
// @brief Viewport com gizmos de transformaΓ§Γ£o.
//
// Renderiza a cena em um framebuffer separado (offscreen) e exibe
// dentro de uma janela ImGui. SobrepΓ΅e gizmos 2D/3D sobre o framebuffer.
//
// Gizmos (RF6.4):
// - Translate: arrastar eixos X/Y/Z
// - Rotate: girar em torno de eixo
// - Scale: escalar por eixo ou uniforme
//
// Teclas de atalho (quando viewport em foco):
// - W = Translate, E = Rotate, R = Scale
// - Q = sem gizmo (seleΓ§Γ£o apenas)
// ============================================================================
class SceneViewport {
public:
struct Config {
u32 width = 1280;
u32 height = 720;
bool grid = true; // mostrar grade 2D
f32 gridSpacing = 64.0f; // pixels por cΓ©lula de grade
};
bool init(RHI::RenderDevice* device, Config cfg = {});
void shutdown();
// Renderiza cena no offscreen buffer e exibe como imagem ImGui
void render(ECS::World& world, EditorContext& ctx,
const Render::Camera2D& editorCamera);
// Retorna true se o mouse estΓ‘ sobre o viewport (nΓ£o sobre painΓ©is)
bool isHovered() const { return m_hovered; }
private:
RHI::TextureHandle m_framebuffer;
Config m_config;
bool m_hovered = false;
void renderGrid(RHI::CommandBuffer* cmd);
void renderGizmos(ECS::World& world, EditorContext& ctx);
void renderTranslateGizmo(const Scene::Transform& t, EditorContext& ctx);
void renderRotateGizmo(const Scene::Transform& t, EditorContext& ctx);
void renderScaleGizmo(const Scene::Transform& t, EditorContext& ctx);
void handleGizmoInput(ECS::World& world, EditorContext& ctx);
};
// ============================================================================
// @brief Browser de assets do projeto com drag-and-drop.
//
// Exibe os assets em `/assets/` com thumbnails e permite:
// - Navegar por pastas
// - Arrastar assets para o SceneViewport β cria entidade
// - Preview de textura ao hover
// - Renomear / deletar assets (operaΓ§Γ΅es no sistema de arquivos)
// ============================================================================
class AssetBrowser {
public:
struct Entry {
std::filesystem::path path;
Assets::AssetType type; // Texture, Audio, Mesh, etc.
RHI::TextureHandle thumbnail; // preview da textura
};
void init(Assets::AssetManager* assetManager, std::string_view rootPath);
void render(EditorContext& ctx);
// Drag payload: retorna o path do asset sendo arrastado, ou {} se nenhum.
std::optional<std::filesystem::path> getDroppedAsset() const;
private:
std::vector<Entry> m_entries;
std::filesystem::path m_currentDir;
Assets::AssetManager* m_assetManager = nullptr;
std::optional<std::filesystem::path> m_droppedAsset;
void refreshDirectory();
void renderEntry(const Entry& entry);
};
// ============================================================================
// @brief Orquestrador de todos os painΓ©is do editor.
//
// Layout padrΓ£o (docking ImGui):
//
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// β [Menu Bar] File | Edit | View | Build | Run β
// ββββββββββ¬ββββββββββββββββββββββββββββ¬βββββββββββββββββββββ€
// βHierarchβ β Inspector β
// β Panel β Scene Viewport β Panel β
// β β (offscreen render) β β
// β β [W][E][R] gizmos β β Transform β β
// β β β β Sprite β β
// ββββββββββ΄ββββββββββββββββββββββββββββ΄βββββββββββββββββββββ€
// β Asset Browser | Console | Profiler β
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// ============================================================================
class SceneEditor {
public:
bool init(RHI::RenderDevice* device, Assets::AssetManager* assetManager,
std::string_view assetsPath);
void shutdown();
// Chame no loop principal entre imgui.beginFrame() e imgui.endFrame()
void render(ECS::World& world, Render::Camera2D& editorCamera);
// SerializaΓ§Γ£o da cena
bool saveScene(std::string_view path);
bool loadScene(std::string_view path, ECS::World& world);
private:
EditorContext m_ctx;
HierarchyPanel m_hierarchy;
InspectorPanel m_inspector;
SceneViewport m_viewport;
AssetBrowser m_assetBrowser;
bool m_dockingSetup = false;
void renderMenuBar(ECS::World& world);
void setupDockingLayout();
// Cria entidade ao soltar asset no viewport
void handleAssetDrop(ECS::World& world, const std::filesystem::path& asset);
};
} // namespace Caffeine::Editor
Layout Visual do Editor
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Caffeine Studio β Scene: "Level01.caf" β unsaved β
β File | Edit | View | Build | Run β
ββββββββββββββ¬βββββββββββββββββββββββββββββββββββ¬ββββββββββββββββββββββ€
β Hierarchy β β Inspector β
β β Scene Viewport β β
β βΈ Root β β Entity: "Hero" β
β ββ Hero β +ββββββββββ+ β ββββββββββββββββββ β
β ββ Enemy β β Sprite β β βΎ Transform β
β ββ Floor β β (Gizmo) β β Pos: [120, 340] β
β β β ββ β β Rot: 45.0Β° β
β β +ββββββββββ+ β Scl: [1.0, 1.0] β
β β β βΎ Sprite Renderer β
β β Grid: ON Translate [W] β Tex: hero.caf β
β β β βΎ RigidBody2D β
β β β Mass: 1.0 β
ββββββββββββββ΄βββββββββββββββββββββββββββββββββββ΄ββββββββββββββββββββββ€
β Assets /sprites/ β Console β Profiler β
β π hero.caf π floor.caf β [INFO]... β Frame: 16.7ms / 60fps β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ-ββ
IntegraΓ§Γ£o com ECS e SceneSerializer
// Salvar cena via menu File > Save:
void SceneEditor::saveScene(std::string_view path) {
Scene::SceneSerializer serializer;
serializer.serialize(m_world, path); // β .caf binΓ‘rio (ver fase4/scene.md)
m_ctx.isDirty = false;
}
// Carregar cena ao abrir arquivo:
void SceneEditor::loadScene(std::string_view path, ECS::World& world) {
world.clear(); // destrΓ³i todas as entidades atuais
Scene::SceneSerializer serializer;
serializer.deserialize(world, path); // β popula o ECS
m_ctx.selectedEntity = ECS::INVALID_ENTITY;
}
// Criar entidade ao soltar texture no viewport:
void SceneEditor::handleAssetDrop(ECS::World& world,
const std::filesystem::path& asset) {
auto entity = world.createEntity();
world.add<Scene::NameComponent>(entity, { asset.stem().string() });
world.add<Scene::Transform>(entity, {}); // posiΓ§Γ£o zero
world.add<Render::SpriteRenderer>(entity, {
.texture = m_assetManager->load<Render::Texture>(asset.string())
});
m_ctx.selectedEntity = entity;
m_ctx.isDirty = true;
}
Gizmos de TransformaΓ§Γ£o (RF6.4)
Translate Gizmo (W): Rotate Gizmo (E): Scale Gizmo (R):
β² Y β― β Y
β β± β² β
β β β β ββββββββ X
ββββββΊ X β² β± β
β± β² β± β±
β± Z (3D only) β²β± β± Z (3D only)
- Arrastar eixo X: move apenas em X
- Arrastar eixo Y: move apenas em Y
- Arrastar plano XY (centro): move livremente em 2D
- Shift+drag: snap de 16px (configurΓ‘vel)
Registo de Drawers Customizados
// Registar um drawer para um componente de jogo personalizado:
struct HealthComponent {
f32 current;
f32 maximum;
};
// No init do jogo:
editor.getInspector().registerDrawer(
ECS::componentID<HealthComponent>(),
[](void* data) {
auto* health = static_cast<HealthComponent*>(data);
ImGui::SliderFloat("HP", &health->current, 0.0f, health->maximum);
ImGui::SliderFloat("Max HP", &health->maximum, 1.0f, 9999.0f);
}
);
CritΓ©rio de AceitaΓ§Γ£o
DependΓͺncias
ReferΓͺncias
π¬ Scene Editor
VisΓ£o Geral
O Scene Editor Γ© o coraΓ§Γ£o do Caffeine Studio IDE. Permite criar, inspecionar e modificar entidades ECS visualmente, com suporte a drag-and-drop de assets, hierarquia de entidades, painel de inspeΓ§Γ£o de componentes e gizmos de transformaΓ§Γ£o 2D/3D.
O editor nΓ£o substitui o cΓ³digo C++ β ele Γ© uma ferramenta de produtividade que lΓͺ e escreve os mesmos dados ECS usados em runtime.
Componentes do editor:
HierarchyPanelInspectorPanelSceneViewportAssetBrowserSceneEditorAPI Planejada
Layout Visual do Editor
IntegraΓ§Γ£o com ECS e SceneSerializer
Gizmos de TransformaΓ§Γ£o (RF6.4)
Registo de Drawers Customizados
CritΓ©rio de AceitaΓ§Γ£o
.cafem/assets/com thumbnails.caf(round-trip com SceneSerializer)DependΓͺncias
World,Entity, componentesSceneSerializerImGuiIntegrationReferΓͺncias
docs/fase4/ecs.mdβ ECS World e componentesdocs/fase4/scene.mdβ SceneSerializerdocs/fase6/embedded-ui.mdβ ImGuiIntegrationdocs/fase3/rhi.mdβ RenderDevicedocs/MASTER.mdβ DocumentaΓ§Γ£o unificada