Этот гайд сфокусируется на создании HUD отображения из esp данных, в духе механик "Primary Needs", которых ожидаешь встретить в таких модах как oHUD и IMCNNV.

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

Для умения читать синтакисис xml, смотрите предыдущую статью.

Данный гайд предназначен для Fallout 3, но принцип работает и для других игр Бесезды.

Введение

 
Идя от простого к сложному, добавляя все более сложные функции, вы научитесь добавлять отображение процента (текст) и визуальную полоску (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) и знак процента. В этой строке лейбл и знак процента – части текста, которые не будут меняться, а вот значение будет обновляться, получая новое значение из esp.

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

А в нашем esp сделаем еще один маленький квестовый скрипт, который на данный момент будет делать лишь так:

scn DSHygHUDQstScpt

Begin GameMode

 

SetUIFloat "HUDMainMenu\_DSHygOwnHud" 1

 

End

 

Begin MenuMode

 

SetUIFloat "HUDMainMenu\_DSHygOwnHud" 0

 

End

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

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. Показываем текстовые подсказки на экране

 

Однако кое-что мы пропустили: немного пользовательского удобства. Люди не знают, какие клавиши нажимать, пока мы им не скажем. Хорошо конечно прописать это в Ридми, но все знают, что их никто не читает. Так что давайте скажем им о кнопках прямо в игре, добавив немного текста в наш 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 добавить их как условия каждому текстовому компоненту, которое мы скопируем из esp, используя еще одно кастомное свойство:

<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

С такими положениями в нашем меню МСМ:

; reset block

    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

; default block

    set DSHygHud.iAlpha to 255

; new value block

    elseif iOption == 10

        set DSHygHud.iAlpha to fValue

; show scale block

    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"

; default scale block

    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>

Здесь должна быть подпись к изображению

К этому моменту вы уже сможете догадаться как сделать его еще и ярким, используя блок видимости или другое кастомное свойство элемента, которое можно включать и выключать через esp?

Заключение, титры

 

Всегда существуют разные пути достичь того же результата, которого мы достигли, следуя этому маленькому гайду, так что поиграйтесь с этим знанием, когда изучите его основы. Я придерживаюсь более прямолинейных подходов, основываясь на создании кастомных свойств в моем esp, учитывая, что я совсем не эксперт во всем этом деле и мне намного ближе скрипты ГЕККа, чем xml Бесезды.

В заключение скажу, что в отношении таких скриптов всегда следует читать то, что написано до вас. Я основывался на ванильных скриптах игры, на том, что написано в Вики Обливиона, модах Gopher’а, Impoftheperverse’а and JIP’а и на маленьком моде, который Fallout2AM однажды сделал для меня, чтобы обьяснить некоторые вещи.

Замечание от переводчика

 
Для подготовки к этому гайду существует гайд по чтению XML, читайте его по ссылке.
 
Информация в этом гайде вполне подойдет и для других игр Бесезды, включая Обливион. Так что было бы неплохо дополнить эту информацию гайдом по взаимодействию с LINK, который в данном случае будет аналогичен вставкам кода в MCМ, о которых говорит автор гайда.
 
Если где-то были допущены ошибки или описки, пишите, пожалуйста, в комментариях. Это обьемный гайд, где легко можно было упустить смысл сказанного.
Материал подготовлен ArtemSH специально для TGM — Tesall Game Magazine.
Переводчик: ArtSH
Автор: DoctaSax
Источник: Перейти
1

Комментарии

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

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

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

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