GUI: Кординаты (Часть 2)

Автор: Vlad333000 участник нашего Discord.

В каких координатах работать?

Для начала давайте определимся в каких координатах работать. Начнем с перечисления плюсов и минусов каждой системы координат:

Абсолютная

Плюсы:

  • При использовании координат в диапазоне [0, 1] гарантирует то, что элементы интерфейса не будут вылезать за границы экрана;
  • Масштабируется с помощью параметра «GUI Scale» в настройках;
  • Простой;
  • Позволяет сохранять соотношение сторон 4:3;

Минусы:

  • Не позволяет создавать элементы относительно границ монитора;
  • Не позволяет создавать элементы относительно размеров монитора;
  • Не позволяет создавать элементы, которые соответствовали бы пикселям на экране;
  • Не позволяет работать с системами из трех мониторов;
  • Позволяет сохранять соотношение сторон только 4:3 (Без сложных расчетов);

SafeZone

Плюсы:

  • Позволяет создавать элементы относительно границ монитора;
  • Позволяет создавать элементы относительно размеров монитора;
  • Позволяет работать с системами из трех мониторов;
  • Гарантирует что элементы будут на экране, если не сделаете обратное сами;

Минусы:

  • Не позволяет создавать элементы, которые соответствовали бы пикселям на экране;
  • Не позволяет сохранять соотношение сторон (Без сложных расчетов);
  • Не масштабируется с помощью параметра «GUI Scale» в настройках;

GUI_GRID

Плюсы:

  • Масштабируется с помщью параметра «GUI Scale» в настройках;
  • При использовании координат x в диапазоне [GUI_GRID_X, GUI_GRID_X + GUI_GRID_WAbs] и y в диапазоне [GUI_GRID_Y, GUI_GRID_Y + GUI_GRID_HAbs] гарантирeт то, что элементы не вылезут за границы монитора;
  • Позволяет сохранять соотношение сторон;

Минусы:

  • Не позволяет создавать элементы относительно границ монитора;
  • Не позволяет создавать элементы относительно размеров монитора;
  • Не позволяет создавать элементы, которые соответствовали бы пикселям на экране;
  • Не позволяет работать с системами из трех мониторов;
  • Работает только при включении специального файла с помощью дериктивы прероцессора #include;

Pixel Grid System

Плюсы:

  • Масштабируется с помощью параметра «GUI Scale» в настройках;
  • При использовании координат x в диапазоне [0, 80 * pixelGrid * pixelW] и y в диапазоне [0, 60 * pixelGrid * pixelH] гарантируeт то, что элементы не вылезут за границы монитора (Тяжело подобрать разрешение, которое бы давало меньшее, число клеток);
  • Позволяет сохранять соотношение сторон;
  • Позволяет создавать элементы, которые соответствовали бы пикселям на экране;

Минусы:

  • Не позволяет создавать элементы относительно границ монитора;
  • Не позволяет создавать элементы относительно размеров монитора;

Итог

  1. Абсолютные, GUI_GRID и Pixel Grid можно использовать как самостоятельные системы координат только тогда, когда нужны элементы в центре монитора;
  2. Абсолютные, GUI_GRID и Pixel Grid взаимозаменяемые системы координат, а не взаимодополняемые, их смешивание лишено смысла;
  3. SafeZone может использоваться как самостоятельная система координат, когда нужно покрыть какой-то участок монитора/нескольких мониторов относительно их размеров;
  4. SafeZone может использоваться как вспомогательная система координат для других систем, когда нужно создать элементы в любой точке монитора/нескольких мониторов;
  5. Не используйте SafeZone как основную систему координат, если необходимо сохранение пропорций;
  6. Используйте Pixel Grid когда нужна точность вплоть до пикселей;
  7. Используйте Pixel Grid вместо GUI_GRID, т. к. данная система координат является ее полной заменой и имеет более широкие возможности;
  8. Используйте абсолютную систему координат только тогда, когда скорость и простота важнее качества (Например, тестирование, наброски, простое окошко «Да-Нет?» и т. д.);

В итоге получаем завершенный набор правил:

  • Не используем GUI_GRID;
  • Используем абсолютную систему координат только на ранних этапах разработки, преимущественно, для набросков и тестрования функционала, но не в готовом продукте;
  • Используем SafeZone что бы закрывать участки монитора или полноэкранных эффектов;
  • Используем SafeZone вместе с Pixel Grid для всего остального;

Использование SafeZone и Pixel Grid

Алогоритм, который будет предложен далее может быть избыточным, но он позволяет очень просто и пошагово настроить положение и размер любого элемента интерфейса

1. Базовая точка

Базовая точка — это такая точка на любом объекте, относительно которой мы будем позиционаровать наш элемент. Примерами таких точек могут служить: границы монитора, центр какого-то объекта, MemoryPoint в модели и т. д.

В рамках интерфейса можно выделить три основных базовых объекта:

  • Монитора (Левый/Центральный/Правый в случае с системой с тремя мониторами);
  • Элемента интерфейса (Картинка, поле для ввода и т. д.);
  • Родительский элемент интерфейса (control типа RscControlsGroup внутри которого мы хотим позиционировать наш элемент);

Базовых точек на этих объектах может быть сколь угодно много, но существуют 9 точек, который покрывают 99% всех потребностей:

Что бы перейти к любой из этих точек необходимо сдвинуть начало координат к ней. Этого можно достигнуть путем прибавления смещения к исходным координатам x и y базового объекта:

Точка x y
1 x = X0 + 0.0 * W0 y = Y0 + 0.0 * H0
2 x = X0 + 0.0 * W0 y = Y0 + 0.5 * H0
3 x = X0 + 0.0 * W0 y = Y0 + 1.0 * H0
4 x = X0 + 0.5 * W0 y = Y0 + 0.0 * H0
5 x = X0 + 0.5 * W0 y = Y0 + 0.5 * H0
6 x = X0 + 0.5 * W0 y = Y0 + 1.0 * H0
7 x = X0 + 1.0 * W0 y = Y0 + 0.0 * H0
8 x = X0 + 1.0 * W0 y = Y0 + 0.5 * H0
9 x = X0 + 1.0 * W0 y = Y0 + 1.0 * H0

Пояснение используемых переменных в выражении:

  • x — переменная или свойство класса, которое используется для позиционирования элемента по оси X;
  • y — переменная или свойство класса, которое используется для позиционирования элемента по оси Y;
  • X0 — базовая точка объекта по оси X, относительно которого мы выбираем точку;
  • Y0 — базовая точка объекта по оси Y, относительно которого мы выбираем точку;
  • W0 — ширина базового объекта;
  • H0 — высота базового объекта;

Примеры переменных, которые необходимо использовать

Базовый объект X0 Y0 W0 H0
Центральный монитор safeZoneX safeZoneY safeZoneW safeZoneH
Левый монитор safeZoneXAbs safeZoneY (safeZoneWAbs - safeZoneW) / 2 safeZoneH
Правый монитор safeZoneX + safeZoneW safeZoneY (safeZoneWAbs - safeZoneW) / 2 safeZoneH
Элемент интерфейса x этого элемента y этого элемента w этого элемента h этого элемента
Родительский элемент интерфейса 0.0 0.0 w этого элемента h этого элемента

2. Размер элемента

Выполнив предыдущий щаг мы создали свою виртуальную «систему координат», в которой 0 находится в указанной точке. Теперь нам необходимо определиться с размерами нашего элемента, выбираем любой размер, который нравиться (A и B простые числа-коэффициенты):

w = A * W0;                                 // A% от ширины базового объекта, A от 0.0 до 1.0
h = A * H0;                                 // B% от высоты базового объекта, A от 0.0 до 1.0

w = A * pixelGrid * pixelW;                 // А клеток в ширину, с возможностью масштабирования интерфейса настройками
h = B * pixelGrid * pixelH;                 // B клеток в высоту, с возможностью масштабирования интерфейса настройками

w = A * pixelGridNoUIScale * pixelW;        // А клеток в ширину, без возможностью масштабирования интерфейса настройками
h = B * pixelGridNoUIScale * pixelH;        // B клеток в высоту, без возможностью масштабирования интерфейса настройками

w = A * pixelGridBase * pixelW;             // А клеток в ширину, без возможностью масштабирования интерфейса настройками и без влияния конфига
h = B * pixelGridBase * pixelH;             // B клеток в высоту, без возможностью масштабирования интерфейса настройками и без влияния конфига

w = A * pixelW;                             // A пикселей в ширену
h = B * pixelH;                             // B пикселей в высоту

w = A * safeZoneW;                          // A% от ширины центрального монитора, A от 0.0 до 1.0
h = B * safeZoneH;                          // B% от высоты центрального монитора, B от 0.0 до 1.0

w = A * safeZoneWAbs;                       // A% от ширины трех мониторов монитора, A от 0.0 до 1.0
h = B * safeZoneH;                          // B% от высоты монитора, B от 0.0 до 1.0

w = A * ((safeZoneWAbs - safeZoneW) / 2);   // A% от ширины левого/правого монитора, A от 0.0 до 1.0
h = B * safeZoneH;                          // B% от высоты левого/правого монитора, B от 0.0 до 1.0

3. Положение элемента

После того как определились с размером элемента, необходимо определиться с расположением нашего элемента относительно выбранной нами точки — в центре или сдвинуть в какую-либо сторону:

Положение: в центре. Базовые точки: 5

Положение: Со сдвигом по горизонтали. Базовые точки: 2, 8

Положение: Со сдвигом по вертикали. Базовые точки: 4, 6

Положение: Со сдвигом по главной диагонали. Базовые точки: 1, 9

Положение: Со сдвигом по помобчной диагонали. Базовые точки: 3, 7

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

Положение x y
В центре Не изменяется Не изменяется
Со сдвигом по горизонтали влево x = x - XD Не изменяется
Со сдвигом по горизонтали вправо x = x + XD Не изменяется
Со сдвигом по вертикали вверх Не изменяется y = y - YD
Со сдвигом по вертикали вниз Не изменяется y = y + YD
Со сдвигом по главной диагонали вверх x = x - XD y = y - YD
Со сдвигом по главной диагонали вниз x = x + XD y = y + YD
Со сдвигом по побочной диагонали вверх x = x + XD y = y - YD
Со сдвигом по побочной диагонали вниз x = x - XD y = y + YD

Пояснение используемых переменных в выражении:

  • x — переменная или свойство класса, которое используется для позиционирования элемента по оси X;
  • y — переменная или свойство класса, которое используется для позиционирования элемента по оси Y;
  • w — переменная или свойство класса, которое используется для обозначения ширина нашего элемента;
  • h — переменная или свойство класса, которое используется для обозначения высоты нашего элемента;
  • XD — необходимое смещение по оси X;
  • YD — необходимое смещение по оси Y;

Примеры переменных XD:

XD = K * W0;                                // K% от ширины базового объекта, K больше 0.0
XD = K * w;                                 // K% от ширины нашего элемента, K больше 0.0
XD = K * pixelGrid * pixelW;                // K клеток, с возможностью масштабирования интерфейса настройками
XD = K * pixelGridNoUIScale * pixelW;       // K клеток, без возможностью масштабирования интерфейса настройками
XD = K * pixelGridBase * pixelW;            // K клеток, без возможностью масштабирования интерфейса настройками и без влияния конфига
XD = K * pixelW;                            // K пикселей
XD = K * safeZoneW;                         // K% от ширины центрального монитора, K от 0.0 до 1.0
XD = K * safeZoneWAbs;                      // K% от ширины трех мониторов монитора, K от 0.0 до 1.0
XD = K * ((safeZoneWAbs - safeZoneW) / 2);  // K% от ширины левого/правого монитора, K от 0.0 до 1.0

Примеры переменных YD:

YD = K * H0;                                // K% от высоты базового объекта, K больше 0.0
YD = K * h;                                 // K% от высоты нашего элемента, K больше 0.0
YD = K * pixelGrid * pixelH;                // K клеток, с возможностью масштабирования интерфейса настройками
YD = K * pixelGridNoUIScale * pixelH;       // K клеток, без возможностью масштабирования интерфейса настройками
YD = K * pixelGridBase * pixelH;            // K клеток, без возможностью масштабирования интерфейса настройками и без влияния конфига
YD = K * pixelH;                            // K пикселей
YD = K * safeZoneH;                         // K% от высоты монитора, K от 0.0 до 1.0

4. Переход от центра элемента к верхнему-левому углу

Все необходимые операции завершены. Остался один момент — в игре для позиционирования элементов интерфейса используется левый-верхний угол, а мы получили центральную точку, поэтому нам необходимо выполнить следующие:

x = x - w / 2;
y = y - h / 2;

Пояснение используемых переменных в выражении:

  • x — переменная или свойство класса, которое используется для позиционирования элемента по оси X;
  • y — переменная или свойство класса, которое используется для позиционирования элемента по оси Y;
  • w — переменная или свойство класса, которое используется для обозначения ширина нашего элемента;
  • h — переменная или свойство класса, которое используется для обозначения высоты нашего элемента;

Примеры:

В рамках примеров будем использовать скриптовую версию. Шаблон скрипта, который можно вставить в консоль отладки:

with uiNamespace do {
    // Создаем пустой дисплей
    private _display = (findDisplay 46) createDisplay "RscDisplayEmpty";

    // Создаем элемент, на котором будем показывать примеры
    private _ctrl = _display ctrlCreate ["RscText", -1];

    _ctrl ctrlSetBackgroundColor [0.25, 0, 0, 0.7];
    _ctrl ctrlSetPosition [0, 0, 0, 0];
    _ctrl ctrlCommit 0;

    // Переменные для настроек
    private ["_x", "_y", "_w", "_h"];

    // Далее вставляем код из примера
    ...

    // Настраиваем элемент
    _ctrl ctrlSetPosition [_x, _y, _w, _h];
    _ctrl ctrlCommit 0;
};

Пример №1

Задача: создать элемент в центре со сторонами 800×600 пикселей.

Шаг 1. Выбираем базовый объект и точку на нем

Так как нам необходимо что бы элемент был в центре, то в качестве опорного объекта лучше всего выбрать центральный монитор и его центральную точку №5. Согласно таблице для точки №5 возьмем следующие формулы:

x = X0 + 0.5 * W0;
y = Y0 + 0.5 * H0;

Теперь обратимся ко второй таблице, в которой привидены примеры переменных, нам нужен центральный монитор:

Базовый объект X0 Y0 W0 H0
Центральный монитор safeZoneX safeZoneY safeZoneW safeZoneH

Подставляем указанные значения в формулу и запишем их в наш скрипт:

_x = safeZoneX + 0.5 * safeZoneW;
_y = safeZoneY + 0.5 * safeZoneH;

На данном этапе мы еще не можем вывести элемент, так как не хватает высоты и ширины.

Шаг 2. Выбираем размер элемента

Наша цель элемент размером 800 на 600 пикселей, находим соответствующий пример значений w и h, и подставляем значения:

w = A * pixelW;
h = B * pixelH;

Получаем:

_x = safeZoneX + 0.5 * safeZoneW;
_y = safeZoneY + 0.5 * safeZoneH;
_w = 800 * pixelW;
_h = 600 * pixelH;

Теперь мы можем вывести наш элемент:

Шаг 3. Выбираем положение элемента

Так как нам нужен элемент в центре, то и выбираем соответствующее положение «В центре» и получаем то, что координаты изменять не надо.

_x = safeZoneX + 0.5 * safeZoneW;
_y = safeZoneY + 0.5 * safeZoneH;
_w = 800 * pixelW;
_h = 600 * pixelH;

Теперь мы можем вывести наш элемент:

Шаг 4. Переходим к верхнему-левому углу

Имеем формулу:

x = x - w / 2;
y = y - h / 2;

Подставляем ее в наш скрипт и получаем:

_x = safeZoneX + 0.5 * safeZoneW;
_y = safeZoneY + 0.5 * safeZoneH;
_w = 800 * pixelW;
_h = 600 * pixelH;
_x = _x - _w / 2;
_y = _y - _h / 2;

И получаем:

Шаг 5. Проверка

Проверяем элемент при разных разрешениях:

Разрешение: 800×600

Разрешение: 1280×720

Разрешение: 1920×1080

Пример №2

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

Шаг 1. Выбираем базвый объект и точку на нем

Так как нам необходимо что бы элемент был в центре сверху, то в качестве опорного объекта лучше всего выбрать центральный монитор и его центральную точку №4. Согласно таблице для точки №4 возьмем следующие формулы:

x = X0 + 0.5 * W0;
y = Y0 + 0.0 * H0;

Теперь обратимся ко второй таблице, в которой привидены примеры переменных, нам нужен центральный монитор:

Базовый объект X0 Y0 W0 H0
Центральный монитор safeZoneX safeZoneY safeZoneW safeZoneH

Подставляем указанные значения в формулу и запишем их в наш скрипт:

_x = safeZoneX + 0.5 * safeZoneW;
_y = safeZoneY + 0.0 * safeZoneH;

На данном этапе мы еще не можем вывести элемент, так как не хватает высоты и ширины.

Шаг 2. Выбираем размер элемента

Нам необходим прямоугольник 8 на 4 клетки с масштабированием, для этого возьмем следующую формулу из примеров:

w = A * pixelGrid * pixelW;
h = B * pixelGrid * pixelH;

Подставим необходимые значения и запишем в скрипт:

_x = safeZoneX + 0.5 * safeZoneW;
_y = safeZoneY + 0.0 * safeZoneH;
_w = 8 * pixelGrid * pixelW;
_h = 4 * pixelGrid * pixelH;

Попробуем вывести:

Шаг 3. Выбираем положение элемента

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

x - Не изменяется
y = y + YD

Подбираем нужные переменные в списке далее:

YD = K * h;

Подставляем значения в формулу и вставляем ее в скрипт:

_x = safeZoneX + 0.5 * safeZoneW;
_y = safeZoneY + 0.0 * safeZoneH;
_w = 8 * pixelGrid * pixelW;
_h = 4 * pixelGrid * pixelH;
_y = _y + 0.5 * _h;

Проверяем результат:

Шаг 4. Переходим к верхнему-левому углу

Имеем формулу:

x = x - w / 2;
y = y - h / 2;

Подставляем ее в наш скрипт и получаем:

_x = safeZoneX + 0.5 * safeZoneW;
_y = safeZoneY + 0.0 * safeZoneH;
_w = 8 * pixelGrid * pixelW;
_h = 4 * pixelGrid * pixelH;
_y = _y + 0.5 * _h;
_x = _x - _w / 2;
_y = _y - _h / 2;

Получим:

Шаг 5. Проверка

В задаче было сказано, что элемент должен быть с возможность масштабирования, проверяем:

Масштаб: Very Small

Масштаб: Normal

Масштаб: Very Large

Первая часть руководства:
Система координат пользовательского интерфейса (UI)

Оригинал руководства: GUI: Кординаты (Часть 2)

Так же по GUI:
GUI. Часть 1. Общее представление.
GUI. Часть 2. Пишем простой интерфейс.

Что-то не поняли? Заходите в наш Discord, там вам всегда помогут.