Перейти к основному содержимому

Интерфейс

Введение

Названия объектов интерфейса на сцене имеют очень важную роль. По этим названиям Вы будете обращаться к конкретному объекту для работы с ним (показать, скрыть, модифицировать).

Наша система поддерживает основные компоненты, доступные в Unity для работы с интерфейсами:

  • RectTransform
  • TextMeshProUGUI
  • Image
  • Button
  • ContentSizeFitter
  • GridLayoutGroup
  • HorizontalLayoutGroup
  • VerticalLayoutGroup

Помимо этих компонентов, есть кастомные, которые были немного модифицированы нами, но сохраняют свою логику:

  • ScrollView - окно с возможностью скроллинга. Хорошо подходит для создания списков.
  • Slider - полоска для выбора количества чего-либо.
  • InputField - поле для ввода текста.

Для использования кастомных компонентов, нужно перетащить соответствующий префаб из папки /ServerPrefabs/customUIcomponents в проекте Unity.

Эти несколько компонентов были сделаны кастомными для того, чтобы допустить изменение лишь определенных вещей внутри них.

Типы названий

Для удобства и понимания, а также хорошей обратной совместимости между будущими версиями Kotiki Server, рекомендуется в названиях указывать тип объекта, которому присваивается кастомное название:

  • Text - если объект имеет компонент TextMeshProUGUI
  • Block - если объект является блоком (например, отдельное окно, которое то показывается игроку, то скрывается).
  • Button - если объект имеет компонент Button

Примеры:

  • Block:Window1
  • Text:Text1
  • Button:Button1

Гайд по названиям

Название, которое Вы присвоили объекту, является как бы ссылкой на этот объект.

Мы не советуем использовать одинаковые названия для разных объектов, если планируется обращение к ним с сервера, так как команда будет выполнена для всех объектов с данным названием

Допустим, есть объект Block:Window1, который является прямоугольным окном в центре экрана. И есть объект с компонентом Button (кнопка).

Если мы хотим выполнить простую операцию, а именно показать Window1 пользователю (SetActive(true)) при нажатии на Button1, мы можем сделать это следующим образом:

Переименовать название кнопки на Button:open(Block:Window1)

Возможные команды:

  • :open(название) - применить SetActive(true) для объектов с названием "название"
  • :close(название) - применить SetActive(false) для объектов с названием "название"
  • :input(название) - при нажатии на кнопку с таким ключом, на сервер передастся событие inputSent с аргументами ButtonName, InputName и InputValue. В "название" находится название CustomInputField, чтобы значение передалось в InputValue.

Можно использовать несколько раз таким образом: :input(name1):input(name2):input(name3).

  • :slider(название) - при нажатии на кнопку с таким ключом, на сервер передастся событие inputSent с аргументами ButtonName, SliderName и SliderValue. В "название" находится название CustomSlider, чтобы значение передалось в SliderValue.

Можно использовать несколько раз таким образом: :slider(name1):slider(name2):slider(name3).

Events.on('buttonClicked', (pID, pRef, buttonName, inputs, sliders) => {
//inputs - Массив со всеми CustomInputField, которые были упомянуты в названии кнопки через :input(name)
//sliders - Массив со всеми CustomSlider, которые были упомянуты в названии кнопки через :slider(name)
return;
});

Не используйте в названиях такие символы: #, ., а также символ пробела. Лучше использовать только заглавные и обычные латинские буквы.

Гайд по кнопкам

При нажатии на кнопку, на сервер отправляется событие нажатия на кнопку (с названием кнопки). Таким образом можно узнать, какой игрок нажал на какую кнопку. Чтобы не отправлять событие на сервер, нужно добавить в конец названия кнопки :Silent.

Пример:

Button:open(Block:Window1):Silent - открыть окно с названием Block:Window1, но не отправлять на сервер событие о нажатии на кнопку.

Серверные команды

Для отправки игроку команд с сервера необходимо использовать цепочку методов (Method Chaining), которая начинается с GUI()., и далее идут операции по-очереди, каждая из которых завершается методом .next(), после чего использовается метод send(playerID) для отправки всей команды на клиент конкретного игрока с нужным ID.

Формирование цепочки запросов

Примерная команда для интерфейса выглядит так:

GUI().звено1().звено2()...звеноN().next().send(123);

GUI()

Начальное звено, которое означает инициализацию новой цепочки запросов.

next()

Означает завершение текущего действия в цепочке запросов. Обязательно использовать между запросами и перед вызовом send().

send(pID)

  • pID (int) - ID игрока, которому нужно отправить запросы.

Вызывается в конце цепочки запросов, после последнего next(). Без вызова этого метода пользователь не получит команду на изменения в своем интерфейсе, поэтому любая цепочка запросов завершается этим методом.

get(name)

  • name (string) - название объекта интерфейса.

Находит объекты в интерфейсе с указанным названием для выполнения команды, которая последует после. Если объектов с таким названием несколько, команда будет выполнена для каждого из этих объектов.

setActive(isActive)

  • isActive (bool) - true для активации объекта, false для деактивации.

Показывает или скрывает объект на клиенте игрока. Аналог команды в Unity SetActive(isActive).

Пример:

  • Включить (отобразить) объект с названием Block:Window1 игроку с ID 123:
GUI().get("Block:Window1").setActive(true).next().send(123);

setText(text)

  • text (string) - текст, который нужно указать.

Меняет значение текста в компоненте TextMeshProUGUI. Поддерживает Rich Text.

Пример:

  • Установить текст "Hello, World!" в компоненте TextMeshProUGUI для объекта с названием Text:Text1 и чтобы это увидел игрок с ID 123:
GUI().get("Text:Text1").setText("Hello, World!").next().send(123);

setInputPlaceholder(text)

  • text (string) - текст, который нужно указать.

Меняет значение текста в Placeholder в компоненте CustomInputField. Поддерживает Rich Text.

Пример:

  • Установить текст "Hello, World!" в компоненте CustomInputField для объекта с названием InputField:InputField1 и чтобы это увидел игрок с ID 123:
GUI().get("InputField:InputField1").setInputPlaceholder("Hello, World!").next().send(123);

setInputText(text)

  • text (string) - текст, который нужно указать.

Меняет значение текста в text в компоненте CustomInputField. Поддерживает Rich Text.

Пример:

  • Установить текст "Hello, World!" в компоненте CustomInputField для объекта с названием InputField:InputField1 и чтобы это увидел игрок с ID 123:
GUI().get("InputField:InputField1").setInputText("Hello, World!").next().send(123);

setSliderValue(value)

  • value (float) - числовое значение, который нужно указать.

Меняет значение value в компоненте CustomSlider.

Пример:

  • Установить значение 1 в компоненте CustomSlider для объекта с названием Slider:Slider1 и чтобы это увидел игрок с ID 123:
GUI().get("Slider:Slider1").setSliderValue(1).next().send(123);

getElementByIndex(index)

  • index (int) - индекс дочернего объекта.

Метод для работы с созданными копиями префаба (например, для списка).

Находит дочерний объект по его индексу внутри родительского объекта, который был получен с помощью метода get().

Правила использования

Является продолжением метода get().

Можно использовать только после того, как был получен объект с помощью метода get().

Можно использовать только один раз в одном запросе.

За этим методом обязательно должен следовать метод getElementByName().

getElementByName(name)

  • name (string) - название объекта, который находится где-то внутри конкретной копии префаба.

Метод для работы с созданными копиями префаба (например, для списка).

После получения объекта с помощью методов get() и далее getElementByIndex(), метод getChildByName() будет искать где-либо внутри найденного до этого объекта другой объект с указанным названием.

Этот метод используется для того, чтобы модифицировать один из объектов внутри созданного из префаба объекта. Например, чтобы установить специфический заголовок для каждой созданной копии префаба, или назначить уникальный ID для кнопки внутри каждой созданной копии префаба, чтобы понимать на сервере, на какую копию нажал игрок.

Правила использования

Является продолжением метода getElementByIndex().

Можно использовать только один раз в одном запросе.

Перед этим методом обязательно должен быть метод getElementByIndex().

addButtonEvent(json)

  • json (object) - json-объект, который будет отправлен клиентом на сервер при нажатии кнопки.

Этот метод добавляет кнопке команду на отправку указанного json на сервер, если игрок нажимает на эту кнопку.

Пример:

  • При нажатии на кнопку Button:Button1, клиент игрока с ID 123 отправит json-объект {"key1": "value1", "key2": "value2"} на сервер.
GUI().get("Button:Button1").addButtonEvent({"key1": "value1", "key2": "value2"}).next().send(123);

setSprite(spriteHash)

  • spriteHash (string) - hash-строка, которая ссылается на исходный спрайт.

Изменяет .sprite в Unity-компоненте Image на другой спрайт, которому соответствует hash-строка из spriteHash.

Встроенные значения

Вместо hash-строки можно использовать такие значения: UISprite, Background, UIMask, Knob, InputFieldBackground, встроенные в сам Unity Editor.

Пример:

  • Изменить текущий спрайт на UISprite для компонента Image внутри объекта Image:Image1 для клиента игрока с ID 123.
GUI().get("Image:Image1").setSprite("UISprite").next().send(123);

setSpriteColor(color)

  • color (array) - массив из 4 int-элементов, означающий RGBA для цвета спрайта [0-255, 0-255, 0-255, 0-255].

Изменяет цвет (.color) в Unity-компоненте Image на указанный в color в формате RGBA.

Примеры значений color:

  • [255, 255, 255, 255] - белый.
  • [255, 255, 255, 120] - полупрозрачный белый.
  • [0, 0, 0, 255] - черный.
  • [0, 0, 0, 120] - полупрозрачный темный.
  • [255, 0, 0, 255] - красный.

Для "невидимости", достаточно указать 0 в качестве значения последнего элемента массива color:

[255, 255, 255, 0]

Пример:

  • Изменить цвет спрайта на красный для компонента Image внутри объекта Image:Image1 для клиента игрока с ID 123.
GUI().get("Image:Image1").setSpriteColor([255, 0, 0, 255]).next().send(123);

clear()

Метод удаляет дочерние объекты из текущего объекта. Используется для очистки списка копий префабов, чтобы далее пересоздать его на актуальные версии копий. То есть это "очистка списка".

Пример:

  • Удалить все дочерние объекты из объекта Content:Content1 для клиента игрока с ID 123.
GUI().get("Content:Content1").clear().next().send(123);

add(prefabName)

  • prefabName (string) - название префаба.

Метод создает копию указанного префаба, и делает эту копию дочерней для текущего объекта.

Пример:

  • Очистить список Content:Content1 и добавить в него новый дочерний объект в виде копии префаба Prefab:PrefabName1 для клиента игрока с ID 123.
GUI().get("Content:Content1").clear().next()
.get("Content:Content1").add("Prefab:PrefabName1").next()
.send(123);

Объединение команд

Если нужно, команды можно объединить вместе.

Пример:

Данный пример удалит дочерние объекты у объекта Content:Content1, а затем создаст 10 клонов префаба Prefab:PrefabName1 и поместит их внутрь Content:Content1 (сделает их дочерними объектами). В текстовый объект в каждом клоне с названием :Text1 будет установлен его индекс, а также при нажатии на кнопку с названием Button внутри каждого клона будет выставлено событие нажатия на эту кнопку. Нажатие отправит json-объект {"event": ID}, где ID - индекс клона префаба. Затем вся эта команда будет отправлена игроку с ID 123.

let command = GUI().get("Content:Content1").clear().next();

for (let i = 0; i < 10; i++) {
let _id = i;
command = command.get("Content:Content1").add("Prefab:PrefabName1").next();

command = command.get("Content:Content1").getChildByIndex(i).getChildByName(":Text1").setText(""+_id).next();

command = command.get("Content:Content1").getChildByIndex(i).getChildByName("Button").addButtonEvent({"event": _id}).next();
}

command.send(123);

Советы

Комбинируйте запросы

По возможности соединяйте команды для конкретного игрока в виде одной длинной цепочки запросов. Это уменьшит количество отправляемых пакетов данных с сервера.

Используйте постраничное отображение данных

Лучше отобразить 10 элементов в списке со стрелками назад и вперед, чем сразу добавлять 100 элементов в список. По возможности избегайте добавления большого числа элементов в список, особенно если планируете далее модифицировать их.

Префабы только для списков

Лучше сразу иметь все возможные панели интерфейса в отключенном (скрытом) виде, а потом отображать их игроку через setActive(), чем создавать их с помощью префабов и команды add().

Создание новых объектов нагружает устройство игрока.

Мы рекомендуем использовать add() только при заполнении списков клонами префабов, а не для динамичного отображения панелей интерфейса.

Большие изображения

Чтобы игрок быстрее подключался к игровому режиму, нужно избегать использования больших изображений. Если это возможно, лучше использовать встроенные в Unity UISprite, Background и UIMask. С помощью них можно создавать неплохие блоки и рамки, примеры которых можно будет изучить в разделе с готовыми примерами.

Частота запросов

Не рекомендуется отправлять с помощью метода send() команды конкретному игроку чаще, чем 10 раз в секунду.

Не рекомендуется вызывать send() для конкретного игрока чаще, чем 1 раз каждый тик сервера.