Этот гайд сфокусируется на создании HUD элемента в моде, копирующем механики "Primary Needs", как в oHUD и IMCNNV.
Метод данной статьи заключается в поэтапном создании мода "Гигиена", выступающем в роли примера, так что осмыслять эту тему мы будем шаг за шагом.
Чтобы научиться читать синтакисис XML, смотрите предыдущую статью.
Данный гайд предназначен для Fallout 3\New Vegas, но принцип работает и для других игр Бесезды, включая TES IV: Oblivion.
Введение
1. ПЕРвые шаги: немного текста
1.1. Цель: HUD для Гигиены (Hygiene.esp)
Давайте предположим, что у вас есть мод "Гигиена", который отслеживает уровень гигиены игрока во время игры. Ну, я уже написал этот мод, чтобы было нагляднее.
Вот главный скрипт, высчитывающий значение, которое я хочу отобразить на экране:

В старых добрых фоллаутовских традициях мы зовем эту характеристику «Гигиена», но чем выше ее значение, тем хуже "гигиена" игрока. То же характерно и для характеристики «Вода», маркирующей "Жажду" при верхних значениях. Вот и наша полоска гигиены будет становиться красной при достижении верхней отметки.
Скрипт выше просто суммирует значение с течением времени, в зависимости от того, что делает игрок и сколько времени ему нужно, чтобы достичь 100 пунктов по дефолту, — все эти переменные можно будет настроить в MCM.
То есть на экране я отображу просто процент и небольшой текст, поясняющий, что этот процент означает. Например: HYG: 10%
1.2. Привязываем к UIO
Начнем. Сперва, если вы не знали, когда дело доходит до добавления элемента интерфейса, нельзя просто положить новый файл в некую папку. Существует только один файл XML, который отвечает за интерфейс игрока: hud_main_menu.xml, расположенный по адресу menus\main.
Раньше, добавляя нечто в этот файл с помощью мода, мы создавали конфликт с другими модами, изменявшими этот же файл. Костыль в виде использования элемента <include> имел много недостатков и этим был крайне неудобен для модеров, затем появился uHUD, автоматизировавший весь этот процесс, но он требовал от автора обновлять себя каждый раз, когда новый мод, привязанный к нему, публиковался.
Оба решения уже в прошлом благодаря UIO, разрешившему все конфликты и автоматически распознающему добавленные элементы интерфейса.

Мы создаем файл XML по адресу menus\prefabs.
Создаем текстовый файл в папке uio\public, оповещающий UIO, что мы загрузили новый XML, который следует добавить в интерфейс. Впишем в файл следующий код:
DSHyg\DSHygHUD.xml::HUDMainMenu
true
1.3. Добавляем Текстовые Элементы
Разобравшись с этим, открываем XML файл. Теперь разобьем "HYG: 10%» на три части: лейбл (label), значение (value) и знак процента. В этой строке лейбл и знак процента – части текста, которые не будут меняться, а вот значение будет обновляться из скрипта мода.
К тому же самое время сгруппировать эти три элемента в один, поскольку в последующих главах мы слегка двинемся на теме изменения их положения и подключения к другим модам интерфейса. И мы всё еще нуждаемся в корневом элементе, содержащем всё, что будет в нашем XML. Общая структура файла на данный момент будет следующей: корень с нашим HUD в виде сгруппированных элементов в нем.
<rect name="DSHygHUDROOT">
<rect name="DSHygOwnHUD" >
<text name="DSHygLabel"> ; Лейбл
</text>
<text name="DSHygPercentSign"> ; знак процента
</text>
<text name="DSHygValueDisplay"> ; значение переменной "Гигиена"
</text>
</rect>
</rect>
Если вы прочли гайд по чтению XML, то уже заметили, что эта главная структура содержит лишь объектные элементы, добавив 3 текстовых компонента, сгруппированных под одним невидимым прямоугольником. Только отсутствует текст, который они должны отображать, и неясно положение на экране, где они будут отображаться. Так что, добавим свойства и сперва введем строки (strings).
<text name="DSHygLabel">
<string>HYG:</string>
</text>
<text name="DSHygPercentSign">
<string>%</string>
</text>
Нединамические строки ставятся между тегами <string>. Значение, которое мы хотим отобразить, будет вызвано главным скриптом из esp, где будет такая строчка кода:
SetUIFloat "HUDMainMenu\_DSHygValue" fHyg
Она присваивает значению пользовательское свойство элемента, _DSHygValue. Нам остается только сделать так, чтобы в XML файле код считывал это свойство из элемента, используя оператор <copy> с "io()" src атрибутом:
<text name="DSHygValueDisplay">
<copy src="io()" trait="_DSHygValue"/>
</text>
2. Местоположение
Теперь наш XML выглядит так:
<rect name="DSHygHUDROOT">
<rect name="DSHygOwnHUD" >
<text name="DSHygLabel">
<string>HYG:</string>
</text>
<text name="DSHygPercentSign">
<string>%</string>
</text>
<text name="DSHygValueDisplay">
<copy src="io()" trait="_DSHygValue"/>
</text>
</rect>
</rect>
В игре уже появятся все три текстовых компонента, только они наслаиваются друг на друга в левом верхнем углу, потому что мы еще не расписали их расположение. Чтобы это сделать сперва установим положение их родительского прямоугольника:
<rect name="DSHygOwnHUD" >
<locus> &true; </locus>
<x>
<copy src="screen" trait="width" />
<sub> 150 </sub>
</x>
<y>
<copy src="screen" trait="height" />
<div> 2 </div>
</y>
<!--наши три текстовых компонента всё еще здесь-->
</rect>
Копируя широту экрана и вычитая (subtracting) некоторые пиксели, я кладу элемент в правую часть экрана и в середину по высоте.
Поскольку мы собираемся позиционировать наши текстовые элементы, давайте еще включим элемент <locus>, который воспринимает положение дочернего элемента по осям X и Y по отношению к родительскому прямоугольнику, а не по отношению к экрану. Просто мне так удобнее. Со включенным <locus> я выбираю не специфицировать положение лейбла, поэтому он копирует свое положение по осям родителя. Но я расположу его слева через элемент <justify>:
<text name="DSHygLabel">
<justify> &left; </justify>
<string> HYG: </string>
</text>
Остальные два элемента будут отрисованы справа, поскольку я хочу, чтобы "Значение" было ближе к "Знаку процента", да и к тому же так проще.
<text name="DSHygPercentSign">
<x>100</x>
<justify> &right; </justify>
<string>%</string>
</text>
<text name="DSHygValueDisplay">
<x>
<copy src="sibling(DSHygPercentSign)" trait="x" />
<sub src="sibling(DSHygPercentSign)" trait="width" />
</x>
<y> <copy src="sibling(DSHygLabel)" trait="y" /> </y>
<justify> &right; </justify>
<string> <copy src="io()" trait="_DSHygValue" /> </string>
</text>
Вот как это будет выглядеть в игре:

Хорошее начало, но я мог бы достичь того же результата множеством других способов. Вы можете позиционировать компоненты относительно экрана или друг друга, используя самые различные формулы, а то, что взял за основу я, всего лишь самый простейший из методов.
3. Включение и выключение видимости
Хоть мы и добавили наш элемент на экран в Gamemode, он также отображается и в Menumode.

Нам этого не нужно, поэтому необходимо его убрать. Также неплохо бы, чтобы мы не видели такие элементы в других ситуациях и в Gamemode тоже – в бою, например. Решаем проблему тем же способом: включая видимость с помощью пользовательского свойства.
Помните наш прямоугольник родительского элемента, который группирует три текстовых элемента? Сам по себе он невидим, но мы включим его видимость. Она включает и все три дочерних элемента. Так что давайте сделаем эту видимость зависимой от пользовательского свойства:
<rect name="DSHygOwnHUD">
<visible>
<copy src="io()" trait="_DSHygOwnHud"
</visible>
<locus> &true; </locus>
<x>
<copy src="screen" trait="width" />
<sub> 150 </sub>
</x>
<y>
<copy src="screen" trait="height" />
<div> 2 </div>
</y>
<!--наши три текстовых компонента всё еще здесь-->
</rect>
А в нашем моде сделаем еще один маленький квестовый скрипт, который пока что будет делать следующее:
scn DSHygHUDQstScpt
Begin GameMode
SetUIFloat "HUDMainMenu\_DSHygOwnHud" 1
End
Begin MenuMode
SetUIFloat "HUDMainMenu\_DSHygOwnHud" 0
End
Можно легко добавить и больше условий в этот скрипт. Например, чтобы наш элемент интерфейса исчезал во время боя, сделаем так:
Begin GameMode
if playerref.IsInCombat
SetUIFloat "HUDMainMenu\_DSHygOwnHud" 0
else
SetUIFloat "HUDMainMenu\_DSHygOwnHud" 1
endif
End
Наконец, нам стоит отключать элемент в тех же ситуациях, когда отключается и ванильный интерфейс игры. В катсценах, к примеру… Обычно это делается привязкой видимости элемента к видимости «ActionPoints» компонентов интерфейса:
<rect name="DSHygOwnHUD">
<visible>
<copy src="ActionPoints" trait="visible"/>
<and src="io()" trait="_DSHygOwnHud" />
</visible>
<!--всё остальное-->
</rect>
(Учитывайте, что моды подобные iHUD, которые при определенных условиях заставляют элементы, типа ActionPoints, визуально исчезать, не трогают видимость этого компонента: он всё равно будет активен. Условия отключения таких компонентов, включают ситуации, где используется функция DisablePlayerControls.)
С другой стороны, мы хотим дать возможность заставить наш элемент интерфейса остаться на экране несмотря на то, что ванильный интерфейс будет выключен. Для этого мы включаем новое пользовательское свойство в esp:
<rect name="DSHygOwnHUD">
<visible>
<copy src="ActionPoints" trait="visible"/>
<or src="io()" trait="_DSHygForceHud" />
<and src="io()" trait="_DSHygOwnHud" />
</visible>
<!--всё остальное-->
</rect>
scn DSHygHUDQstScpt
int iForceHUD
Begin GameMode
SetUIFloat "HUDMainMenu\_DSHygOwnHud" 1
SetUIFloat "HUDMainMenu\_DSHygForceHud" iForceHUD
End
Begin MenuMode
SetUIFloat "HUDMainMenu\_DSHygOwnHud" 0
End
Включить элемент можно посредством скрипта, реагирующего на особые условия, или по желанию игрока через МСМ, связь с которым я собираюсь добавить в наш мод: через чекбокс (checkbox), который будет включать фукнцию iForceHUD.
4. Позволяем игроку подправлять позицию элементов
В дополнение к тому, чтобы не переписывать оригинальные файлы интерфейса игры, мы очевидно хотим быть хорошими соседями другим подобным модам. Лучше всего дать игроку опцию самому установить положение нашего элемента интерфейса в случае конфликта. Некоторые моды дают такую опцию в настройках МСМ в Menumode, а мы сделаем её в Gamemode.
4.1. Добавляем Пользовательские Свойства для X и Y
На самом деле нам повезло, ведь мы сгруппировали наши компоненты под одним прямоугольником, так что воздействовать нужно только на один элемент. Ну, конечно, всё это было задумано с самого начала! А как вы хотели? Мы добавим новые пользовательские свойства к существующим осям X и Y нашего прямоугольника, которые назовем _DSHygX и _DSHygY:
<rect name="DSHygOwnHud">
<!--visibility intel here>
<locus> &true; </locus>
<x>
<copy src="screen" trait="width" />
<sub> 150 </sub>
<add src="io()" trait="_DSHygX" />
</x>
<y>
<copy src="screen" trait="height" />
<div> 2 </div>
<add src="io()" trait="_DSHygY" />
</y>
<!-- всё остальное-->
</rect>
Смысл в том чтобы оба пользовательские свойства отражали переменные, которые содержатся в нашем скрипте, и эти переменные будут добавляться и удаляться в зависимости от клавиш, которые нажимает игрок.
scn DSHygHudQstScpt
int iForceHud
float fOffsetX
float fOffsetY
Begin GameMode
SetUIFloat "HUDMainMenu\_DSHygOwnHud" 1
SetUIFloat "HUDMainMenu\_DSHygForceHud" iForceHud
SetUIFloat "HudMainMenu\_DSHygX" fOffsetX
SetUIFloat "HudMainMenu\_DSHygY" fOffsetY
End
Begin MenuMode
SetUIFloat "HUDMainMenu\_DSHygOwnHud" 0
End
Чтобы адекватно распознавать нажатие клавиш нужен быстрый скрипт, так что сделаем его отдельным от остальных, включим его через MCM и выключим, когда закончим.
4.2. Определяем и Храним Нажатые Клавиши
Сначала весь квестовый скрипт надо включить через МСМ. Вот один из способов сделать это:
; 'reset' block блок "ресета"
SetUIFloat "StartMenu/MCM/*:1/*:9/_enable" 1
SetUIString "StartMenu/MCM/*:1/*:9/_title" "Configure HUD position"
SetUIFloat "StartMenu/MCM/*:1/*:9/_type" 5
SetUIFloat "StartMenu/MCM/*:1/*:9/_value" DSHygHud.iEditing
; 'default' block блок "по умолчанию"
set DSHygHud.iEditing to 0
; 'new value' block
; other stuff "другое"
elseif iOption == 9
set DSHygHud.iEditing to fValue
endif
; 'mouseover' block блок "наведение мышью"
elseif iMouseover == 9
SetUIString "StartMenu/MCM/*:9/string" "Возвращайтесь в gamemode чтобы начать менять положение HUD"
endif
в моем квестовом скрипте DSHygHud :
int iEditing
if iEditing
set DSHygHudConfig.iStage to 0
startquest DSHygHudConfig
if GetQuestRunning DSHygHudConfig
set iEditing to 0
endif
endif
Так как мы хотим позволить игроку менять положение с помощью клавиш движения, нам нужно:
- ограничить движение игрока, чтобы он не крутился, пока мы меняем положение элемента интерфейса
- сканировать нажатые клавиши и хранить их как смещения (offsets) в локальных переменных от обычных положений элементов нашего интерфейса в квестовом скрипте
- прокинуть эти смещения в XML, где наши кастомные свойства _DSHygX и _DSHygY прочтут их.
Вот этот скрипт сделает всё это. Смотрите.
На стадии 0 он останавливает игрока и заставляет интерфейс включиться, если тот был выключен по какой-то причине.
Стадия 1 определяет нажаты ли клавиши и вносит изменения в соответствии с ними, повышая или понижая положение нашего элемента интерфейса, которые мы передаем в пользовательские свойства _DSHygX и DSHygY в нашем XML.
Стадия 100 останавливает весь процесс (cleans up), освобождает игрока и останавливает скрипт внесения изменений.
scn DSHygHudConfigQstScpt
int iGap
int iStage
int iWasForced
Begin GameMode
if playerref.IsInCombat
set iStage to 100
endif
if iStage == 0
if Playerref.GetRestrained
set iStage to 1
SetUIFloat "HUDMainMenu\_DSHygOwnHud" 1
if 0 == GetQuestRunning DSHygHUD
startquest DSHygHUD
endif
if DSHygHUD.iForceHUD == 0
set DSHygHUD.iForceHud to 1
set iWasForced to 1
endif
else
playerref.SetRestrained 1
return
endif
endif
if iStage == 1
; how much to move шаг сдвига элемента интерфейса
if IsControlPressed 9 ; 'run' control клавиша бега
set iGap to 50
else
set iGap to 5
endif
; forward = up вверх
if IsControlPressed 0
let DSHygHUD.fOffSetY -= iGap
; backward = down вниз
elseif IsControlPressed 1
let DSHygHud.fOffSetY += iGap
endif
SetUIFloat "HUDMainMenu\_DSHygY" DSHygHud.fOffsetY
; left = left лево
if IsControlPressed 2
let DSHygHUD.fOffSetX -= iGap
; right = right право
elseif IsControlPressed 3
let DSHygHUD.fOffSetX += iGap
endif
SetUIFloat "HUDMainMenu\_DSHygX" DSHygHud.fOffsetX
; activate = save and exit активация = сохранить и выйти
if IsControlPressed 5
set iStage to 100
; jump = restore defaults - прыжок = восстановить "по умолчанию"
elseif IsControlPressed 12
set DSHygHUD.fOffSetX to 0
set DSHygHUD.fOffSetY to 0
set iStage to 0
endif
endif
; quit выход
if iStage == 100
if iWasForced
set DSHygHud.iForceHud to 0
endif
SetUIFloat "HUDMainMenu\_DSHygOwnHud" 0
if playerref.GetRestrained
playerref.SetRestrained 0
else
stopquest DSHygHudConfig
endif
endif
End
4.3. Показываем текстовые подсказки на экране
Но кое-что мы пропустили: удобство игрока. Откуда игрок узнает, какими клавишами ему менять положение элемента в настройках? Хорошо бы прописать это в README, но их же никто не читает! Так что давайте прямо в игре скажем о кнопках, добавив текста в наш XML.
Сперва создадим новый прямоугольник, сиблинг по отношению к DSHygOwnHUD. Это сгруппирует наши новые текстовые элементы и включит видимость их всех с помощью пользовательского свойства:
<rect name="DSHygPositionTips">
<visible>
<copy src="io()" trait="_DSHygShowHints" />
</visible>
<text name="DSHygTipsMove">
</text>
<text name="DSHygTipsActivate">
</text>
<text name="DSHygTipsJump">
</text>
<text name="DSHygTipsRun">
</text>
<!-- добавим еще одну клавишу для проверки: клавиша 'тащить' будет включать подсказки -->
<text name="DSHygTipsGrab">
</text>
</rect>
Мы добавим немного кода, чтобы включать подсказки, когда зажата кнопка в нашем скрипте HudConfig:
if iStage == 1
; остальное
elseif IsControlPressed 27
if 0 == IsPressed
set isPressed to 1
if 0 == GetUIFloat "HUDMainMenu\_DSHygShowHints"
SetUIFloat "HUDMainMenu\_DSHygShowHints" 1
else
SetUIFloat "HUDMainMenu\_DSHygShowHints" 0
endif
endif
else
set isPressed to 0
endif
Нам нужно пару текстовых элементов, чтобы описать какие клавиши нажимать. Так как игрок уже знает как у него назначены клавиши движения, их можно записать в виде статичной строки в XML:
<text name="DSHygTipsMove">
<string> Используй клавиши движения для изменения положения HUD </string>
</text>
Создадим и другие строки в нашем квестовом скрипте для настройки интерфейса (HUD config), используя GetControl чтобы выявить какие клавиши каким элементам управления принадлежат, прокинем их через SetUIStringEx и через спецификатор формата %k, а затем включим видимость всего блока по умолчанию:
if iStage == 0
if playerref.GetRestrained
; остальное
set iKeyCode to GetControl 5
SetUIStringEX "HUDMainMenu\_DSHygTipActivate" "Нажми ACTIVATE (%k) чтобы сохранить значения и выйти" iKeyCode
set iKeyCode to GetControl 12
SetUIStringEX "HUDMainMenu\_DSHygTipJump" "Нажми JUMP (%k) чтобы восстановить значения по умолчанию" iKeyCode
set iKeyCode to GetControl 27
SetUIStringEx "HUDMainMenu\_DSHygTipGrab" "Нажми GRAB (%k) чтобы включить данные подсказки" iKeyCode
set iKeyCode to GetControl 9
SetUIStringEX "HUDMainMenu\_DSHygTipRun" "Удерживай RUN (%k) чтобы увеличить шаг смещения элемента" iKeyCode
SetUIFloat "HUDMainMenu\_DSHygShowHints" 1
; остальное
И скопируем их в наш XML, поместив их друг под друга где-то посередине экрана:
<rect name="DSHygPositionTips">
<visible>
<copy src="io()" trait="_DSHygShowHints" />
</visible>
<locus> &true; </locus>
<x>
<copy src="screen()" trait="width"/>
<div> 2 </div>
</x>
<y> 400 </y>
<text name="DSHygTipsMove">
<string> Use the movement controls to move the HUD. </string>
</text>
<text name="DSHygTipsActivate">
<y> 25 </y>
<string>
<copy src="io()" trait="_DSHygTipActivate" />
</string>
</text>
<text name="DSHygTipsJump">
<y> 50 </y>
<string>
<copy src="io()" trait="_DSHygTipJump" />
</string>
</text>
<text name="DSHygTipsRun">
<y> 75 </y>
<string>
<copy src="io()" trait="_DSHygTipRun" />
</string>
</text>
<text name="DSHygTipsGrab">
<y> 100 </y>
<string>
<copy src="io()" trait="_DSHygTipGrab" />
</string>
</text>
</rect>
Получаем такой результат:

5. ИнтермеЦЦо
5.1. Подправляем Шрифты
Не удивлюсь, если стандартный шрифт покажется слишком маленьким, и вам захотелось интерфейс покрупнее. И вполне возможно, что в пику мне вы не используете мод Darn's UI, который делает шрифты меньше, да и я не проверял, адекватно ли помещены на экране наши элементы интерфейса с учетом использования оригинальных шрифтов.
Возможность переключаться между шрифтами, которые есть у пользователя, поможет нам во всех случаях, когда возникают проблемы с читаемостью.
На счастье, шрифты пронумерованы от 1 до 8, независимо от того, перезаписаны они или нет, так что нам просто нужно в XML добавить их как условия каждому текстовому компоненту, которое мы скопируем из мода, используя еще одно пользовательское свойство:
<font> <copy src="io()" trait="_DSHYGFont" /> </font>
В таком случае простое решение – прокинуть это в наш квестовый скрипт интерфейса, привязав к переменной:
SetUIFloat "HUDMainMenu\_DSHygFont" iFont
И позволив игрокам установить значение переменной через МСМ.
Другая опция – добавить этот кусок кода в наш скрипт настройки позиционирования, чтобы игроки переключали его в реальном времени:
; crouch key = change font size клавиша красться = смена размеров щрифта
elseif IsControlPressed 8
if 0 == IsPressed
let isPressed := 1
if DSHygHUD.iFont < 8
let DSHygHud.iFont += 1
else
let DSHygHud.iFont := 1
endif
endif
и создав следующую строку:
set iKeyCode to GetControl 8
SETUIStringEX "HUDMainMenu\_DSHygTipCrouch" "Нажми клавишу вызова Pipboy (%k) чтобы поменять размер шрифта" iKeycode
; Я так понял, у автора ошибка, тк в остальном используется клавиша "красться"
И вложив это в наш XML:
<text name="DSHygTipsCrouch">
<y> 100 </y>
<string>
<copy src="io()" trait="_DSHygTipCrouch" />
</string>
</text>


5.2. Подправляем Непрозрачность
Пока что мы не добавили опцию отключить наш интерфейс. Ну там, для скриншотов или еще чего. Да и вполне возможно, что полная непрозрачность выглядит уж очень навязчивой. Убьем двух зайцев разом и создадим способ управлять непрозрачностью.
И сделаем слайдер МСМ – это наиболее разумный выход. Непрозрачность варьируется от 0 до 255, так что этот диапазон будет и на нашем слайдере МСМ, привязанный к еще одной переменной, которую мы прокинем из нашего квестового скрипта в наш XML.
В нашем XML:
<alpha>
<copy src="io()" trait="_DSHygAlpha" />
</alpha>
В главном квестовом скрипте:
SetUIFloat "HUDMainMenu\_DSHygAlpha" iAlpha
С такими положениями в нашем меню МСМ:
; блок ресета
SetUIFloat "StartMenu/MCM/*:1/*:10/_enable" 1
SetUIString "StartMenu/MCM/*:1/*:10/_title" "Set HUD opacity"
SetUIFloat "StartMenu/MCM/*:1/*:10/_type" 2
SetUIFloat "StartMenu/MCM/*:1/*:10/_value" DSHygHud.iAlpha
; блок по умолчанию
set DSHygHud.iAlpha to 255
; блок новых значений переменной
elseif iOption == 10
set DSHygHud.iAlpha to fValue
; блок отображения размера визуального элемента
elseif iOption == 10
SetUIFloat "StartMenu/MCM/_Value" DSHygHud.iAlpha
SETUIFloat "StartMenu/MCM/_ValueDecimal" 3
SetUIFloat "StartMenu/MCM/_ValueIncrement" 5
SetUIFLoat "StartMenu/MCM/_ValueMax" 255
SetUIFloat "StartMenu/MCM/_ValueMin" 0
SetUIString "StartMenu/MCM/*:2/_title" "Hud opacity"
; блок размера по умолчанию
elseif iOption == 10
SetUIFloat "StartMenu/MCM/_Value" 255
5.3. Подправляем цвет
В Нью Вегасе царит приятный и щегольской янтарь (amber), но возможно наши игроки предпочтут яркий и болезненный зеленый (green), а, может, ярко розовый. А кто мы такие чтобы им запрещать, верно?
У нас же есть <red>, <green> и <blue> элементы свойств и четыре стандартных цвета интерфейса, соответствующие данным значениям RGB:
amber - 255, 182, 66
blue - 46, 207, 255
green - 26, 255, 128
white - 197, 255, 255
Что-то подсказывает, что мы можем создать «список» в МСМ, который будет хранить эти имена, а так же пользовательскую опцию, привязанную к переменной в нашем квестовом скрипте:
int iColorOption
int iRed
int iGreen
int iBlue
if iColorOption == 1 ; amber
SetUIFloat "HUDMainMenu\_DSHygRed" 255
SetUIFloat "HUDMainMenu\_DSHygGreen" 182
SetUIFloat "HUDMainMenu\_DSHygBlue" 66
elseif iColorOption == 2 ; blue values go under here
elseif iColorOption == 3 ; green values here
elseif iColorOption == 4 ; white values here
elseif iColorOption == 5 ; custom
SetUIFloat "HUDMainMenu\_DSHygRed" iRed
SetUIFloat "HUDMainMenu\_DSHygGreen" iGreen
SetUIFloat "HUDMainMenu\_DSHygBlue" iBlue
Endif
В нашем XML мы отделяем текстовые элементы от их обычной цветовой схемы, мы не хотим использовать регулярную схему цветов. Поэтому добавим <red>, <green> и <blue> элементы свойств каждому текстовому элементу:
<systemcolor> &nosystemcolor; </systemcolor>
<red> <copy src="io()" trait="_DSHygRed" /> </red>
<green> <copy src="io()" trait="_DSHygGreen" /> </green>
<blue> <copy src="io()" trait="_DSHygBlue" /> </blue>
А МСМ расширим следующим списком:
; reset block
SetUIFloat "StartMenu/MCM/*:1/*:11/_enable" 1
SetUIString "StartMenu/MCM/*:1/*:11/_title" "Color options"
SetUIFloat "StartMenu/MCM/*:1/*:11/_type" 1
SetUIFloat "StartMenu/MCM/*:1/*:11/_value" DSHygHud.iColorOption
if DSHygHud.iColorOption == 1
SetUIString "StartMenu/MCM/*:1/*:11/value/*:1/string" "Amber"
elseif DSHygHud.iColorOption == 2
SetUIString "StartMenu/MCM/*:1/*:11/value/*:1/string" "Blue"
elseif DSHygHud.iColorOption == 3
SetUIString "StartMenu/MCM/*:1/*:11/value/*:1/string" "Green"
elseif DSHygHud.iColorOption == 4
SetUIString "StartMenu/MCM/*:1/*:11/value/*:1/string" "White"
elseif DSHygHud.iColorOption == 5
SetUIString "StartMenu/MCM/*:1/*:11/value/*:1/string" "Custom"
endif
; default block
set DSHygHud.iColorOption to 1
; new value block
elseif iOption == 11
set DSHygHud.iColorOption to fValue
; list block блок списка
elseif iOption == 11
SetUIString "StartMenu/MCM/*:3/_title" "Color option"
SetUIFloat "StartMenu/MCM/*:3/*:1/_enable" 1
SetUIFloat "StartMenu/MCM/*:3/*:2/_enable" 1
SetUIFloat "StartMenu/MCM/*:3/*:3/_enable" 1
SetUIFloat "StartMenu/MCM/*:3/*:4/_enable" 1
SetUIFloat "StartMenu/MCM/*:3/*:5/_enable" 1
SetUIString "StartMenu/MCM/*:3/*:1/text/string" "Amber"
SetUIString "StartMenu/MCM/*:3/*:2/text/string" "Blue"
SetUIString "StartMenu/MCM/*:3/*:3/text/string" "Green"
SetUIString "StartMenu/MCM/*:3/*:4/text/string" "White"
SetUIString "StartMenu/MCM/*:3/*:5/text/string" "Custom"
Который подарит мне болезненный зеленый оттенок, если выберу его в игре:


А зачем, собственно, останавливаться на этом? Можно добавить добавить в МСМ удобную опцию type-9 чтобы позволить людям вводить любое значение RGB, которое им нравится. Ну а когда еще вы этим воспользуетесь?
Внимательнее к документации МСМ, так как она содержит ошибки, где говорят пользоваться SetUIStringEX
там, где надо использовать SetUIFloat
. Сделаем эту опцию зависимой от нашей пользовательской опции в списке:
; reset block
if DSHygHud.iColorOption == 5
SetUIFloat "StartMenu/MCM/*:1/*:12/_enable" 1
else
SetUIFloat "StartMenu/MCM/*:1/*:12/_enable" 2
endif
SetUIString "StartMenu/MCM/*:1/*:12/_title" "Custom color"
SetUIFloat "StartMenu/MCM/*:1/*:12/_type" 9
SetUIFloat "StartMenu/MCM/*:1/*:12/_value1" DSHygHud.iRed
SetUIFloat "StartMenu/MCM/*:1/*:12/_value2" DSHygHud.iGreen
SetUIFloat "StartMenu/MCM/*:1/*:12/_value3" DSHygHud.iBlue
; default block
set DSHygHud.iRed to 255
set DSHygHud.iGreen to 182
set DSHygHud.iBlue to 66
; new value block:
elseif iOption == 12
set DSHygHud.iRed to GetUIFloat "StartMenu/MCM/_Value1"
set DSHygHud.iGreen to GetUIFloat "StartMenu/MCM/_Value2"
set DSHygHud.iBlue to GetUIFloat "StartMenu/MCM/_Value3"
; showscale block: блок изменения цвета
elseif iOption == 12
SetUIFloat "StartMenu/MCM/_Value1" DSHygHud.iRed
SetUIFloat "StartMenu/MCM/_Value2" DSHygHud.iGreen
SetUIFloat "StartMenu/MCM/_Value3" DSHygHud.iBlue
SetUIString "StartMenu/MCM/*:2/_title" "Custom color"
; defaultscale block блок значения цвета по умолчанию
elseif iOption == 12
SetUIFloat "StartMenu/MCM/_Value1" 255
SetUIFloat "StartMenu/MCM/_Value2" 182
SetUIFloat "StartMenu/MCM/_Value3" 66
Это позволит выбрать клейкий розовый:


6. Добавляем измеритель (meter) или полоску (bar)
Вместо сухих процентов, может, лучше предпочесть полоску, отображающую как мы грязны?
Давайте снова добавим еще одну опцию в МСМ:
; reset block
SetUIFloat "StartMenu/MCM/*:1/*:13/_enable" 1
SetUIString "StartMenu/MCM/*:1/*:13/_title" "Use a bar"
SetUIFloat "StartMenu/MCM/*:1/*:13/_type" 5
SetUIFloat "StartMenu/MCM/*:1/*:13/_value" DSHygHud.iUseBar
; new value
elseif iOption == 13
set DSHygHud.iUseBar to fValue
и прокинем её в XML с другим пользовательским свойством:
if iUseBar
SetUIFloat "HUDMainMenu\_DSHygBar" 1
else
SetUIFloat "HUDMainMenu\_DSHygBar" 0
endif
а после нужно остановить отображение "знака процента", если включена полоска:
<text name="DSHygPercentSign">
<!--остальные вещи-->
<visible>
<copy src="io()" trait="_DSHygValue" />
<gt>0</gt>
<and>
<copy src="io()" trait="_DSHygBar" />
<eq>0</eq>
</and>
</visible>
<!--остальные вещи-->
</text>
<!-- also do the same for DSHygValueDisplay -->
Затем добавляем 2 элемента изображения (image elements) под тем же прямоугольником, где мы поместили наши текстовые компоненты, один для задника, другой для его заполнения:
<rect name="DSHygOwnHUD">
<!-- the other stuff-->
<image name="DSHygBarBack">
</image>
<image name="DSHygBar">
</image>
</rect>
Их видимость должна зависеть от опции «use bars». Мы будем использовать простую сплошную текстуру из игры для заполнения полоски:
<image name="DSHygBarBack">
<visible>
<copy src="io()" trait="_DSHygBar" />
</visible>
<width>100</width>
<height>15</height>
<depth>2</depth>
<y>20</y>
<filename>Interface\Shared\solid.dds</filename>
</image>
Вы могли заметить, что задник имеет максимальную ширину в 100 пикселей. (Можно и больше, но тогда значение гигиены надо тоже конвертировать.) Изображение, которое будет заполняться, DSHygBar, усвоит её ширину из значения fHyg из нашего квестового скрипта:
if iUseBar
SetUIFloat "HUDMainMenu\_DSHygBar" 1
SetUIFloat "HUDMainMenu\_DSHygBarWidth" DSHyg.fHyg
else
SetUIFloat "HUDMainMenu\_DSHygBar" 0
endif
<image name="DSHygBar"
<visible>
<copy src="io()" trait="_DSHygBar" />
</visible>
<width>
<copy src="io()" trait="_DSHygBarWidth" />
</width>
<height>15</height>
<depth>3</depth>
<y>20</y>
<filename>Interface\Shared\solid.dds</filename>
</image>
Чтобы обеспечить контраст, я отключу обычный цвет интерфейса на фоне и нарисую его серым, устанавливая его непрозрачность на половину того значения непрозрачности, которое мы ранее подключили к переменной. Также обратите внимание, что фактически полоса имеет более высокое значение <depth >, чем значение фона, поэтому убедитесь, что она нарисована поверх нее.
<image name="DSHygBarBack">
<visible>
<copy src="io()" trait="_DSHygBar" />
</visible>
<width>100</width>
<height>15</height>
<depth>2</depth>
<y>20</y>
<filename>Interface\Shared\solid.dds</filename>
<systemcolor>&nosystemcolor;</systemcolor>
<red>190</red>
<green>190</green>
<blue>190</blue>
<alpha>
<copy src="io()" trait="_DSHygAlpha" />
<div>2</div>
</alpha>
</image>
Что даст нам следующий результат:

А давайте продолжим! Коль скоро мы знаем как менять цвет текста, давайте дадим возможность менять цвет полоски в те же цвета, которые мы выбрали для текста:
<image name="DSHygBar">
<!-- остальные вещи-->
<systemcolor>&nosystemcolor;</systemcolor>
<red> <copy src="io()" trait="_DSHygRed" /> </red>
<green> <copy src="io()" trait="_DSHygGreen" /> </green>
<blue> <copy src="io()" trait="_DSHygBlue" /> </blue>
<!-- остальные вещи-->
</image>

А еще, почему бы не залить полоску красным, когда уровень гигиены пересекает порог в 90 пунктов?
<image name="DSHygBarOverride">
<visible>
<copy src="io()" trait="_DSHygBar" />
<and>
<copy src="io()" trait="_DSHygBarWidth" />
<gt>90</gt>
</and>
</visible>
<width>
<copy src="io()" trait="_DSHygBarWidth" />
</width>
<height>15</height>
<depth>4</depth>
<y>20</y>
<systemcolor>&nosystemcolor;</systemcolor>
<red>255</255>
<green>0</green>
<blue>0</blue>
<alpha> 255 </alpha>
<filename>Interface\Shared\solid.dds</filename>
</image>

Ну а теперь-то вы уже сможете догадаться, как сделать его еще и ярким, используя блок видимости или другое пользовательское свойство элемента, которое можно настраивать в МСМ?
Заключение, титры
Всегда можно сделать ту же штуку разными средствами, и этот гайд дает лишь один способ реализации интерфейса. Так что поразминайте мышцы, играясь с этими новыми знаниями.
Я был и остаюьс сторонником прямолинейного подхода, когда дело касается пользовательских свойств в подобных модах. Я же не эксперт во всем этом деле, и мне намного ближе скрипты из ГЕККа, чем XML от Бесезды.
В заключение, скажу лишь, что в случае с такими скриптами всегда следует читать то, что написано до вас. Я основывался на ванильных скриптах игры, на том, что написано в Wiki Обливиона, модах Gopher’а, Impoftheperverse’а и JIP’а, чтобы затем на маленьком моде, который Fallout2AM однажды сделал для меня, обьяснить вам базовые вещи.
от переводчика
Для подготовки к этому гайду существует гайд по чтению XML, читайте его по ссылке.
Информация в этом гайде вполне подойдет и для других игр Бесезды, включая Обливион. Если где-то были допущены ошибки или описки, пишите, пожалуйста, в комментариях. Это обьемный гайд, так что переводчик вполне мог где-нибудь ошибиться.
Комментарии