Этот гайд сфокусируется на создании HUD отображения из esp данных, в духе механик "Primary Needs", которых ожидаешь встретить в таких модах как oHUD и IMCNNV.
Метод данной статьи заключается в медленном создании мода "Гигиена", который я специально сделал для этого гайда, что позволит вам учиться моей методике шаг за шагом.
Для умения читать синтакисис xml, смотрите предыдущую статью.
Данный гайд предназначен для Fallout 3, но принцип работает и для других игр Бесезды.
Введение
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>
Остальные два элемента будут отрисованы справа, поскольку я хочу, чтобы значение было ближе к знаку процента, и к тому же сделать так намного проще.
<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 однажды сделал для меня, чтобы обьяснить некоторые вещи.
Комментарии