Это ImGui - самый функциональный и одновременно самый простой GUI-фреймворк из всех, что мне известны.
И теперь каждый из вас сможет использовать его в своих Lua скриптах для MoonLoader.
Представляю вам Moon ImGui - Lua биндинг ImGui для MoonLoader. Он включает все основные возможности фреймворка, а API максимально приближен к оригинальному по мере возможного.
Тут не будут подробно рассматриваться возможности и особенности ImGui, в этой теме будет рассказано и показано как использовать Moon ImGui в Lua. За информацией по самому 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
Начнём с самого элементарного:
Lua:
local imgui = require 'imgui' -- загружаем библиотеку
-- в этой функции осуществляется вся работа с ImGui
-- она вызывается каждый кадр, но только если imgui.Process равен true
function imgui.OnDrawFrame()
imgui.Begin('My window') -- новое окно с заголовком 'My window'
imgui.Text('Hello world') -- простой текст внутри этого окна
imgui.End() -- конец окна
end
function main()
imgui.Process = true -- ImGui будет обрабатываться, пока imgui.Process равен true
-- в этом примере мы просто активируем ImGui сразу же после загрузки игры
end
Это работает, но что-то не впечатляет, согласитесь. Окно изначально маленькое, показывается сразу при старте и его нельзя закрыть.
Сделаем его побольше, добавим активацию и какое-нибудь действие:
Lua:
local imgui = require 'imgui'
local key = require 'vkeys'
-- одно из основных отличий от оригинального апи
-- все переменные, значения которых записываются в ImGui по указателю, могут использоваться только через специальные типы
local main_window_state = imgui.ImBool(false)
function imgui.OnDrawFrame()
if main_window_state.v then -- чтение и запись значения такой переменной осуществляется через поле v (или Value)
imgui.SetNextWindowSize(imgui.ImVec2(150, 200), imgui.Cond.FirstUseEver) -- меняем размер
-- но для передачи значения по указателю - обязательно напрямую
-- тут main_window_state передаётся функции imgui.Begin, чтобы можно было отследить закрытие окна нажатием на крестик
imgui.Begin('My window', main_window_state)
imgui.Text('Hello world')
if imgui.Button('Press me') then -- а вот и кнопка с действием
-- условие будет выполнено при нажатии на неё
printStringNow('Button pressed!', 1000)
end
imgui.End()
end
end
function main()
while true do
wait(0)
if wasKeyPressed(key.VK_X) then -- активация по нажатию клавиши X
main_window_state.v = not main_window_state.v -- переключаем статус активности окна, не забываем про .v
end
imgui.Process = main_window_state.v -- теперь значение imgui.Process всегда будет задаваться в зависимости от активности основного окна
end
end
В коде была использована переменная типа ImBool - это один из новых типов, добавленных в Moon ImGui. Такой подход неизбежен, поскольку в луа невозможна передача базовых типов по ссылке. Это не единственное изменение, есть и другие, вам потребуется о них знать. Вот их полный список:
Разница между C++ API и Lua API
Описание | В C++ | В Lua |
---|---|---|
Все функции из пространства имён ImGui, как и все типы, и все перечисления находятся в таблице, возвращаемой модулем | ImGui::Text("text"); ImVec2(0.1f, 2.3f); | imgui.Text("text"); imgui.ImVec2(0.1, 2.3); |
Названия перечислений (enum) и их значений лишились префиксов и символа "_" в конце | ImGuiWindowFlags_NoTitleBar | imgui.WindowFlags.NoTitleBar |
Значения базовых типов, которые в ImGui записываются по указателю, должны быть использованы через специальные типы: ImBool для bool, ImFloat для float, ImInt для int и unsigned int, ImFloat2-4 для float[2-4], ImInt2-4 для int[2-4] | static bool win = false; ImGui::Begin("window", &win); win = false; | local win = imgui.ImBool(false) imgui.Begin("window", win) win.v = false |
Функции с переменным количеством аргументов для форматирования текста не поддерживают форматирование, используйте string.format | ImGui::Text("hey, %s", name) | imgui.Text(string.format('hey, %s', name)) |
Функции InputText и InputTextMultiline принимают ImBuffer вместо char* buf + size_t buf_size | char buf[256]{}; ImGui::InputText('input', buf, sizeof(buf)) | local buf = imgui.ImBuffer(256); imgui.InputText('input', buf) |
Динамические массивы в виде массива указателей + количество элементов заменены таблицами | const char* items[] = {"1", "2", "3"}; ImGui::ListBox("list", &lb_cur, items, 3) | imgui.ListBox('list', lb_cur, {'1', '2', '3'}) |
Функции с аргументами const char* str_start, const char* str_end, идущими подряд, принимают обычную строку | ImGui::TextUnformatted(some_str, some_str + 24) | imgui.TextUnformatted(some_str) |
Все функции, принимающие калбэк + user_data, принимают ImCallback | void swszCb(ImGuiSizeConstraintCallbackData*) {}; ImGui::SetNextWindowSizeConstraints(size_min, size_max, &swszCb, (void*)&my_data) | local swszCb = imgui.ImCallback(function(data) end) imgui.SetNextWindowSizeConstraints(size_min, size_max, swszCb) |
ImFont::CalcTextSizeA, ImFontAtlas::CustomRect::CalcCustomRectUV, ImFontAtlas::GetTexDataAsRGBA32, ImFontAtlas::GetTexDataAsAlpha8, ImFontAtlas::GlyphRangesBuilder::BuildRanges, ImGui::ColorConvertRGBtoHSV и ImGui::ColorConvertHSVtoRGB возвращают значения вместо изменения по ссылке | float r, g, b; ImGui::ColorConvertHSVtoRGB(h, s, v, r, g, b); | local r, g, b = imgui.ColorConvertHSVtoRGB(h, s, v) |
ImGuiIO::IniFilename и ImGuiIO::LogFilename принимают ImBuffer вместо указателя на строку | const char ini_path[] = "my/path.ini"; ImGui::GetIO().IniFilename = ini_path; | local ini_path = imgui.ImBuffer('my/path.ini') imgui.GetIO().IniFilename = ini_path |
Изменение ImGuiTextEditCallbackData::Buf автоматически обновляет длину и задаёт значение BufDirty | s.copy(data.Buf, data.BufSize); data.BufTextLen = s.length(); data.BufDirty = true; | data.Buf = 'text' |
Остальные изменения
- Добавлено несколько дополнительных функций
- ImColor::ToU32 - преобразование ImColor в целое
- ImGui::GetStyleColorU32 вместо ImGui::GetColorU32 для цвета стиля
- ImGui::PlotLinesEx вместо callback-варианта PlotLines
- ImGui::PlotHistogramEx вместо callback-варианта PlotHistogram
- ImDrawList::AddTextEx вместо AddText с дополнительными аргументами
- Отсутствуют функции
- Функции, принимающие и возвращающие void*-идентификаторы (например, PushID(void*) и GetID(void*))
- Функции с va_list-форматированием текста (например, TextV)
Но это ещё не всё. Вся работа с текстом в ImGui основана на UTF-8, т.е. текст не ограничен лишь стандартным набором символов. Но т.к. GTA, SAMP и MoonLoader не поддерживают юникод, кодировки необходимо конвертировать.
Работа с другими языками на примере русского
В MoonLoader v.025 были добавлены библиотеки lua-iconv и encoding, они призваны помочь в работе с разными кодировками текста.
Следующий пример показывает как использовать текст на русском в ImGui:
Скрипт должен быть сохранён в кодировке Windows-1251
Lua:
local imgui = require 'imgui'
local encoding = require 'encoding' -- загружаем библиотеку
encoding.default = 'CP1251' -- указываем кодировку по умолчанию, она должна совпадать с кодировкой файла. CP1251 - это Windows-1251
u8 = encoding.UTF8 -- и создаём короткий псевдоним для кодировщика UTF-8
local test_text_buffer = imgui.ImBuffer(256)
function imgui.OnDrawFrame()
imgui.Begin(u8'Основное окно') -- обратите внимание на u8 перед текстом, это и есть преобразование кодировки
if imgui.InputText(u8'Вводить текст сюда', test_text_buffer) then -- условие будет срабатывать при изменении текста
-- здесь первая строка передаётся по-обычному, без u8
-- но введённый текст при выводе преобразуется обратно из UTF-8 в кодировку по умолчанию, т.е. в Windows-1251
print('Введённый текст:', u8:decode(test_text_buffer.v)) -- при работе с ImBuffer тоже не забывайте о .v
end
imgui.Text(u8'Введённый текст: ' .. test_text_buffer.v) -- но тут обратное преобразование введённого текста не требуется, т.к. текст буфера и так в UTF-8
imgui.Text(u8(string.format('Текущая дата: %s', os.date()))) -- u8 - это функция, её можно использовать и с неконстантными строками
imgui.End()
end
function main()
imgui.Process = true
end
Не так уж и сложно, верно? Текст, передаваемый ImGui - кодируем, текст, получаемый из ImGui - декодируем. Если в вашем скрипте много текста на русском для ImGui, но мало текста, выводимого через MoonLoader (в лог или чат SA:MP, к примеру), то можно сделать наоборот - сохранить скрипт в кодировке UTF-8 и не кодировать текст, передаваемый ImGui, а вместо этого кодировать текст при работе с функциями мунлоадера.
Остальные особенности
В Moon ImGui есть несколько дополнительных возможностей. В частности, они реализуют взаимодействие с игрой и управление интерфейсом.
Код:
Параметры
imgui.BeforeDrawFrame = nil - опциональный калбэк. Если он задан, будет вызываться каждый кадр перед OnDrawFrame и NewFrame самого ImGui. Его можно использовать для загрузки шрифтов и текстур (будьте осторожны, он вызывается каждый кадр)
imgui.OnDrawFrame = nil - основной калбэк для рендера, о нём вы уже знаете
imgui.Process = false - только если задано true, имгуи будет обрабатываться и выводиться
imgui.RenderInMenu = false - показывать интерфейс в меню паузы, по умолчанию отключено
imgui.ShowCursor = true - показывать курсор, по умолчанию включен. Отключение может пригодиться, если нужно рисовать только какой-то оверлей без взаимодействия с ним
imgui.LockPlayer = false - отключить управление игроком, пока ImGui активен. Если не задано, то управление игроком будет отключаться только когда ImGui требуется обработать ввод с клавиатуры
Функции
после загрузки новых шрифтов и обязательно вне OnDrawFrame
function imgui.CreateTextureFromFile(path) - загрузить текстуру из файла. Возвращает загруженную текстуру или nil в случае неудачи
function imgui.CreateTextureFromMemory(address, size) - загрузить текстуру из изображения, хранящегося по указанному адресу в памяти. Возвращает загруженную текстуру или nil в случае неудачи
function imgui.GetTextureFromAddress(address) - получить ImGui-совместимую текстуру по адресу памяти
function imgui.ReleaseTexture(texture) - выгрузить текстуру, загруженную с помощью CreateTextureFromFile или CreateTextureFromMemory\
function imgui.RebuildFonts() - пересобрать внутреннюю текстуру шрифтов. Необходимо использовать
function imgui.SwitchContext() - переключить ImGui-контекст на принадлежащий скрипту. В BeforeDrawFrame и OnDrawFrame контекст переключается автоматически, так что в них эту функцию использовать нет нужды.
На этом всё. Во вложениях есть пример с демонстрацией использования всех этих фич, рекомендую посмотреть код и пощупать его в игре. Вот скриншот:
Полезные штуки для разработчиков
FontAwesome 4: https://blast.hk/threads/19292/post-168990
FontAwesome 5: https://blast.hk/threads/19292/post-335148
ImGui Pie: https://blast.hk/threads/19648/post-226145
Внутриигровая песочница: https://blast.hk/threads/19292/post-219453
ImGui Custom (хоткеи и ещё чет): https://blast.hk/threads/22080/
Global notification: https://blast.hk/threads/21619/
Стили:
https://blast.hk/threads/19292/post-260462
Ссылки
Скачать Moon ImGui (Download)
Установка: распаковать содержимое архива в корневую папку игры.
Требуется MoonLoader v.026 или выше
Официальная страница ImGui
Пример на C++ с применением большинства возможностей (imgui_demo.cpp)
Все функции ImGui (imgui.h. Список поддерживаемых также есть в imgui.lua)
Вложения
Последнее редактирование: