【ZeloEngine】ImGui汇总

需求

游戏内置菜单(In-Game Menus)

GM界面,这是一个程序编写的界面

需求:

  • 程序化的,代码驱动的界面
  • 无需美术拼UI,注重实用,不太在乎美观
  • 跨平台
  • 入口,一键呼出,PC可以是快捷键,手机可以是一个可以移动的小按钮,手柄可以是一个组合键

以下图片来自《游戏引擎架构》

顽皮狗是自己定制的一个界面,属于ImGui的子集

Main development menu

Rendering submenu

Mesh options subsubmenu

Background meshes turned off

使用Icon

前面虽然说实用优先,不需要表面功夫,不过话说回来

程序开发的界面,Icon是一个很好的优化,美观,提供视觉引导

这种引导是潜移默化的,即使你不去看手册,一个有Icon的编辑器用多了,自然会看Icon来识别出哪个功能在哪里

而没有Icon的编辑器,则一直需要看文字来找位置

案例二

Images and Icons for Visual Studio - Visual Studio | Microsoft Docs

Icon Download

案例二

阿比盖尔 | Don’t Starve 中文維基 | Fandom

本地化

编辑器本地化有标准方案,参考Godot

说实话,对于国内团队,中文编辑器是刚需

大部分人的英文水平,仍然需要中文编辑器来达到最好的编辑效率

ImGui

如何开发/编程指南

  • ImGui基本原理
  • ImGui框架概念
  • ImGui接口
  • 脚本绑定接口
  • 脚本薄封装框架
  • 参考项目

痛点

原理学完只是第一步,我们要铺量去写UI,那工具链要是完善的

目前抄了几个界面后的痛点是:对接口不熟悉,查接口要跳很多文档

理想的状态是:Model数据结构=》设计粗略View展示Model=》转换成代码=》迭代交互和样式

接口文档

https://blog.csdn.net/zolo_mario/article/details/120359861?spm=1001.2014.3001.5501

https://blog.csdn.net/zolo_mario/article/details/120357560?spm=1001.2014.3001.5501

https://blog.csdn.net/zolo_mario/article/details/120359935?spm=1001.2014.3001.5501

Doc/Editor/ImGui/**

imgui的文档维护比较糟糕,接口文档都在代码里,所以我整理了一份文档

脚本绑定接口

原文档

imgui_patch.lua

文档导出的桩文件,基本可用,按需要改即可

脚本绑定方案和接口,可以多看看几个方案,但是持续维护目前的就够了

主要是由于C和Lua的差异,脚本接口是有微小差异的,所以需要维护一套

ImGui Demo 自解释

ImGui本身有很多参数,ImGui本身又是一个参数编辑器框架,所以Demo基本就是自己编辑自己

UI框架

基本架构

ImGui(C++) => sol wrapper => ImGui(Lua) => ImGui Framework

框架主要是做一个薄封装和分类,便于开发

薄封装

ImGui接口本身都是全局函数,有两个问题:

  • 没有做分类,量很大,从开发者角度有一些冗余

    • 同样功能不同接口二选一,Column和Table
    • 实际用不到的
    • 过时的接口
    • beta接口
  • 脚本绑定后Lua接口和C接口的差异
  • ImGui更新(docking分支目前仍然不是主分支),后向兼容性的风险

分类

  • panel
  • widget
  • layout
  • plugin

脚本绑定减少重载

既然脚本层封装了,那么绑定层就不要提供太多重载,影响性能

以MenuItem为例,下面这样写太复杂了,封装成一个接口就可以了

bool MenuItem ( const  char * label, const  char *shortcut = NULL , bool selected = false , bool enabled = true );
激活时返回真。bool MenuItem ( const  char * label, const  char * shortcut, bool * p_selected, bool enabled = true );
激活时返回 true + toggle (*p_selected) 如果 p_selected != NULL
inline bool MenuItem(const std::string &label) { return ImGui::MenuItem(label.c_str()); }inline bool MenuItem(const std::string &label, const std::string &shortcut) {return ImGui::MenuItem(label.c_str(), shortcut.c_str());
}inline std::tuple<bool, bool> MenuItem(const std::string &label, bool selected) {bool activated = ImGui::MenuItem(label.c_str(), nullptr, &selected);return std::make_tuple(selected, activated);
}inline std::tuple<bool, bool> MenuItem(const std::string &label, const std::string &shortcut, bool selected) {bool activated = ImGui::MenuItem(label.c_str(), shortcut.c_str(), &selected);return std::make_tuple(selected, activated);
}inline std::tuple<bool, bool> MenuItem(const std::string &label, const std::string &shortcut, bool selected,bool enabled) {bool activated = ImGui::MenuItem(label.c_str(), shortcut.c_str(), &selected, enabled);return std::make_tuple(selected, activated);
}
--- Parameters A: text (label), text (shortcut) [0]
--- Parameters B: text (label), text (shortcut), bool (selected)
--- Parameters C: text (label), bool (selected)
--- Returns A: bool (activated)
--- returns B: bool (selected), bool (activated)
--- Overloads
--- activated = ImGui.MenuItem("Label")
--- activated = ImGui.MenuItem("Label", "ALT+F4")
--- selected, activated = ImGui.MenuItem("Label", selected)
--- selected, activated = ImGui.MenuItem("Label", "ALT+F4", selected)
--- selected, activated = ImGui.MenuItem("Label", "ALT+F4", selected, true)
--- ```
function ImGui.MenuItem(label, shortcut, selected) end

本地化

略,没空

UI图片

ImGui接受GL的纹理ID

Docking

https://github.com/ocornut/imgui/issues/2109

  • 把多个窗口合并到一个窗口的分页
  • 把窗口吸附到窗口边上
  • 多视口,是指可以把imgui窗口拖出Windows窗口独立存在
    • 这个目前仅在Windows上测试通过
    • 这个功能对代码的改动比较大

接口

启用Docking

ImGuiManager.EnableDocking

PanelWindow启动Docking

local DefaultPanelWindowSettings = {dockable = true;
}

撤销机制 Undo/Redo

https://github.com/ocornut/imgui/issues/1875

Lua脚本兼容性

指针参数

指针参数被额外用作返回值,也就是in+out

lua肯定是没有这种用法,对应改成额外返回值即可

也就是说,原来一个变量,要改成传入参数,再传出结果

lua变量open传入C时,C API会解包做拷贝传参,成为局部变量,对lua的open是没有影响的

p_open还额外携带bool信息,lua没有可空类型,一般还是额外传参数

bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags)window->HasCloseButton = (p_open != NULL);

函数重载

C函数重载,基本靠动态解析lua传来的参数来匹配

有两个问题,脚本重构火葬场,和解析性能

首先有一个痛点,就是IDE,因为lua IDE是没法辅助重载的,基本靠文档和人脑记忆

大批量写代码时,跑到才会报错,非常蛋疼

然后就是重载的越多,解析越复杂

所以应该限制重载

以BeginChild为例

脚本绑定之后没有额外开销,最后都是调用一个函数

这里重载是为了默认参数,其实文档用最长的即可

脚本接口

-- ImGui.BeginChild(...)
-- Parameters: text (name), float (size_x) [O], float (size_y) [O], ImGuiWindowFlags (flags) [O]
-- Returns: bool (shouldDraw)
-- Overloads
shouldDraw = ImGui.BeginChild("Name", 100)
shouldDraw = ImGui.BeginChild("Name", 100)
shouldDraw = ImGui.BeginChild("Name", 100, 200)
shouldDraw = ImGui.BeginChild("Name", 100, 200, true)
shouldDraw = ImGui.BeginChild("Name", 100, 200, true, ImGuiWindowFlags.NoMove)-- ImGui.EndChild()
ImGui.EndChild()

脚本绑定

bool BeginChild(const std::string &name) { return ImGui::BeginChild(name.c_str()); }bool BeginChild(const std::string &name, float sizeX) { return ImGui::BeginChild(name.c_str(), {sizeX, 0}); }bool BeginChild(const std::string &name, float sizeX, float sizeY) {return ImGui::BeginChild(name.c_str(), {sizeX, sizeY});
}bool BeginChild(const std::string &name, float sizeX, float sizeY, bool border) {return ImGui::BeginChild(name.c_str(), {sizeX, sizeY}, border);
}bool BeginChild(const std::string &name, float sizeX, float sizeY, bool border, int flags) {return ImGui::BeginChild(name.c_str(), {sizeX, sizeY}, border, static_cast<ImGuiWindowFlags>(flags));
}ImGui.set_function("BeginChild", sol::overload(
sol::resolve<bool(const std::string &)>(BeginChild),
sol::resolve<bool(const std::string &, float)>(BeginChild),
sol::resolve<bool(const std::string &, float, float)>(BeginChild),
sol::resolve<bool(const std::string &, float, float, bool)>(BeginChild),
sol::resolve<bool(const std::string &, float, float, bool, int)>(BeginChild)
));

调到的内核接口

bool BeginChild(const char* str_id, const ImVec2& size = ImVec2(0, 0), bool border = false, ImGuiWindowFlags flags = 0);

控件ID

为什么控件交互没有反应?

https://github.com/ocornut/imgui/blob/master/docs/FAQ.md#q-why-is-my-widget-not-reacting-when-i-click-on-it

ImGui隐式维护ID,控件树中的控件路径被hash来标识一个控件

每个控件的接口一般都有label,是该控件的ID

所以传空串就会导致无法交互,解决是用##XXX来标识,这些在显示时被忽略

因为复杂的控件其实维护了状态,比如树节点,有开关状态,所以在帧之间需要标识,这个控件调用就是这个控件

ID

  • ID可以是字符串(label参数),索引,或者指针
  • 索引指针用于标识列表控件中的item
  • PushID/PopID用于手工构造作用域,来解决冲突
  • 因为label还承担显示名字的作用,用##XXX来解决ID冲突

用指针做ID的例子,不过lua脚本没法这么用,可以用##i来标识

int i;
bool node_open = ImGui.TreeNodeEx((void*)(intptr_t)i, node_flags, "Selectable Node %d", i);
// ID堆栈/范围
//阅读 FAQ(docs/FAQ.md 或 http://dearimgui.org/faq)以了解有关如何在 Dear imgui 中处理 ID 的更多详细信息。
// - 通过对 ID 堆栈系统的理解来回答和影响这些问题:
//    - “问:为什么我的小部件在我点击时没有反应?”
//    - “问:我怎样才能拥有带有空标签的小部件?”
//    - “问:我怎样才能拥有多个具有相同标签的小部件?”
// - 简短版本:ID 是整个 ID 堆栈的哈希值。如果您在循环中创建小部件,您很可能
//   想要推送一个唯一标识符(例如对象指针、循环索引)来唯一区分它们。
// - 您还可以在小部件标签中使用“标签##foobar”语法来区分它们。
// - 在这个头文件中,我们使用“标签”/“名称”术语来表示将显示的字符串 + 用作 ID,
//   而 "str_id" 表示仅用作 ID 且不正常显示的字符串。
void           PushID ( const  char * str_id);                                     //将字符串推入 ID 堆栈(将哈希字符串)。
void           PushID ( const  char * str_id_begin, const  char * str_id_end);       //将字符串推入 ID 堆栈(将哈希字符串)。
void           PushID ( const  void * ptr_id);                                     //将指针推入 ID 堆栈(将散列指针)。
void           PushID ( int int_id);                                             //将整数推入 ID 堆栈(将散列整数)。
void           PopID ();                                                        //从 ID 堆栈中弹出。
ImGuiID        GetID ( const  char * str_id);                                      //计算唯一 ID(整个 ID 堆栈的哈希值 + 给定参数)。例如,如果您想自己查询 ImGuiStorage
ImGuiID        GetID ( const  char * str_id_begin, const  char * str_id_end);
ImGuiID        GetID ( const  void * ptr_id);

Cpp Trick in ImGui

避免大函数,拆分成小函数

避免大函数,拆分成小函数,因为链接大函数的时间的复杂度是非线性的

ImGui的调用风格

  • 返回bool,给if去触发逻辑
  • 额外的返回值通过指针参数传出
  • pStatus承担了至多三种功能,输入和返回值,NULL时还是禁用
  • 相对复杂的控件是带状态的,有控件ID,输入参数会被缓存在控件状态中
bool MenuItem(const char * label, const char *shortcut, bool *p_selected, bool enabled=true);

变长参数

TextXXX文本控件有两个版本,比如Text和TextV

两个都是vararg,一个是接口,一个是内部实现

…用va_start和va_end可以收集到一个va_list中

void ImGui::Text(const char* fmt, ...)
{va_list args;va_start(args, fmt);TextV(fmt, args);va_end(args);
}void ImGui::TextV(const char* fmt, va_list args)
{ImGuiWindow* window = GetCurrentWindow();if (window->SkipItems)return;ImGuiContext& g = *GImGui;const char* text_end = g.TempBuffer + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args);TextEx(g.TempBuffer, text_end, ImGuiTextFlags_NoWidthForLargeClippedText);
}

常见问题

无法获取键盘输入

https://github.com/ocornut/imgui/issues/2608

io.AddInputCharacter()

Character和Key的概念和接口是分开的

字体要支持中文,否则只能用英文输入法

AddFontFromMemoryTTF的内存问题

Assertion failed: font_offset >= 0 && "FontData is incorrect, or FontNo cannot be found.",
file imgui_draw.cpp, line 2117

错误信息并没有什么

找到出错的接口,从内存加载ttf字体

IMGUI_API ImFont* AddFontFromMemoryTTF(void* font_data, int font_size, float size_pixels);

出错的原因在于这个接口的NOTE,大意是所有权移交ImFontAtlas,ttf的内存会被delete掉

NB: Transfer ownership of ‘ttf_data’ to ImFontAtlas, unless font_cfg_template->FontDataOwnedByAtlas == false.
Owned TTF buffer will be deleted after Build().

类似的加载资源的第三方库接口其实都要注意这点,比如stbimage加载纹理,imgui加载字体,要注意资源的内存是自己还是库来管理
否则要么是释放了两次,运行时报错,要么是内存泄漏

这里要拷贝一份资源内存交给imgui去使用

对应ZeloEngine的Resource有两个接口read和readCopy,这里使用后者

【ZeloEngine】ImGui汇总相关推荐

  1. 【ZeloEngine】反射系统填坑小结

    [ZeloEngine]反射系统填坑小结 总结一下反射系统,下称ZHT ZHT架构/数据流图 今天是入行两周年,本想写个入行小结,想想还是踏实点把坑填完,写个DevLog 本文接续之前一篇文章的原理描 ...

  2. Unity 3D 博客汇总

    附录 X4.作业优秀博客汇总 目录 文章目录 附录 X4.作业优秀博客汇总 1.最有价值个人博客(Valuable Person Blogs) 2.博客表达技巧与游戏创新 2.1 写读者欢迎的游戏博客 ...

  3. C/C++框架和第三方库汇总

    根据读者反馈,发现此篇汇总获赞率异常的高,为了照顾一下新加入的粉丝,故重新发一遍,供粉丝查阅温习. 值得学习的C语言开源项目 - 1. Webbench Webbench是一个在linux下使用的非常 ...

  4. C++标准库,框架等资源大汇总!

    C++标准库,框架等资源大汇总! C++ 资源大全 关于 C++ 框架.库和资源的一些汇总列表,内容包括:标准库.Web应用框架.人工智能.数据库.图片处理.机器学习.日志.代码分析等. 标准库 C+ ...

  5. 【ZeloEngine】Lua调试器

    [ZeloEngine]Lua调试器 Lua没有非常强势的IDE和调试器方案,基本上都是专用方案造的轮子 尝试了几个方案(按时间顺序) Decoda LuaPerfect EmmyLua(Clion) ...

  6. 最常见NLP任务练手项目汇总

    分词 Word Segmentation chqiwang/convseg ,基于CNN做中文分词,提供数据和代码. 词预测 Word Prediction Kyubyong/word_predict ...

  7. 用python下载文件的若干种方法汇总

    压缩文件可以直接放到下载器里面下载的 you-get 连接 下载任意文件 重点 用python下载文件的若干种方法汇总 写文章 用python下载文件的若干种方法汇总 zhangqibot发表于Met ...

  8. 命名实体识别训练集汇总(一直更新)

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/leitouguan8655/artic ...

  9. LeetCode简单题之汇总区间

    题目 给定一个 无重复元素 的 有序 整数数组 nums . 返回 恰好覆盖数组中所有数字 的 最小有序 区间范围列表 .也就是说,nums 的每个元素都恰好被某个区间范围所覆盖,并且不存在属于某个范 ...

最新文章

  1. java基础-static
  2. Dubbo源码解析 --- DIRECTORY和ROUTER
  3. JavaScript面向对象的支持(7)
  4. 测试用例设计之正交表法详解
  5. Eclipse插件开发总结(第二天)
  6. closewait一直不释放_机床为什么要释放应力?怎么释放应力才好?
  7. 哈希表思路图解和代码实现
  8. Shell脚本编程:使用shell打印九九乘法表
  9. C小项目 简易英汉词典
  10. Message启动菜单个性化制作工具V1.0.3.1最终版
  11. 与其埋头啃文献不如关注这些公众号
  12. STM32外接DHT11显示温湿度
  13. 拥有超过1200个PNG格式图标的免费图标集 - Cosmo Mini
  14. google的视频下载插件
  15. win10 1073linux密码,Linux Bash on Win10 忘记密码解决
  16. mysql字符串类型建立全文索引
  17. 基于STM32F103的USB学习笔记38 - Mass Storage之SPI Flash做U盘
  18. 湖南c语言程序设计,C语言程序设计
  19. 发烧怎么办?按这5个穴位
  20. weibo4j中用到的mysql2bean的java工具

热门文章

  1. PM:iOS 为什么感觉比 Android 流畅?
  2. Android Gallary扩展实现 WheelView(Gallary竖着滑实现时间现实)
  3. HDU5015 233 Matrix
  4. python如何输入字母_python怎么输出单词的字母
  5. 【离散数学】数理逻辑 第一章 命题逻辑(7) 命题逻辑的推理理论
  6. 标点符号的英文读写搜集(二)中英文标点符号的读法用法大全
  7. 【历史】 apache catalina servlet tomcat 命名的由来
  8. ueditor 编辑器增加css样式_百度编辑器(uedtior)怎么更换样式文件
  9. 01【刘立刚图形学笔记】_图形学整体概述
  10. vue大屏展示 代码 从0 到1