2021SC@SDUSC
开源游戏引擎Overload代码分析十三:OvEditor——END(下)
前言
这是Overload引擎相关的第十四篇文章,同时也是OvEditor分析的第九篇。Overload引擎的Github主页在这里。
本篇文章将会介绍OvEditor的Panels文件夹中剩余的文件,具体应该会涉及ProjectSettings,SceneView和Toolbar。
一、ProjectSettings
1.ProjectSettings.h
class ProjectSettings : public OvUI::Panels::PanelWindow
{
public:
/**
* Constructor
* @param p_title
* @param p_opened
* @param p_windowSettings
*/
ProjectSettings
(
const std::string& p_title,
bool p_opened,
const OvUI::Settings::PanelWindowSettings& p_windowSettings
);
/**
* Generate a gatherer that will get the value associated to the given key
* @param p_keyName
*/
template <typename T>
std::function<T()> GenerateGatherer(const std::string& p_keyName)
{
return std::bind(&OvTools::Filesystem::IniFile::Get<T>, &m_projectFile, p_keyName);
}
/**
* Generate a provider that will set the value associated to the given key
* @param p_keyName
*/
template <typename T>
std::function<void(T)> GenerateProvider(const std::string& p_keyName)
{
return std::bind(&OvTools::Filesystem::IniFile::Set<T>, &m_projectFile, p_keyName, std::placeholders::_1);
}
private:
OvTools::Filesystem::IniFile& m_projectFile;
};
定义了一个类,和项目的设置相关,具体各项函数的说明在下面讲。
2.ProjectSettings.cpp
OvEditor::Panels::ProjectSettings::ProjectSettings(const std::string & p_title, bool p_opened, const OvUI::Settings::PanelWindowSettings & p_windowSettings) :
PanelWindow(p_title, p_opened, p_windowSettings),
m_projectFile(EDITOR_CONTEXT(projectSettings))
{
auto& saveButton = CreateWidget<Buttons::Button>("Apply");
saveButton.idleBackgroundColor = { 0.0f, 0.5f, 0.0f };
saveButton.ClickedEvent += [this]
{
EDITOR_CONTEXT(ApplyProjectSettings());
m_projectFile.Rewrite();
};
saveButton.lineBreak = false;
auto& resetButton = CreateWidget<Buttons::Button>("Reset");
resetButton.idleBackgroundColor = { 0.5f, 0.0f, 0.0f };
resetButton.ClickedEvent += [this]
{
EDITOR_CONTEXT(ResetProjectSettings());
};
CreateWidget<OvUI::Widgets::Visual::Separator>();
{
/* Physics settings */
auto& root = CreateWidget<Layout::GroupCollapsable>("Physics");
auto& columns = root.CreateWidget<Layout::Columns<2>>();
columns.widths[0] = 125;
GUIDrawer::DrawScalar<float>(columns, "Gravity", GenerateGatherer<float>("gravity"), GenerateProvider<float>("gravity"), 0.1f, GUIDrawer::_MIN_FLOAT, GUIDrawer::_MAX_FLOAT);
}
{
/* Build settings */
auto& generationRoot = CreateWidget<Layout::GroupCollapsable>("Build");
auto& columns = generationRoot.CreateWidget<Layout::Columns<2>>();
columns.widths[0] = 125;
GUIDrawer::DrawBoolean(columns, "Development build", GenerateGatherer<bool>("dev_build"), GenerateProvider<bool>("dev_build"));
}
{
/* Windowing settings */
auto& windowingRoot = CreateWidget<Layout::GroupCollapsable>("Windowing");
auto& columns = windowingRoot.CreateWidget<Layout::Columns<2>>();
columns.widths[0] = 125;
GUIDrawer::DrawScalar<int>(columns, "Resolution X", GenerateGatherer<int>("x_resolution"), GenerateProvider<int>("x_resolution"), 1, 0, 10000);
GUIDrawer::DrawScalar<int>(columns, "Resolution Y", GenerateGatherer<int>("y_resolution"), GenerateProvider<int>("y_resolution"), 1, 0, 10000);
GUIDrawer::DrawBoolean(columns, "Fullscreen", GenerateGatherer<bool>("fullscreen"), GenerateProvider<bool>("fullscreen"));
GUIDrawer::DrawString(columns, "Executable name", GenerateGatherer<std::string>("executable_name"), GenerateProvider<std::string>("executable_name"));
}
{
/* Rendering settings */
auto& renderingRoot = CreateWidget<Layout::GroupCollapsable>("Rendering");
auto& columns = renderingRoot.CreateWidget<Layout::Columns<2>>();
columns.widths[0] = 125;
GUIDrawer::DrawBoolean(columns, "Vertical Sync.", GenerateGatherer<bool>("vsync"), GenerateProvider<bool>("vsync"));
GUIDrawer::DrawBoolean(columns, "Multi-sampling", GenerateGatherer<bool>("multisampling"), GenerateProvider<bool>("multisampling"));
GUIDrawer::DrawScalar<int>(columns, "Samples", GenerateGatherer<int>("samples"), GenerateProvider<int>("samples"), 1, 2, 16);
GUIDrawer::DrawScalar<int>(columns, "OpenGL Major", GenerateGatherer<int>("opengl_major"), GenerateProvider<int>("opengl_major"), 1, 3, 4);
GUIDrawer::DrawScalar<int>(columns, "OpenGL Minor", GenerateGatherer<int>("opengl_minor"), GenerateProvider<int>("opengl_minor"), 1, 0, 6);
}
{
/* Scene Management settings */
auto& gameRoot = CreateWidget<Layout::GroupCollapsable>("Scene Management");
auto& columns = gameRoot.CreateWidget<Layout::Columns<2>>();
columns.widths[0] = 125;
GUIDrawer::DrawDDString(columns, "Start scene", GenerateGatherer<std::string>("start_scene"), GenerateProvider<std::string>("start_scene"), "File");
}
}
首先是构造函数,除了基础的单纯赋值外,创建了一个保存按钮,文本显示为Apply,背景色为绿色,被点击时会应用更改的项目设定并且重写项目的文件;同时设置了红色的Reset按钮,在点击时会重置项目的设置。
在之后创建了多个分列,包括Physics,Build,Windowing,Rendering和Scene Management。各个分列内部都有其特有的设定可以更改,具体实现并不复杂,有条理且清晰。
二、SceneView
1.SceneView.h
class SceneView : public OvEditor::Panels::AViewControllable
{
public:
/**
* Constructor
* @param p_title
* @param p_opened
* @param p_windowSettings
*/
SceneView
(
const std::string& p_title,
bool p_opened,
const OvUI::Settings::PanelWindowSettings& p_windowSettings
);
/**
* Update the scene view
*/
virtual void Update(float p_deltaTime) override;
/**
* Custom implementation of the render method
*/
virtual void _Render_Impl() override;
/**
* Render the actual scene
* @param p_defaultRenderState
*/
void RenderScene(uint8_t p_defaultRenderState);
/**
* Render the scene for actor picking (Using unlit colors)
*/
void RenderSceneForActorPicking();
/**
* Render the scene for actor picking and handle the logic behind it
*/
void HandleActorPicking();
private:
OvCore::SceneSystem::SceneManager& m_sceneManager;
OvRendering::Buffers::Framebuffer m_actorPickingFramebuffer;
OvEditor::Core::GizmoBehaviour m_gizmoOperations;
OvEditor::Core::EGizmoOperation m_currentOperation = OvEditor::Core::EGizmoOperation::TRANSLATE;
std::optional<std::reference_wrapper<OvCore::ECS::Actor>> m_highlightedActor;
std::optional<OvEditor::Core::GizmoBehaviour::EDirection> m_highlightedGizmoDirection;
};
这里定义了场景视窗的类,具体各个函数的实现在下面。
2.SceneView.cpp
OvEditor::Panels::SceneView::SceneView
(
const std::string& p_title,
bool p_opened,
const OvUI::Settings::PanelWindowSettings& p_windowSettings
) : AViewControllable(p_title, p_opened, p_windowSettings, true),
m_sceneManager(EDITOR_CONTEXT(sceneManager))
{
m_camera.SetClearColor({ 0.098f, 0.098f, 0.098f });
m_camera.SetFar(5000.0f);
m_image->AddPlugin<OvUI::Plugins::DDTarget<std::pair<std::string, OvUI::Widgets::Layout::Group*>>>("File").DataReceivedEvent += [this](auto p_data)
{
std::string path = p_data.first;
switch (OvTools::Utils::PathParser::GetFileType(path))
{
case OvTools::Utils::PathParser::EFileType::SCENE: EDITOR_EXEC(LoadSceneFromDisk(path)); break;
case OvTools::Utils::PathParser::EFileType::MODEL: EDITOR_EXEC(CreateActorWithModel(path, true)); break;
}
};
}
void OvEditor::Panels::SceneView::Update(float p_deltaTime)
{
AViewControllable::Update(p_deltaTime);
using namespace OvWindowing::Inputs;
if (IsFocused() && !m_cameraController.IsRightMousePressed())
{
if (EDITOR_CONTEXT(inputManager)->IsKeyPressed(EKey::KEY_W))
{
m_currentOperation = OvEditor::Core::EGizmoOperation::TRANSLATE;
}
if (EDITOR_CONTEXT(inputManager)->IsKeyPressed(EKey::KEY_E))
{
m_currentOperation = OvEditor::Core::EGizmoOperation::ROTATE;
}
if (EDITOR_CONTEXT(inputManager)->IsKeyPressed(EKey::KEY_R))
{
m_currentOperation = OvEditor::Core::EGizmoOperation::SCALE;
}
}
}
void OvEditor::Panels::SceneView::_Render_Impl()
{
PrepareCamera();
auto& baseRenderer = *EDITOR_CONTEXT(renderer).get();
uint8_t glState = baseRenderer.FetchGLState();
baseRenderer.ApplyStateMask(glState);
HandleActorPicking();
baseRenderer.ApplyStateMask(glState);
RenderScene(glState);
baseRenderer.ApplyStateMask(glState);
}
void OvEditor::Panels::SceneView::RenderScene(uint8_t p_defaultRenderState)
{
auto& baseRenderer = *EDITOR_CONTEXT(renderer).get();
auto& currentScene = *m_sceneManager.GetCurrentScene();
auto& gameView = EDITOR_PANEL(OvEditor::Panels::GameView, "Game View");
// If the game is playing, and ShowLightFrustumCullingInSceneView is true, apply the game view frustum culling to the scene view (For debugging purposes)
if (auto gameViewFrustum = gameView.GetActiveFrustum(); gameViewFrustum.has_value() && gameView.GetCamera().HasFrustumLightCulling() && Settings::EditorSettings::ShowLightFrustumCullingInSceneView)
{
m_editorRenderer.UpdateLightsInFrustum(currentScene, gameViewFrustum.value());
}
else
{
m_editorRenderer.UpdateLights(currentScene);
}
m_fbo.Bind();
baseRenderer.SetStencilMask(0xFF);
baseRenderer.Clear(m_camera);
baseRenderer.SetStencilMask(0x00);
m_editorRenderer.RenderGrid(m_cameraPosition, m_gridColor);
m_editorRenderer.RenderCameras();
// If the game is playing, and ShowGeometryFrustumCullingInSceneView is true, apply the game view frustum culling to the scene view (For debugging purposes)
if (auto gameViewFrustum = gameView.GetActiveFrustum(); gameViewFrustum.has_value() && gameView.GetCamera().HasFrustumGeometryCulling() && Settings::EditorSettings::ShowGeometryFrustumCullingInSceneView)
{
m_camera.SetFrustumGeometryCulling(gameView.HasCamera() ? gameView.GetCamera().HasFrustumGeometryCulling() : false);
m_editorRenderer.RenderScene(m_cameraPosition, m_camera, &gameViewFrustum.value());
m_camera.SetFrustumGeometryCulling(false);
}
else
{
m_editorRenderer.RenderScene(m_cameraPosition, m_camera);
}
m_editorRenderer.RenderLights();
if (EDITOR_EXEC(IsAnyActorSelected()))
{
auto& selectedActor = EDITOR_EXEC(GetSelectedActor());
if (selectedActor.IsActive())
{
m_editorRenderer.RenderActorOutlinePass(selectedActor, true, true);
baseRenderer.ApplyStateMask(p_defaultRenderState);
m_editorRenderer.RenderActorOutlinePass(selectedActor, false, true);
}
baseRenderer.ApplyStateMask(p_defaultRenderState);
baseRenderer.Clear(false, true, false);
int highlightedAxis = -1;
if (m_highlightedGizmoDirection.has_value())
{
highlightedAxis = static_cast<int>(m_highlightedGizmoDirection.value());
}
m_editorRenderer.RenderGizmo(selectedActor.transform.GetWorldPosition(), selectedActor.transform.GetWorldRotation(), m_currentOperation, false, highlightedAxis);
}
if (m_highlightedActor.has_value())
{
m_editorRenderer.RenderActorOutlinePass(m_highlightedActor.value().get(), true, false);
baseRenderer.ApplyStateMask(p_defaultRenderState);
m_editorRenderer.RenderActorOutlinePass(m_highlightedActor.value().get(), false, false);
}
m_fbo.Unbind();
}
void OvEditor::Panels::SceneView::RenderSceneForActorPicking()
{
auto& baseRenderer = *EDITOR_CONTEXT(renderer).get();
auto [winWidth, winHeight] = GetSafeSize();
m_actorPickingFramebuffer.Resize(winWidth, winHeight);
m_actorPickingFramebuffer.Bind();
baseRenderer.SetClearColor(1.0f, 1.0f, 1.0f);
baseRenderer.Clear();
m_editorRenderer.RenderSceneForActorPicking();
if (EDITOR_EXEC(IsAnyActorSelected()))
{
auto& selectedActor = EDITOR_EXEC(GetSelectedActor());
baseRenderer.Clear(false, true, false);
m_editorRenderer.RenderGizmo(selectedActor.transform.GetWorldPosition(), selectedActor.transform.GetWorldRotation(), m_currentOperation, true);
}
m_actorPickingFramebuffer.Unbind();
}
bool IsResizing()
{
auto cursor = ImGui::GetMouseCursor();
return
cursor == ImGuiMouseCursor_ResizeEW ||
cursor == ImGuiMouseCursor_ResizeNS ||
cursor == ImGuiMouseCursor_ResizeNWSE ||
cursor == ImGuiMouseCursor_ResizeNESW ||
cursor == ImGuiMouseCursor_ResizeAll;;
}
void OvEditor::Panels::SceneView::HandleActorPicking()
{
using namespace OvWindowing::Inputs;
auto& inputManager = *EDITOR_CONTEXT(inputManager);
if (inputManager.IsMouseButtonReleased(EMouseButton::MOUSE_BUTTON_LEFT))
{
m_gizmoOperations.StopPicking();
}
if (IsHovered() && !IsResizing())
{
RenderSceneForActorPicking();
// Look actor under mouse
auto [mouseX, mouseY] = inputManager.GetMousePosition();
mouseX -= m_position.x;
mouseY -= m_position.y;
mouseY = GetSafeSize().second - mouseY + 25;
m_actorPickingFramebuffer.Bind();
uint8_t pixel[3];
EDITOR_CONTEXT(renderer)->ReadPixels(static_cast<int>(mouseX), static_cast<int>(mouseY), 1, 1, OvRendering::Settings::EPixelDataFormat::RGB, OvRendering::Settings::EPixelDataType::UNSIGNED_BYTE, pixel);
m_actorPickingFramebuffer.Unbind();
uint32_t actorID = (0 << 24) | (pixel[2] << 16) | (pixel[1] << 8) | (pixel[0] << 0);
auto actorUnderMouse = EDITOR_CONTEXT(sceneManager).GetCurrentScene()->FindActorByID(actorID);
auto direction = m_gizmoOperations.IsPicking() ? m_gizmoOperations.GetDirection() : EDITOR_EXEC(IsAnyActorSelected()) && pixel[0] == 255 && pixel[1] == 255 && pixel[2] >= 252 && pixel[2] <= 254 ? static_cast<OvEditor::Core::GizmoBehaviour::EDirection>(pixel[2] - 252) : std::optional<Core::GizmoBehaviour::EDirection>{};
m_highlightedActor = {};
m_highlightedGizmoDirection = {};
if (!m_cameraController.IsRightMousePressed())
{
if (direction.has_value())
{
m_highlightedGizmoDirection = direction;
}
else if (actorUnderMouse != nullptr)
{
m_highlightedActor = std::ref(*actorUnderMouse);
}
}
/* Click */
if (inputManager.IsMouseButtonPressed(EMouseButton::MOUSE_BUTTON_LEFT) && !m_cameraController.IsRightMousePressed())
{
/* Gizmo picking */
if (direction.has_value())
{
m_gizmoOperations.StartPicking(EDITOR_EXEC(GetSelectedActor()), m_cameraPosition, m_currentOperation, direction.value());
}
/* Actor picking */
else
{
if (actorUnderMouse)
{
EDITOR_EXEC(SelectActor(*actorUnderMouse));
}
else
{
EDITOR_EXEC(UnselectActor());
}
}
}
}
if (m_gizmoOperations.IsPicking())
{
auto mousePosition = EDITOR_CONTEXT(inputManager)->GetMousePosition();
auto [winWidth, winHeight] = GetSafeSize();
m_gizmoOperations.SetCurrentMouse({ static_cast<float>(mousePosition.first), static_cast<float>(mousePosition.second) });
m_gizmoOperations.ApplyOperation(m_camera.GetViewMatrix(), m_camera.GetProjectionMatrix(), { static_cast<float>(winWidth), static_cast<float>(winHeight) });
}
}
首先是构造函数,除了传参,还有对相机设置清屏的颜色,远裁剪面,同时加入了文件项,可以以此加载磁盘中的场景或者用模型创建物体。
之后是Update函数,用于刷新视口可以看到的画面。首先会调用AViewControllable的Update,用于处理输入,并同时更新画面。接着是一些独有的输入处理,如果面板被选定且按住了右键,那么根据键盘输入的‘W’,‘E’,‘R’来执行分别对应的变换。
接着是_Render_Impl函数,这个用于自定义处理渲染方式,首先要先预制一个摄像机,接着获得特定的renderer,也就是自定义的渲染方法,获得这个renderer的状态,然后对特定的renderer使用Mask改变状态,先渲染选定物体,再改变状态后渲染实际场景。
接着是RenderScene函数,首先获得renderer,当前的场景和游戏视窗。如果游戏开始了,同时开启了ShowLightFrustumCullingInSceneView,那么就使用视锥体对场景进行裁剪,否则只单纯的更新光照。之后绑定FBO,即渲染对象,接着对renderer进行调整,设置模板缓冲写入方式和摄像机等,之后用renderer渲染我们的几何。当然,只有ShowGeometryFrustumCullingInSceneView被设定开启时,才会对我们的集合进行裁剪,否则单纯渲染场景。此时再渲染光照,如果有物体被选中的话,那就获得选中的物体,更改其渲染模式,使其有描边(先写入模板,再依据模板缓冲描边),高亮选中轴向且渲染选中物体的坐标轴。之后会再检测一次是否有需要高亮的物体,有就进行描边。最后解绑FBO。
之后是RenderSceneForActorPicking函数,用于渲染选中的物体,不过使用最基本的无光照。首先获得renderer并清空,之后更改帧缓冲大小,渲染被选中物体。同时对被选中物体渲染坐标轴,最后解绑物体。
IsResizing用于获得鼠标指针,如果指针在更改视窗大小就返回真值。
HandleActorPicking函数用于解决选中物体的逻辑和渲染。首先看是否停止选择,没有就先渲染选中物体。之后更改鼠标,并对各种输入做出对应反应,且决定是否选中了物体和是否渲染坐标轴。
三、Toolbar
1.Toolbar.h
class Toolbar : public OvUI::Panels::PanelWindow
{
public:
/**
* Constructor
* @param p_title
* @param p_opened
* @param p_windowSettings
*/
Toolbar
(
const std::string& p_title,
bool p_opened,
const OvUI::Settings::PanelWindowSettings& p_windowSettings
);
/**
* Custom implementation of the draw method
*/
void _Draw_Impl() override;
private:
OvUI::Widgets::Buttons::ButtonImage* m_playButton;
OvUI::Widgets::Buttons::ButtonImage* m_pauseButton;
OvUI::Widgets::Buttons::ButtonImage* m_stopButton;
OvUI::Widgets::Buttons::ButtonImage* m_nextButton;
};
工具栏的类,具体在下面讲解。
2.Toolbar.cpp
OvEditor::Panels::Toolbar::Toolbar
(
const std::string& p_title,
bool p_opened,
const OvUI::Settings::PanelWindowSettings& p_windowSettings
) : PanelWindow(p_title, p_opened, p_windowSettings)
{
std::string iconFolder = ":Textures/Icons/";
auto& textureManager = OvCore::Global::ServiceLocator::Get<OvCore::ResourceManagement::TextureManager>();
m_playButton = &CreateWidget<OvUI::Widgets::Buttons::ButtonImage>(EDITOR_CONTEXT(editorResources)->GetTexture("Button_Play")->id, OvMaths::FVector2{ 20, 20 });
m_pauseButton = &CreateWidget<OvUI::Widgets::Buttons::ButtonImage>(EDITOR_CONTEXT(editorResources)->GetTexture("Button_Pause")->id, OvMaths::FVector2{ 20, 20 });
m_stopButton = &CreateWidget<OvUI::Widgets::Buttons::ButtonImage>(EDITOR_CONTEXT(editorResources)->GetTexture("Button_Stop")->id, OvMaths::FVector2{ 20, 20 });
m_nextButton = &CreateWidget<OvUI::Widgets::Buttons::ButtonImage>(EDITOR_CONTEXT(editorResources)->GetTexture("Button_Next")->id, OvMaths::FVector2{ 20, 20 });
CreateWidget<OvUI::Widgets::Layout::Spacing>(0).lineBreak = false;
auto& refreshButton = CreateWidget<OvUI::Widgets::Buttons::ButtonImage>(EDITOR_CONTEXT(editorResources)->GetTexture("Button_Refresh")->id, OvMaths::FVector2{ 20, 20 });
m_playButton->lineBreak = false;
m_pauseButton->lineBreak = false;
m_stopButton->lineBreak = false;
m_nextButton->lineBreak = false;
refreshButton.lineBreak = false;
m_playButton->ClickedEvent += EDITOR_BIND(StartPlaying);
m_pauseButton->ClickedEvent += EDITOR_BIND(PauseGame);
m_stopButton->ClickedEvent += EDITOR_BIND(StopPlaying);
m_nextButton->ClickedEvent += EDITOR_BIND(NextFrame);
refreshButton.ClickedEvent += EDITOR_BIND(RefreshScripts);
EDITOR_EVENT(EditorModeChangedEvent) += [this](OvEditor::Core::EditorActions::EEditorMode p_newMode)
{
auto enable = [](OvUI::Widgets::Buttons::ButtonImage* p_button, bool p_enable)
{
p_button->disabled = !p_enable;
p_button->tint = p_enable ? OvUI::Types::Color{ 1.0f, 1.0f, 1.0f, 1.0f} : OvUI::Types::Color{1.0f, 1.0f, 1.0f, 0.15f};
};
switch (p_newMode)
{
case OvEditor::Core::EditorActions::EEditorMode::EDIT:
enable(m_playButton, true);
enable(m_pauseButton, false);
enable(m_stopButton, false);
enable(m_nextButton, false);
break;
case OvEditor::Core::EditorActions::EEditorMode::PLAY:
enable(m_playButton, false);
enable(m_pauseButton, true);
enable(m_stopButton, true);
enable(m_nextButton, true);
break;
case OvEditor::Core::EditorActions::EEditorMode::PAUSE:
enable(m_playButton, true);
enable(m_pauseButton, false);
enable(m_stopButton, true);
enable(m_nextButton, true);
break;
case OvEditor::Core::EditorActions::EEditorMode::FRAME_BY_FRAME:
enable(m_playButton, true);
enable(m_pauseButton, false);
enable(m_stopButton, true);
enable(m_nextButton, true);
break;
}
};
EDITOR_EXEC(SetEditorMode(OvEditor::Core::EditorActions::EEditorMode::EDIT));
}
void OvEditor::Panels::Toolbar::_Draw_Impl()
{
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(10, 10));
OvUI::Panels::PanelWindow::_Draw_Impl();
ImGui::PopStyleVar();
}
首先构造函数,对各个按钮进行了定义,包括图片,功能,是否启用等。如果按钮在属性上被开启了,就启用对应的按钮。
之后_Draw_Impl函数,自定义添加关于绘图方式的功能,可以添加自定义样式,之后会调用PanelWindow的同名函数。
总结
本篇文章讲了ProjectSettings,SceneView和Toolbar三个部分,以上是全部内容。至此Overload的OvWindowing部分和OvEdiotr部分分析完毕。