Этот гайд сфокусируется на создании HUD элемента в моде, копирующем механики "Primary Needs", как в oHUD и IMCNNV.

Метод данной статьи заключается в поэтапном создании мода "Гигиена", выступающем в роли примера,  так что осмыслять эту тему мы будем шаг за шагом.

Чтобы научиться читать синтакисис XML, смотрите предыдущую статью.

Данный гайд предназначен для Fallout 3\New Vegas, но принцип работает и для других игр Бесезды, включая TES IV: Oblivion.

Введение

Идя от простого к сложному, добавляя все более сложные функции, вы научитесь отображать наши переменные, как текстом, в виде процентов на экране, так и визуальным хелсбаром (visual bar).
 
По мере обучения вы узнаете, как добавлять элементы интерфейса в UIO, отправляя информацию из ГЕКК-скрипта в XML, и создадите скрипт, позволяющий игрокам вручную изменять положение нашего элемента интерфейса на экране.

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>

Остальные два элемента будут отрисованы справа, поскольку я хочу, чтобы "Значение" было ближе к "Знаку процента", да и к тому же так проще.

Остальные элементы имеют одну и ту же позицию по оси Y, но я смещаю знак процента вправо (+ 100 X относительно родительского элемента) и делаю то же самое для "Значения", копируя позицию "Знака процента" по оси X: минус ширина самого знака процента:

    <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. Определяем и Храним Нажатые Клавиши

Есть два пути достижения нашей цели.
 
Перый – написать скрипт, который позволит игроку включать и выключать опцию через МСМ, но сам по себе он может наслаиваться на другие моды, так что понадобятся дополнительные манипуляции с его положением на экране.
 
Другой – сделать это в Gamemode. Проблема в том, что большинство клавиш уже зарезервированно, включая клавиши движения игрока, которые хотелось бы использовать. Однако второй путь все равно предпочтительнее – он вполне осуществим, если на время настройки ограничить передвижение игрока.

Сначала весь квестовый скрипт надо включить через МСМ. Вот один из способов сделать это:

; '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, читайте его по ссылке
Информация в этом гайде вполне подойдет и для других игр Бесезды, включая Обливион. Если где-то были допущены ошибки или описки, пишите, пожалуйста, в комментариях. Это обьемный гайд, так что переводчик вполне мог где-нибудь ошибиться.

Материал подготовлен ArtemSH специально для TGM — Tesall Game Magazine.
Переводчик: ArtSH
Автор: DoctaSax
Источник: Перейти
1

Комментарии

Lord RZ
администратор
06.09.2024 — 23:52

Спасибо за твой труд.

Lord RZ, благодарю!

12.03.25

В этой статье был надмозговый перевод. Я исправил и переписал почти всю статью.
Теперь читаемость должна быть намного лучше.

Авторизуйтесь, чтобы оставить новый комментарий. Или зарегистрируйтесь.