Сповіщення
Очистити все

Опис методів створення GUI інтерфейсів


NazarTmm
Ранг:
Досвідчений
Роль:
Модмейкер
Угруповання:
Бандити
Записи:
35
Приєднався:
9 місяців тому
Початок теми  

Почнемо з того, що всі завдання роботи з інтерфейсом зводяться до двох: виведення і введення. Відразу розділимо наші завдання на два принципово різні режими, в яких може перебувати гра.

Перший режим назвемо ігровим. У цьому режимі гравець керує персонажем у тривимірному світі. При цьому введення від миші та клавіатури повністю працює на керування пресонажем. Відповідно, питання завдання введення в цьому режимі не стоїть взагалі. На жаль, розробники не залишили можливості додати у цьому режимі скриптову реакцію на свої кнопки. Якось це питання вирішується, але зараз про це не будемо. Отже, в ігровому режимі можна створювати на екрані поверх тривимірного зображення свої елементи. Взаємодіяти з ними неможливо.

Другий режим назвемо діалоговим. Це режим для головного меню, вбудованих діалогів на зразок інвентарю та користувацьких діалогів. У цьому режимі відкривається вікно, яке повністю перехоплює керування мишею та клавіатурою. Відповідно, в цьому режимі можна організувати введення своїх даних та реагувати на дії з мишею. Події в ігровому світі при цьому можуть зупинитися (у головному меню) або продовжуватися своєю чергою (в усіх інших випадках).

Почнемо з ігрового режиму і спочатку визначимося з термінологією.

У цьому режимі гравець бачить перед собою тривимірну картинку і так званий худий (HUD = heads-up display, веде походження від інтерфейсу військових літаків, при якому інформація відображається на склі огляду, що дозволяє не опускати голову на прилади, тобто голова залишається піднятою (англ. "head up")). Причому, елементами поганого є в тому числі руки і предмет, який актор крутить в руках. Ці об'єкти за своєю природою тривимірні і в цьому двигуні на них впливати не можна (а було б непогано насправді управляти, скажімо, зображенням на екрані детектора, який знаходиться в руках у актора в ЧН і ПП). Так що надалі ми говоритимемо про худе, маючи на увазі лише плоскі елементи інтерфейсу: індикатори здоров'я, зброї, слотів, мінікарту та інше. Якщо точніше, будемо називати худим ту прозору площину, на якій всі ці елементи розташовані.

Якщо це движкові елементи, то керувати ними можна тільки в плані приховати/показати, і тільки разом. Однак при цьому можна додати своїх елементів і вже з ними робити що завгодно. Так можна створювати всякі хитрі індикатори, прицільні мітки, що рухаються, і інше в цьому роді. Усі такі (плоскі) елементи створюються за допомогою вікон, описаних раніше. Створивши вікно як об'єкт, його потрібно розмістити на худому.

Для управління худим є клас CUIGameCustom.

Подібно до деяких інших класів існує всього один глобальний об'єкт такого класу і отримати його можна за допомогою глобальної функції get_hud() (див. опис просторів імен).

class CUIGameCustom {
void AddCustomMessage(string id, float x, float y, float font_sz, CGameFont *font, unsigned short alignment, DWORD color); // только ТЧ
void AddCustomMessage(string id, float x, float y, float font_sz, CGameFont *font, unsigned short alignment, DWORD color, float flicker); // тільки ТЧ
void CustomMessageOut(string id, string msg, DWORD color); // тільки ТЧ
void RemoveCustomMessage(string id); // тільки ТЧ

SDrawStaticStruct* AddCustomStatic(string id, bool single_instance);
SDrawStaticStruct* GetCustomStatic(string id);
void RemoveCustomStatic(string id);
void AddDialogToRender(CUIWindow* wnd);
void RemoveDialogToRender(CUIWindow* wnd);

void HidePdaMenu(); // тільки ПП
void HideActorMenu(); // тільки ПП
void show_messages(); // тільки ПП
void hide_messages(); // тільки ПП
};

Цей клас дає два методи для розмішення на погані своїх елементів.

Метод перший та історично найпоширеніший. Використовуються функції AddCustomStatic, RemoveCustomStatic та GetCustomStatic.

Тепер по порядку:

Є файл xml з описами створюваних елементів. Це файл configs\ui\ui_custom_msgs.xml, ім'я якого прописано прямо в движку.
Необхідно внести до цього файлу опис свого елемента і дати йому ім'я. Дивіться, як це робиться. Коротко створюється новий вузол XML з атрибутами і підвузлами, що містять всі необхідні описи. Ім'я тега буде ім'ям нашого елемента. Можна використовувати файли, що включаються (за допомогою #include <ім'я файлу>), щоб мінімально змінювати оригінальний файл.
тепер під час виконання отримуємо об'єкт погано так
local hud = get_hud()

і створюємо наш елемент так

local st = hud:AddCustomStatic(<ім'я елемента>, true)

при цьому створюється вікно типу CUIS tatic за описом з XML і автоматично розміщується на худому. Об'єкт, що повертається, має тип SDrawStaticStruct. Його опис:

class SDrawStaticStruct {
    float m_endTime;
    CUIStatic* wnd();
};

Тут нам переважно цікавий метод wnd, який повертає покажчик на створений CUIStatic. Уважно вивчіть можливості цього контролю, вони досить великі.
Видалити елемент можна так

hud:RemoveCustomStatic(<ім'я елемента>)

отримати будь-якої миті можна так

local st = hud:GetCustomStatic(<ім'я елемента>)

хоча можна просто запам'ятати посилання на елемент, отриману під час створення.

Власне, все. За допомогою опису в XML можна зобразити щось просте, але якщо вам потрібна динаміка або складні елементи, ніщо не заважає розмістити на створеному вікні додаткові дочірні вікна за допомогою AttachChild. При цьому можна зробити опис у XML мінімальним, фактично визначивши тільки координати вікна і більше нічого, а все, що потрібно зробити пізніше за допомогою дочірніх вікон. Цей метод багатьом хороший, але у нього є один невеликий недолік. Всі створені таким чином елементи будуть розташовані під стандартними поганими елементами. А іноді потрібно створити елемент, який перекриває стандартні індикатори та діалоги.

Для цього є другий метод з використанням функцій AddDialogToRender та RemoveDialogToRender. Послідовність роботи така:

створюємо будь-яким методом вікно будь-якого типу
wnd = <будь-яке вікно>

розміщуємо його на худому так
get_hud():AddDialogToRender(wnd)

коли захочемо, прибираємо з лиха так
get_hud():RemoveDialogToRender(wnd)

Важливо: Після приміщення вікна на худий таким способом обов'язково треба якось зберегти посилання на об'єкт вікна аж до його видалення з лиха.

Якщо втратити посилання, то збирач сміття видалить вікно, і гра впаде.

Ще зауваження: і першим і другим способом можна розміщувати на найгіршому скільки завгодно складні вікна, в тому числі з кнопками, полями введення та іншим в цьому роді.

Але звісно, де вони реагувати на користувача, тобто. працюватимуть лише на висновок.

Тепер про діалоговий режим. Тут все крутиться довкола класу CUIScriptWnd. Основна ідея полягає у створенні на основі цього класу свого скриптового. Не буду в подробицях це описувати (може потім якось). Є безліч прикладів, те саме головне меню зроблено саме так (файл ui_main_menu.script). Як найпростіший і наочний приклад рекомендую ui_numpad.script. Це діалогове вікно з панеллю введення коду дверей.

Воно просте і водночас має всі необхідні елементи: кнопки, поле введення, обробку мишачих подій та натискання клавіатури. Загалом, варто його спершу уважно вивчити.

Я зараз лише розгляну виведення створеного вікна на екран. У випадку з основним меню його запускає сам двигун. Прописування користувача класу в цьому випадку здійснюється через реєстрацію клієнтського класу в class_registrator.script. Це там завжди вже зроблено, і це єдине вікно, яке відкривається таким чином. Всі інші вікна користувача треба відкривати самому. Це робиться по-різному в різних версіях двигуна.

У ТЧ

Допустимо, у вас є скриптовий клас CMyDialog, зроблений на основі CUIScriptWnd. Спочатку ви створюєте об'єкт такого класу

local dlg = CMyDialog() -- конструктор може бути з параметрами, оскільки ви його самі написали

потім запускаємо діалог

level.start_stop_menu(dlg, true/false)

або так

dlg:GetHolder():start_stop_menu(dlg, true/false)

різниці ніякої, це фактично одна й та сама функція. Другий аргумент визначає, чи будуть прибрані стандартні елементи погана: індикатори та інше. закрити діалог можна викликавши ту ж функцію вдруге, другий булівський аргумент при цьому значення не має.

ЧН я не розглядаю, але там схоже на ТЧ

У ПП все ще простіше, у діалогу є потрібні методи ShowDialog і HideDialog.

Спочатку як зазвичай створюємо об'єкт діалогу

local dlg = CMyDialog()

потім запускаємо його

dlg:ShowDialog(true/false) - аргумент як і раніше дозволяє приховати індикатори

закриваємо діалог

dlg:HideDialog()

От і все. Інші подробиці для діалоги дивись на прикладах.

У неохопленому залишилася тема про створення вікон на основі опису XML. Для цього є клас CScriptXmlInit. Він сам по собі нескладний, але до нього треба ще й описувати формати XML-файлів для різних вікон, а до цього я зараз морально не готовий.

Також є ще досить складна тема про модифікацію стандартних вікон: торгівлі, PDA та інше. На допомогу може прийти функція level.main_input_receiver(), яка повертає поточне активне вікно. Прийоми роботи у разі досить нетривіальні. З іншого боку, в ПП цієї функції немає.

У ТЧ та ЧН були глобальні функції GetTextureInfo, GetTextureName та GetTextureRect. Це означає на ім'я текстури отримати її файл (наче це полотном називають) і координати прямокутника. Нагадую, що в такому розумінні текстура це те, що зазвичай вказується в описах XML різних вікон. Там зазвичай прописується ім'я, яке посилається на окремий опис (в іншому XML файлі), де вже й задається ім'я файлу і координати прямокутника. Ці функції таки дозволяють отримати цю інформацію. Особливої користі від цих функцій не було, оскільки не було жодної функції, яка б повертала ім'я текстури.

І ось у ЧН така функція з'явилася! Для онлайнових об'єктів сталкерів можна отримати іконку персонажа за допомогою функції character_icon. Це дуже потрібна функція, оскільки дає інформацію про реальний профіль сталкера. Так що ви думаєте! У ПП розробники взяли та прибрали функції отримання інформації про текстури. І який тепер толк від тієї функції отримання іконки? XML прочитати не можна, інакше логічне ім'я ні про що не говорить. Немає слів, одні вигуки.

 


   
Fenomen відреагував
Цитата
Теги теми