Добро пожаловать в гайд по mimgui.
mimgui — это новая графическая библиотека, написанная в результате устаревания предыдущей графической библиотеки Moon ImGui, использующей Dear ImGui v.1.52; использующая в своей основе свежий релиз Dear ImGui v.1.72. Новая библиотека включает в себя все основные возможности фреймворка, а API максимально приближен к оригинальному.
Содержание:
GitHub ImGui:
GitHub - ocornut/imgui: Dear ImGui: Bloat-free Graphical User interface for C++ with minimal dependencies
Dear ImGui: Bloat-free Graphical User interface for C++ with minimal dependencies - ocornut/imgui
github.com
GitHub - THE-FYP/mimgui: Dear ImGui for MoonLoader
Dear ImGui for MoonLoader. Contribute to THE-FYP/mimgui development by creating an account on GitHub.
github.com
Скачать mimgui
Установка: переместить папку mimgui из архива в папку «*Корневая папка с игрой*/moonloader/lib»
Установка: переместить папку mimgui из архива в папку «*Корневая папка с игрой*/moonloader/lib»
Основная информация
В mimgui используется относительно последняя версия ImGui (1.72): на момент написания статьи последняя его версия — 1.79.
Изначально написание этой статьи планировалось после выхода предстоящей версии MoonLoader, в которой должен был быть менеджер зависимостей, и сама библиотека должна была поставляться с помощью функционала МЗ, однако её релиз был отложен на неопределенный срок. В качестве основы используется LuaJit ImGui , который, в свою очередь, в качестве основы использует cimgui.
mimgui разрабатывался с декабря 2018 года, однако, широкую популярность он получил только в июле-августе 2019 года, как раз в период выхода первой beta-версии MoonLoader 027.
Примеры использования mimgui можно увидеть в самом репозитории, но несмотря на это, в этой теме также отдельно будут добавлены примеры использования.
Примеры использования
Давайте напишем примитивный скрипт с использованием mimgui.
Lua:
local imgui = require 'mimgui' -- Подключаем саму библиотеку
local newFrame = imgui.OnFrame( --[[Сама функция создания фрейма, их может быть сколько вашей душе угодно.
Обратите внимание, что в mimgui рекомендуется создавать отдельный
фрейм для каждого окна, однако это не является обязательным.]]
function() return true end, -- Определяет, выполняется/отображается ли текущий фрейм.
function(player) --[[Сама область, в которой уже будем рисовать элементы.
В функцию в качестве первой переменной передаются список функций
для взаимодействия с локальным игроком и рядом нескольких возможностей.]]
imgui.Begin("Main Window") -- Создаём новое окно с заголовком 'Main Window'
imgui.Text("Hello") -- Добавляем туда текст 'Hello'
imgui.End() -- Объявляем конец данного окна
end
)
function main()
wait(-1)
end
Результат:
Само собой, это не все возможности ImGui, поэтому немного преобразим наш скрипт: добавим размер, позицию по умолчанию и клавишу активации для показа окна.
Lua:
local imgui = require 'mimgui'
local vkeys = require 'vkeys' --[[Библиотека со списком индексов клавиш и функциями для
взаимодействия с ними.]]
local wm = require 'windows.message' -- Список событий для окна игры
local new = imgui.new --[[Создаём короткий псевдоним для функции,
создающего буфера для различных функций ImGui]]
local renderWindow = new.bool(--[[true/false, по умолч. false]])
local sizeX, sizeY = getScreenResolution()
local newFrame = imgui.OnFrame(
function() return renderWindow[0] end,
function(player)
imgui.SetNextWindowPos(imgui.ImVec2(sizeX / 2, sizeY / 2), imgui.Cond.FirstUseEver, imgui.ImVec2(0.5, 0.5)) -- Укажем положение окна по центру и выставим оффсет 0.5, чтобы рендер шёл от середины окна
imgui.SetNextWindowSize(imgui.ImVec2(200, 150), imgui.Cond.FirstUseEver) -- Укажем размер
imgui.Begin("Main Window", renderWindow)
imgui.Text("Hello")
imgui.Text(string.format("Current render mode: %s", renderWindow[0]))
imgui.End()
end
)
function main()
addEventHandler('onWindowMessage', function(msg, wparam, lparam) -- Сама функция, в которой будем обрабатывать горячие клавиши. Обратите внимание, что данный способ является наиболее верным в плане оптимизации.
if msg == wm.WM_KEYDOWN or msg == wm.WM_SYSKEYDOWN then -- Если клавиша нажата
if wparam == vkeys.VK_X then -- И если это клавиша X
renderWindow[0] = not renderWindow[0] -- Переключаем состояние рендера
end
end
end)
wait(-1)
end
Результат:
Но, ведь ничего не изменилось: размер тот же, положение то же самое: в левом верхнем углу, почему?
Дело в том, что mimgui умеет сохранять данные окон по их индексу. Индексы окон — это их заголовки, поэтому заголовки также следует делать уникальными.
Если вам необходимы одинаковые названия окон, то достаточно после названия окна добавить
##уникальное_значение
, не беспокойтесь, он не будет виден. Обратите внимание, что это следует делать во всех случаях: InputText(Multiline), InputInt, Button и так далее; абсолютно во всех, иначе работать у вас будет только первая кнопка/инпут.Хорошо, с индексом разобрались, возможно, вам понадобится убрать автоматическое запоминание, давайте выключим:
Lua:
local imgui = require 'mimgui'
local vkeys = require 'vkeys'
local wm = require 'windows.message'
local new = imgui.new
local renderWindow = new.bool()
local sizeX, sizeY = getScreenResolution()
imgui.OnInitialize(function() --[[Функция, вызывающаяся один раз за период жизни скрипта.
Обратите внимание, что пока никакое ImGui окно ни разу не показывалось,
функция не вызовется и это может вызвать ошибки об отсутствии
каких-либо переменных, если вы их здесь объявили.
Поэтому, здесь следует просто изменять значения по умолчанию, например:
цвет элементов, "сохранять ли настройки и в какой файл"
Либо подгружать картинки, необходимые для показа окнам ImGui
Если вы за пределами ImGui попытаетесь подгрузить картинку, вы поймаете ошибку.]]
-- Выключаем сохранение. По умолчанию: moonloader/config/mimgui/%scriptfilename%.ini
imgui.GetIO().IniFilename = nil
end)
local newFrame = imgui.OnFrame(
function() return renderWindow[0] end,
function(player)
imgui.SetNextWindowPos(imgui.ImVec2(sizeX / 2, sizeY / 2), imgui.Cond.FirstUseEver, imgui.ImVec2(0.5, 0.5))
imgui.SetNextWindowSize(imgui.ImVec2(200, 150), imgui.Cond.FirstUseEver)
imgui.Begin("Main Window", renderWindow)
imgui.Text("Hello")
imgui.Text(string.format("Current render mode: %s", renderWindow[0]))
imgui.End()
end
)
function main()
addEventHandler('onWindowMessage', function(msg, wparam, lparam)
if msg == wm.WM_KEYDOWN or msg == wm.WM_SYSKEYDOWN then
if wparam == vkeys.VK_X then
renderWindow[0] = not renderWindow[0]
end
end
end)
wait(-1)
end
Теперь окно увеличилось, отображается по центру и его можно скрыть:
Давайте теперь напишем что-то на русском языке:
Lua:
local imgui = require 'mimgui'
local ffi = require 'ffi' -- Подключаем библиотеку ffi для использования возможностей Си (C)
local vkeys = require 'vkeys'
local wm = require 'windows.message'
local new, str, sizeof = imgui.new, ffi.string, ffi.sizeof
local renderWindow = new.bool()
local inputField = new.char[256 --[[Указываем размер]]](--[[Здесь можно указать какой-либо текст]])
local sizeX, sizeY = getScreenResolution()
imgui.OnInitialize(function()
imgui.GetIO().IniFilename = nil
end)
local newFrame = imgui.OnFrame(
function() return renderWindow[0] end,
function(player)
imgui.SetNextWindowPos(imgui.ImVec2(sizeX / 2, sizeY / 2), imgui.Cond.FirstUseEver, imgui.ImVec2(0.5, 0.5))
imgui.SetNextWindowSize(imgui.ImVec2(200, 150), imgui.Cond.FirstUseEver)
imgui.Begin("Main Window", renderWindow)
imgui.Text("Hello")
imgui.Text(string.format("Current render mode: %s", renderWindow[0]))
if imgui.InputText("Привет", inputField, sizeof(inputField)) then
-- Первое: уникальное название инпута, второе: само поле, третье: максимальная длина текста/размер инпута.
print(str(inputField)) -- Читаем значение инпута через функцию str
end
if imgui.Button("Очистить поле") then
imgui.StrCopy(inputField, '') -- Можно вписать какое-либо значение в инпут, при желании.
end
imgui.End()
end
)
function main()
addEventHandler('onWindowMessage', function(msg, wparam, lparam)
if msg == wm.WM_KEYDOWN or msg == wm.WM_SYSKEYDOWN then
if wparam == vkeys.VK_X then
renderWindow[0] = not renderWindow[0]
end
end
end)
wait(-1)
end
Результат:
Мы видим здесь каракули и вопросительные знаки, почему это происходит?
Работа с другими языками на примере русского
В MoonLoader 025 были добавлены библиотеки lua-iconv и encoding, они призваны помочь в работе с разными кодировками текста.
Следующий пример показывает, как использовать текст на русском в ImGui:
Скрипт должен быть сохранён в кодировке Windows-1251 (конкретно для данного примера)
Если в вашем скрипте имеется огромный код с использованием ImGui и мало взаимодействия с функциями MoonLoader'a либо SAMPFUNCS'a, то вы при желании можете сохранить ваш скрипт в UTF-8 и вам не придётся проделывать все эти операции.
Lua:
local imgui = require 'mimgui'
local ffi = require 'ffi'
local vkeys = require 'vkeys'
local encoding = require 'encoding' --[[Подключаем библиотеку для чтения/записи данных с кодировкой,
отличающейся от кодировки нашего скрипта.]]
encoding.default = 'CP1251' --[[Указываем кодировку по умолчанию. Обратите внимание,
что она должна совпадать с кодировкой вашего скрипта.]]
local u8 = encoding.UTF8 -- И создаём короткий псевдоним для кодировщика UTF-8
local wm = require 'windows.message'
local new, str, sizeof = imgui.new, ffi.string, ffi.sizeof
local renderWindow, freezePlayer, removeCursor = new.bool(), new.bool(), new.bool()
local inputField = new.char[256](--[[Здесь также следует кодировать информацию!]])
local sizeX, sizeY = getScreenResolution()
imgui.OnInitialize(function()
imgui.GetIO().IniFilename = nil
end)
local newFrame = imgui.OnFrame(
function() return renderWindow[0] end,
function(player)
imgui.SetNextWindowPos(imgui.ImVec2(sizeX / 2, sizeY / 2), imgui.Cond.FirstUseEver, imgui.ImVec2(0.5, 0.5))
imgui.SetNextWindowSize(imgui.ImVec2(220, 200), imgui.Cond.FirstUseEver)
imgui.Begin("Main Window", renderWindow)
imgui.Text("Hello")
imgui.Text(string.format("Current render mode: %s", renderWindow[0]))
if imgui.InputText(u8"Привет", inputField, sizeof(inputField)) then
-- Кодируем название инпута
print(u8:decode(str(inputField))) -- Декодируем в Windows-1251
end
if imgui.Button(u8"Очистить поле") then -- Кодируем название кнопки
imgui.StrCopy(inputField, '')
end
if imgui.Checkbox(u8'Заморозить игрока', freezePlayer) then -- Кодируем название кнопки
player.LockPlayer = freezePlayer[0]
end
if imgui.Checkbox(u8'Скрыть курсор', removeCursor) then -- Кодируем название кнопки
player.HideCursor = removeCursor[0]
end
if player.HideCursor then
imgui.Text(u8'Курсор скрыт') -- Кодируем выводимый текст
end
imgui.End()
end
)
function main()
addEventHandler('onWindowMessage', function(msg, wparam, lparam)
if msg == wm.WM_KEYDOWN or msg == wm.WM_SYSKEYDOWN then
if wparam == vkeys.VK_X then
renderWindow[0] = not renderWindow[0]
end
end
end)
wait(-1)
end
Результат:
В примерах не было затронуто наличие "префрейма" (BeforeFrame), применение его и остальных возможностей вы можете увидеть в репозитории по ссылке:
mimgui/examples/mimgui-extra-features.lua at v1.7.0 · THE-FYP/mimgui
Dear ImGui for MoonLoader. Contribute to THE-FYP/mimgui development by creating an account on GitHub.
github.com
Главные различия между ImGui C++ API и mimgui Lua API
Описание | В C++ ImGui | В Lua mimgui |
---|---|---|
Все функции из пространства имён ImGui, как и все типы, и все перечисления находятся в таблице, возвращаемой модулем | ImGui::Text("text"); ImVec2(0.1f, 2.3f); | imgui.Text("text") imgui.ImVec2(0.1, 2.3) |
Названия перечислений (enum) и их значений лишились префиксов и символа "_" в конце | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | imgui.WindowFlags.NoTitleBar + imgui.WindowFlags.NoResize |
Значения базовых типов, которые в ImGui записываются по указателю, должны быть использованы через ffi | static bool win = false; ImGui::Begin("window", &win); win = false; | local win = imgui.new.bool(false) imgui.Begin("window", win) win[0] = false |
Использование функций InputText и InputTextMultiline | char buf[256]; ImGui::InputText('input', buf, IM_ARRAYSIZE(buf)) | local buf = imgui.new.char[256]() imgui.InputText('input', buf, ffi.sizeof(buf)) |
Динамические массивы в виде массива указателей следует объявлять через ffi | const char* items[] = {"1", "2", "3"}; ImGui::ListBox("list", &lb_cur, items, 3) | local itemsList = {"1", "2", "3"} local current = imgui.new.int(0) local items = imgui.new['const char*'][#itemsList](itemsList) imgui.ListBoxStr_arr("list", current, items, #itemsList) |
Главные различия между MoonImGui и mimgui
Для взаимодействия с ImGui теперь используется встроенная в LuaJIT библиотека ffi, а не самописные функции. Поэтому, получается так, что когда мы пишем код, мы "пишем код" на С++. Поэтому, добавленные в MoonImGui типы: ImBool, ImBuffer, ImInt, ImFloat, ImCallback не требуются, и им на замену пришли встроенные и не встроенные в Си типы:
Тип | Использование в MoonImGui | Использование в mimgui |
---|---|---|
Bool | local bool = imgui.ImBool(true/false) | local bool = imgui.new.bool(true/false) |
Int | local int = imgui.ImInt(5) | local int = imgui.new.int(5) |
Char | local buffer = imgui.ImBuffer(128, "hello") | local buffer = imgui.new.char[128]("hello") |
Float | local float = imgui.ImFloat(5.12) | local float = imgui.new.float(5.12) |
Callback | local test = function(data) print("hey") return 0 end local callback = imgui.ImCallback(test) | local test = function(data) print("hey") return 0 end local callback = ffi.cast('int (*)(ImGuiInputTextCallbackData* data)', test) |
Следует отметить, что получение исходных значений так же подверглось изменению. Если раньше исходное значение можно было получить через ключ
v
(buffer.v; int.v
), то в mimgui они получаются с помощью нулевого индекса (int[0]; float[0]; bool[0]
), а для типа Char
значение необходимо получать через ffi.string
(ffi.string(buffer)
). Не нужно бояться внесёнными в API изменениям, они очень легко осваиваются и для ежедневного кодинга не нужно вникать в их работу.Отличительные способности mimgui Lua
В разработке
Последнее редактирование модератором: