16.07.2007, 17:01 | #1 |
Boom, headshot!
Регистрация: 22.04.2004
Сообщений: 1,918
|
Все что вы хотели знать про мультиборд, но боялись спросить
Автор: Alexey_BH (aka Cacodemon) (c) 2004 - alexey_bh[webdog]mail.ru
В этой статье размещена вся необходимая информация по устройству, принципам и алгоритмам работы с мультибордами. В частности, здесь рассмотрены следующие вопросы:
1. Что такое мультиборд и как он работает? Все прекрасно знают, что такое доска лидерства (Leaderboard) и ее возможности в варкрафте. Сейчас я расскажу о мультибордах (Multiboards), которые обладают гораздо большими возможностями. С их помощью вы можете создавать практические индикаторные доски любой сложности - начиная от простых аналогов лидербордов до сложных комбинированных объектов, сочетающих в себе как текстовую, так и графическую информацию. Кроме того, возможно создание отдельного мультиборда для каждого игрока в мультиплеере. На этой картинке показаны несколько примеров мультибордов. Левый мультиборд - пример из проекта Graveyard. При помощи мультибордов можно делать даже графики (пример слева), миникарты лабиринтов и даже "тетрис"! Принцип организации мультиборда. Мультиборды доступны только в Warcraft - The Frozen Throne начиная с самой ранней версии (1.07). Однако в редакторе триггеров эти объекты доступны только в версиях редактора начиная с 1.13. Для более ранних версий необходимо установить патч - UMSWE или программировать эти объекты на языке JASS. Кроме мультибордов, он вносит в редактор множество новых полезных возможностей. Далее все названия триггерных функций для работы с мультибордом будут взяты именно из UMSWE, поэтому возможно незначительное отличие от названия этих функций в WorldEditor'е (далее WE) 1.13 и выше. Итак, как устроен мультиборд? Этот объект представляет из себя матрицу, каждый элемент которой состоит из двух частей - картинки (Multiboard Icon) и текста (Multiboard Item Value). Пользователь может менять картинки отдельных элементами, расстояние по горизонтали между ними, текст, цвет текста и так далее. Однако в мультиборде (в отличие от Leaderboard'а) нет автоматической сортировки строк - функции сортировки (если они нужны), вам прийдется писать самим. Вообще для написания любого сложного мультиборда необходимы скрипты на языке JASS, но мы пока будем рассматривать довольно простые варианты, в которых можно обойтись только стандартным редактором триггеров (GUI). На этой картинке изображена структура мультиборда. Рассмотрим подробнее из чего она состоит:
Функции для работы с мультибордом Теперь рассмотрим список функций и типов переменных, которые предоставляет UMSWE для работы с этими объектами. Существует два типа переменных для работы с мультибордами - Multoboard (Мультиборд) и Multiboard Item (Элемент Мультиборда). Если вам прийдется использовать как минимум одну переменную типа Multiboard, то без использования Multiboard Items можно легко обойтись за счет функции Multiboard - Get Multiboard Item, о которой я расскажу дальше. Итак, вот полный список всех функций UMSWE для работы с мультибордами (так же привожу JASS-код этих функций):
Теперь я перечислю функции, которые возвращают какое-либо значение и используются в условиях или как аргументы других функций. Перед функцией указан тип возвращаемого значения, эту функцию надо искать в соответствующем ей разделе условий или аргументов.
Итак, в этой статье я подробно рассказал о устройстве мультиборда и тригерных функциях, обеспечивающих его работу. Теперь осталось рассказать о том, как делать разные виды мультибордов на примерах. Это будет в следующих статьях. Пока присылаю скриншот триггеров самого простого мультиборда, который изображен на скриншотах (с надписями "test") . Что важно знать при работе с мультибордом? При создании мультиборда необходимо помнить, что он будет показан лишь в том случае, если создан в начале игры (а не при инициализации карты) и был показан после создания. Поэтому не работайте с мультибордом в действиях триггера, запускающегося при Map Initialization. Вместо этого используйте событие Elapsed Game Time is XXX seconds. И не забудьте включить показ мультиборда после его создания соответствующим действием. Можно ли сделать разные мультиборды для разных игроков? Да, можно. Мультиборд является графическим элементом и локальные действия с ним не приводят к десинхронизации игры (т.н. десинку). Однако реализация локальной работы с мультибордом требует знаний языка jass. Поэтому читать этот пункт дальше имеет смысл тем, кто уже неплохо разбирается в этом языке и имеет понятие о том, как выполняется скрипт карты в мультиплеере. Всю необходимую информацию про jass вы можете найти на нашем сайте www.blizzplanet.ru в этом же разделе. Итак, существует два способа локальной работы с мультибордом. Кстати, обращаю внимание новичков, что понятие 'локальная переменная' и 'локальная запись / работа' - вещи, не имеющие ничего общего. Локальные сегменты кода получаются путем использования jass-функции GetLocalPlayer():
2. Как сделать мультиборд, отображающий жизни у группы юнитов? Итак, сейчас я расскажу о том, как сделать мультиборд, состоящий из нескольких шкал и текстовых комментариев к ним. Он может показывать, например, жизни / ману нескольких юнитов, а также какую-то текстовую информацию. В нашем примере мультиборд будет показывать жизнь у заданной группы юнитов, а также имена этих юнитов. Что у нас получится, вы можете увидеть на скриншоте: Что нам для этого потребуется?
Как это будет работать? Вот задача, стоящая перед нами: нам надо сделать счетчики жизней для каждого юнита в каждой четной строке мультиборда (включая нулевую строку - как я уже говорил, номера строк и колонок в мультиборде начинаются с нуля) и название юнита в каждой нечетной. Число юнитов может быть любым - 1, 5, 10... Как сделать мультиборд, показывающий жизни и название всех юнитов? Для начала рассмотрим, как будет работать счетчик жизней хотя бы для одного юнита. Очевидно, что строку счетчика надо делать из плотно прилегающих друг к другу картинок без текстового комментария. Для простоты рассмотрим самый простой случай. На этом рисунке изображено устройство строки мультиборда, выполняющей роль счетчика: Это самый простой вариант - каждая ячейка или закрашена зеленым или является пустой. Красной полоской обознычены реальные жизни юнита в процентах. Наша шкала состоит из 10 ячеек, соответственно каждая ячейка соответствует каждым 10% жизни юнита. Мы округляем жизни юнита по ячейкам: если красная полоска хотя бы чуть-чуть "заходит" в ячейку, то мы закрашиваем ячейку зеленым. Понятно, что алгоритм для такого закрашивания получается очень простой: если жизнь в процентах больше или равно (9 - номер_ячейки)*10, то мы закрашиваем ячейку зеленым, если нет - то закрашиваем черным. В мультиборде ячейки считаются слева направо, а жизнь растет справа налево, поэтому и потребовалось использовать (9 - номер_ячейки)*10 вместо обычного номера ячейки. Действительно, при этом закрашиванию нулевой ячейки будет соответствовать условие, когда у юнита больше 90% жизней, а закрашиванию 9-й - когда у юнита больше 10% жизней. Однако такой счетчик получится очень неточным - если жизнь юнита в процентах меняется в пределах от i * 10 до (i + 1) * 10 {i - целое число от 0 до 9}, то на мультиборде не будет никаких изменений. Это нас, конечно, не устраивает! Но каждая картинка мультиборда имеет строгий размер - 16x16. Поэтому остается один выход - рисовать несколько вариаций одной ячейки с разной степенью заполненности - например, ячейка заполнена на 0, 50, 100%. В этом случае разрешение счетчика увеличится в два раза - он сможет показывать изменения жизни юнита в пределах 5%, а не 10% как в предыдущем примере. Если сделать еще больше комбинаций - 0%, 25%, 50%, 75%, 100%, то разрешение увеличится до 2.5% (разрешение тем больше, чем точнее мультиборд показывает жизни юнита). И так далее - чем больше комбинаций для ячеек, тем выше разрешение. Размер одной ячейки 16x16 пикселей, поэтому максимально возможное число комбинаций - 16. Однако такая точность никому не нужна - изменение полоски на один пиксель слишком плавное, а картинок получается слишком много. Я нашел оптимальным разрешение счетчика в 2.5% - для этого надо 5 картинок для элемента. Именно так и сделано в мультиборде проекта Graveyard, а для примера мы обойдемся разрешением в 5% - то есть, нам понадобятся картинки, соответствующие заполнению в 0, 50 и 100% каждой ячейки. Еще раз посмотрите на структуру счетчика - мы делаем полоску не "просто так", а в отдельной рамке. Эта рамка повторяет внешнюю рамку мультиборда - мне кажется, так смотрится гораздо эстетичнее. Однако тут же появляется сложность - крайние ячейки отличаются от всех остальных. Поэтому разделим наш мультиборд на три части (см. рисунок):
Нам надо выбрать какую-то схему для обращения к этим картинкам из триггеров. В нашем примере мы будем использовать такую схему (см. рисунок) - все рисунки будут в формате TGA для простоты: Multiboard\(Часть Мультиборда)_(Заполненность Ячейки).tga Например, полностью заполненная средняя ячейка будет называться Multiboard\Middle_High.tga, крайняя левая пустая ячейка будет называться Multiboard\Left_Low.tga и так далее. Все обозначения приведены в предыдущем рисунке. Итак, с ячейками разобрались. В нашей карте уже есть 9 импортированных файлов с нужными названиями. Как я уже говорил в статье о теории мультибордов, для того, чтобы картинки в строке плотно друг к другу, параметр Width для всех ячеек этой строки должен быть равен 0.01, а стиль отображения должен быть (hide) value, (show) icon. По умолчанию Warcraft Import Manager задает файлам путь War3Imported\(Имя_Файла). Не забудьте прописать всем картинкам пути War3Imported\(Имя_Картинки). Делается это очень просто - щелкаете на каждом файле правой кнопкой мыши, выбираете в контекстном меню соответствующий пункт и затем пишите новый путь к файлу.Еще раз проверьте все названия - ошибка в них приведет к "зеленым квадратам" в мультиборде:
В итоге мы рассмотрели алгоритмы формирования разных строк мультиборда:
Создаем триггер инициализации мультиборда. Для начала создадим два массива:
Кроме этого, необходимо создать саму переменную типа multiboard - назовем ее MBoard. Убедитесь, что значением по умолчанию у нее стоит New Multiboard. Теперь создадим триггер, который будет инициализировать мультиборд - сделайте пустой триггер с названием Init. Событием ему ставим Game - Elapsed Game Time is 0,01 second. Почему именно 0,01 секунда после начала игры, а не Map Initialization? Во-первых для того, чтобы раньше сработал триггер Variables и занес все данные в оба массива. А во-вторых, мультиборд так же как и обычный лидерборд не надо инициализировать при Game Initialization, иначе не будет работать. Условия этого триггера следует оставить пустыми. Теперь рассмотрим как будет работать этот триггер. В начале этой статьи было сказано, что число полосок в мультиборде будет зависеть от числа юнитов, записанных в массив Units. Каждому юниту будут соответствовать две строки в мультиборде - строка с полоской жизней и информационная (текстовая) строка. Однако тут возникает одна проблема - как узнать, сколько юнитов было записано в массив при инициализации? Функций определения размера массива и поиска по массиву в стандартных библиотеках Варкрафта не существует - поэтому функцию определения массива прийдется писать самим. Это совсем не сложно - при инициализации карты мы вносили юнитов последовательно в ячейки массива Units, начиная с нулевой. Поэтому после занесения данных в какое-то число первых ячеек последовательно записаны юниты, остальные ячейки остаются пустыми. Определение размера массива организуем очень просто - будем последовательно перебирать ячейки массива и смотреть, что в них находится. Если ячейка пустая - значит мы нашли конец массива, а номер этой ячейки - число юнитов, записанных в этот массив (так как при инициализации мы последовательно записывали юнитов в ячейки начиная с нулевой). Итак, поехали: Код:
For each (Integer A) from (0) to (15) do actions: // Последовательно перебираем все ячейки с номерами от 0 до 15 // (больше не имеет смысла так как даже 32 строки мультиборда (16 * 2) // с трудом уместятся на экране) If Units(Integer A) is equal to (no unit) then // Смотрим, является ли ячейка массива под номером // (Integer A) пустой или нет Then - Actions: // Случай когда ячейка является пустой т.е. содержит значение (no unit) Custom Code - "exitwhen true" // Без JASS все-таки не обойтись. Я не буду подробно рассказывать // о JASS, ведь мы делаем мультиборд в обычном редакторе триггеров // (GUI). Скажу только, что код "exitwhen true" обозначает // мгновенный выход из цикла, но действия триггера, расположенные // после цикла, все равно будут выполнены. В общем, как только нам // встретилась пустая ячейка, мы тут же выходим из цикла. Else - Actions: // Ячейка не пустая - ничего не делаем. Do Nothing // Итак, мы последовательно перебираем в цикле ячейки массива и как // только нам попадается первая пустая ячейка, тут же выходим из цикла. // А переменная-счетчик этого цикла (т.е. For Loop Integer A) // содержит номер первой пустой ячейки (т.к. в этом месте мы вышли) т.е. // просто число юнитов, которых мы записали в этот массив. Если же все // ячейки содержат в себе юнитов, то цикл все равно закончится когда // переменная For Loop Integer A достигнет значения 15. // В результате выполнения такого цикла мы знаем, сколько юнитов у нас // записано в массив, соответственно можем задать нужное число строк // в мультиборде. Это число будет равно (Integer A) * 2 так // как под каждого юнита мы отводим две строки. Multiboard - Set Number of Rows in (MBoard) to 2 * (Integer A) // Устанавливаем число строк в мультиборде. Multiboard - Set Number of Columns in (MBoard) to 10 // А колонок у нас будет всегда 10 - об этом мы уже говорили. Multiboard - Set Style of (MBoard): (hide) values, (show) icons Multiboard - Set Width of Every Item in (MBoard) to (0.01). // Устанавливаем всему мультиборду "графический" стиль, т.е. // показываем только каринки, промежуток ставим минимальный. Multiboard - Set Title of (MBoard) to "Health Bar". // Устанавливаем заголовок мультиборда. // У нас есть "графический" мультиборд нужного размера. Однако кроме // полосок жизней нам в каждой нечетной строке мультиборда надо // отображать текстовую информацию. Соответственно стиль ячеек в // каждой нечетной строке будет "текстовым". Как я уже говорил // раньше, для таких строк нужен следующий стиль - у первой ячейки // в строке отображается только текст, а ее Width = 0,20. // Остальные ячейки в этой строке должны быть скрыты. То есть нам нужен // еще один цикл, который будет перебирать все 10 колонок в каждой // нечетной строке, для первой устанавливать текстовый стиль и // нужный Width, остальные - прятать. For each (Integer A) from 0 to ((Number of Rows in MBoard) / 2) - 1 do action: // Во внешнем цикле мы перебираем все номера от 0 до числа юнитов в массиве. // Так как Число_Строк = (2 * Число_юнитов), то номер последнего юнита // будет равен ((Число_Строк / 2) - 1). For each (Integer B) from 0 to 9 do action: // А во внутреннем цикле мы последовательно перебираем 10 колонок - // от нулевой до девятой. If (Integer B) equal to 0 // А тут проверяем - если у нас нулевой столбец (т.е. самая // левая ячейка), то устанавливаем ей текстовый стиль, иначе // (для столбцов с номерами 1 - 9) прячем ячейки) Multiboard - Set Style of Item in MBoard in Position: ((2 * (Integer A)) + 1, (Integer B)): Show value, Hide icon Multiboard - Set Width of Item in MBoard in Position: ((2 * (Integer A)) + 1, (Integer B)) to 0.2 // Integer A меняется от 0 до (числа юнитов в массиве - 1), // соответственно 2 * (Integer A) + 1 будет нечетными числами: // // (Integer A) (2 * (Integer A) + 1) // 0 1 // 1 3 // 2 5 // 3 7 // ... ... // (число юнитов) (последняя строка) // // Таким образом выражение (2 * (Integer A) + 1) позволяет // получить номера нечетных текстовых строк для всех юнитов, // записанных в массив. Таким образом, для всех ячеек, расположенных // на пересечении нулевых столбцов и нечетных строк, мы задаем // "текстовый" стиль. Else - Actions: // В противном случае (если (Integer B) > 0 и у нас не первая колонка) // мы прячем остальные ячейки в нечетной строке, таким образом видна // будет только первая с "текстовым" стилем. Multiboard - Set Style of Item in MBoard in Position: ((2 * (Integer A)) + 1, (Integer B)): Hide value, Hide icon Multiboard - Set Width of Item in MBoard in Position: ((2 * (Integer A)) + 1, (Integer B)) to 0.01 Multiboard - Set Value of Item in MBoard in Position: ((2 * (Integer A)) + 1, (Integer B)) to Names(Integer A) // А когда внутренний цикл закончен (т.е. мы обработали все колонки) // в нечетной строке, мы задаем текстовое значение нулевой ячейки - // присваиваем ей имя юнита, записанного в массиве Names. Wait (1.00) seconds // Зачем нужен этот Wait, я расскажу позже. Хотя можете его убрать // и посмотреть что будет. Multiboard - (MBoard): Show // И, наконец, показываем мультиборд. Пока на нем не отображается // графика, но имена юнитов в нечетных строках уже видны. // Теперь остается сделать триггер, обновляющий графическую информацию. Создаем триггер обновления графики. Пока у нас есть размеченный мудьтиборд нужного нам размера, в нечетных строках которого находятся названия юнитов. Число строк в мультиборде равно числу юнитов, занесенных в массив и умноженному на два. Однако счетчиков жизней пока нет - вместо них вы видите стандартные зеленые квадраты, которыми Варкрафт замещает отсутствующие графические элементы в мультиборде. Тепеьр нам надо написать второй триггер, который будет "обновлять" графическую информацию в соответствие с жизнями каждого из юнитов в массиве. Я думаю, очевидно, почему этот триггер должен быть периодическим (то есть его событие - Time - Periodic Event) с довольно коротким периодом для того, чтобы информация на мультиборде была достоверной. Я считаю, что для нашего случая надо использовать периодическое событие с периодом 0.5 секунды. За один период мы в цикле пройдем все четные строки мультиборда (а именно в четных строках у нас находятся счетчики жизней), за каждый шаг цикла будем перебирать все колонки в строке и устанавливать для них графику в зависимости от жизней юнита. То есть нам прийдется опять использовать цикл в цикле ("вложенные циклы"), аналогичный тому, что мы использовали в триггере инициализации мультиборда. Сейчас мы подробно рассмотрим действия такого триггера, но для начала создайте его (Название в тестовой карте - Refresh) и добавьте событие - Time - Periodic Event: every 0.5 second of game time. Как и в прошлом триггере, никаких условий добавлять не надо. Вам потребуется еще одна переменная - Prefix (тип - String). С ее помощью мы будем последовательно формировать название нужной картинки на каждом шаге цикла. Действия триггера подробно рассмотрены ниже: Код:
For each (Integer A) from 0 to ((Number of Rows in MBoard) / 2) - 1 do action: // Такой же цикл использовался и в предыдущем триггере - мы делаем столько шагов в // цикле, сколько юнитов у нас записано в массиве. Размер массива мы определили еще // в прошлом триггере, однако в этот массив значение без использования дополнительных // переменных никак не передать. Проще определить число юнитов из числа строк в // мультиборде делением на 2. А так как нумерация идет с нуля, то от этого числа // надо отнять еще единицу. For each (Integer B) from 0 to 9 do action: // А в этом внутреннем цикле мы последовательно перебираем все 10 // колонок каждой строки. Set Prefix = "Multiboard\Middle_" // Начинаем формировать имя картинки. Это имя начинается с Middle_ для // всех колонок, кроме крайних (нулевой и девятой). If (Integer B) equal to 9 then: Set Prefix = "Multiboard\Left_" else Do Nothing If (Integer B) equal to 0 then: Set Prefix = "Multiboard\Right_" else Do Nothing // Для крайних колонок используем картинки с закруглением - // их названия начинаются с "Left_" и "Right_" // А теперь будем определять, какую приставку приписать для картинки - // Low, Med или High. Это зависит от того, сколько жизней есть у юнита. // У нас 10 колонок, следовательно каждая соответствует каждым 10% жизни. // Единственное отличие - номера колонок увеличиваются слева направо, а // жизни юнита наоборот - поэтому каждая колонка соответствует (9 - i) * 10 // процентам жизни (самая левая - 90%, самая правая - 0%), i - номер колонки // Поэтому на каждом шаге мы будем считать такое выражение: // (Жизнь_В_Процентах - 10*(9 - Номер_Колонки)). // - Если это выражение 0 или меньше - значит данная колонка полностью пуста, // т.е. жизни просто "не доходят" до этой колонки. В этом случае выбираем // заполнение ячейки на 0%. // - Если это выражение больше нуля, но меньше 5 - значит значение жизни // юнита находится где-то в районе "середины" интервала, соответствующего // данной ячейке. В этом случае устанавливаем заполнени на 50% // - Если значение больше 5, то можно считать ячейку 100% заполненной. // Пример (L - Low (0%), M - Med (50%), H - High (100%)): // 72% - LLMHHHHHHH, 10% - LLLLLLLLLH, 11% - LLLLLLLLMH, 100% - HHHHHHHHHH If Percent Life Of (Units(Integer A)) - Real(10 * (Integer B)) less or equal to 0 // Случай когда это выражение меньше или равно нулю. Set Prefix = Prefix + Low // Добавляем к названию ячейки слово Low // (так мы обозначили пустые ячейки) Else If Percent Life Of [i](Units(Integer A)) - Real(10 * (Integer B))[i] greater than [i]5[i] // Случай когда это выражение больше пяти. Set Prefix = Prefix + High Else // Последний вариант - частичное заполнение. Set Prefix = Prefix + Med // Делаем аналогично для остальных типов заполненности. Multiboard - Set icon of Item in position: (2 * (Integer A), (9 - (Integer B))) to Prefix + ".tga" // Устанавливаем картинку для ячейки. 2 * (Integer A) дает // все четные строки мультиборда, а обратный номер колонки // используется из-за того, что жизнь и номера колонок // отсчитываются в разных направлениях. В результате у нас готовы триггеры мультиборда. Еще раз проверьте, что на вашей карте есть:
Если вы хотите использовать мультиборд, подобный этому, в вашей карте. ВНИМАНИЕ! Для того, чтобы такой мультиборд без ошибок работал в вашей карте, не используйте в циклах стандартные переменные (For Loop Integer A) и (For Loop Integer B) - дело в том, что скорее всего, все цикл в вашей карте построены при помощи этих же переменных. Поэтому возможны ошибки при параллельной работе триггера Refresh и одного из циклов в ваших триггерах из-за одновременного обращения к одной переменной. Так как Refresh запускается каждые 0,5 секунды - вероятность ошибки очень велика. Поэтому сделайте 2 integer-переменные и используйте их в качестве счетчиков в циклах вместо (For Loop Integer A) и (For Loop Integer B) - UMSWE это позволяет. Для разнообразия и лучшего изучения материала попробуйте реализовать одну или несколько из этих идей в мультиборде. Задания расположены в порядке нарастания их сложности:
(c) Alexey_BH (aka Cacodemon) - alexey_bh[webdog]mail.ru |
Здесь присутствуют: 1 (пользователей: 0 , гостей: 1) | |
|
|