Задумал я сделать игрушку. День 6. HUD.
Запись от Storm23 размещена 01.04.2016 в 18:37
Показов 7615
Комментарии 7
|
День 6. HUD. На шестой день Бог создал человека. Поэтому и мы в этот день обязаны сделать что-то человеческое, а не через пень-колоду, как обычно. Окей, будем потихоньку двигаться дальше. Если кто не в курсе, HUD - это Head-Up Display - информация выводимая поверх игрового мира. Обычно это карта, прицел, показатели здоровья, оружия и так далее. Захотелось мне сделать HUD в стиле футуристик. Что-то вот типа такого: Футуристик
Я бы не писал этот блог, если бы я просто взял графический редактор и нарисовал все что мне нужно. Это не наш путь. Тем более, что художник из меня никакой. Кроме того, реально это сделать сложно. Ведь HUD это не просто статичная картинка. Элементы должны двигаться, поддерживать эффекты, отображать информацию, меняться и так далее. Поэтому нужен редактор векторной графики. Причем редактор специфический, с поддержкой спец эффектов, с поддержкой движения и с программным доступом к получившемуся изображению. Версия 1 Сначала я подумал сделать набор специальных контролов. Кидаешь их на форму, двигаешь, растягиваешь, компонуешь. И не только подумал, но и сделал. Выглядело это примерно так: Идея заключалась в следующем: на форму кидаются визуальные контролы - маркеры. Они задают опорные точки изображения. Для соединения их линиями - тоже кидаются специальные контролы, которые сами по себе не видимы, но при отрисовке, соединяют линиями указанные маркеры. Маркеры, которые нужно соединять - указываются в панели свойств такого контрола. И были еще невизуальные компоненты. Они использовались для добавления эффектов - свечения, раздвоения, отрисовки бордеров и заливки. Все это я сделал, и оно даже работало. Но результат меня не порадовал. Слишком много проблем при таком подходе. Хоть он и выглядит простым, но на практике нарисовать таким образом что-то сложно. Не буду перечислять всех проблем, лишь некоторые. Трудно двигать маркеры по форме, VS к такой тонкой работе приспособлена мало. Трудно регулировать порядок отрисовки примитивов, z-ордер. Тяжело задавать все маркеры в свойствах других контролов. И так далее. В общем - неудобно. Поэтому было решено отказаться от услуг редактора форм VS, и сделать свой (тот самый:). Вы не поверите, но я переделывал этот редактор целиком - три раза. Хотя сегодня и первое апреля, но это не шутка. Версия 2 Вторая версия выглядела так: Было создано отдельное приложение - редактор. На нем можно было размещать и двигать маркеры. Было дерево объектов, в котором можно было добавлять элементы. И была панель свойств, где можно было менять свойства этих объектов. Сама идея отрисовки осталась почти такой же: были маркеры - опорные точки. И были так называемые "слои" - специальные объекты, которые соединяли точки линиями или делали эффекты. При этом к одному маркеры могли цепляться несколько слоев, что позволяло наслаивать их друг на друга. Этот редактор был уже лучше предыдущего. Но он меня тоже не устроил. Проблема заключалась в том, что для отрисовки более-менее приличного изображения нужно куча всяких настроек, объектов, свойств и так далее. И вот проблема как раз с панелью свойств. Даже та панель, что выше на рисунке - заняла у меня довольно много времени. А мне таких панелей нужно было понаделывать десятки. С десятками свойств. Это очень долго. При таком подходе я бы тратил время не на саму графику а на создание панелей со свойствами. А ведь я разрабатываю экспериментальные концепты, которые могут меняться. ТЗ у меня нет, все может вмиг поменяться. И делать каждый раз новую панель свойств с десятком полей - было выше моих сил. Реально. В общем, я бы тратил время не на саму графику, а на обвязку. Фтопку такой редактор. Версия 3 Тогда я сделал третью версию редактора, который наконец-то меня устроил, и я решил на нем остановиться. Идея нового редактора заключалась в том, что для редактирования опорных точек мы как и прежде используем визуальные маркеры. А вот для всего остального - используем специально разработанный скриптовый язык. Да-да, вот так все начиналось с графики, а перешло к синтаксическим анализаторам и текстовым редакторам. Скрипт решает много проблем. Во-первых не нужно делать сто одну панель свойств объектов. Синтаксис языка - унифицированный, сделать поддержку новой опции - не проблема, это делается быстро. В-вторых, текстовый скрипт легко редактируется. Текст можно удалять, копировать, вырезать и так далее. И все это практически бесплатно. В предыдущей версии редактора мне бы пришлось это все программно поддерживать. И вообще я люблю скрипты. Они позволяют упрощать разработку сложных систем. Как разработку интерфейса, так и работу самого пользователя. Конечно иногда удобно бывает иметь панели свойств с галочками, полями ввода, комбобоксиками и так далее. Но это все хорошо только сначала - пока их мало и пока пользователь не знает куда тыкать. Но когда этих галочек становится сотня на каждой панели, а пользователь начинает делать более сложные вещи, то все эти галочки начинают быть в тягость. Их слишком много, это неудобно. Это все очень громоздко. Даже если я точно знаю, какую галочку и где нужно нажать, то пока я до нее доберусь - проходит много времени. В этой модели ректора я отказался от отдельных классов для линий, окружностей, заливки, эффектов и так далее. А сделал все это методами одного класса. Хоть это не очень ООП-шно, но зато я теперь могу сосредоточится именно на графике а не на жонглировании классами. Класс Marker я оставил, поскольку он завязан на интерфейс, и пользователь по-прежнему его двигает по экрану. Центральный класс библиотеки называется незамысловато: Engine. Он делает почти все - парсит скрипт, компилит его и отрисовывает. Происходит это так: Engine содержит метод void Parse(string text). На вход подается текст скрипта. Engine парсит его, используя простой синтаксис <команда> <набор параметров>. Одна команда - в одной строке. Пример скрипта: Пример скрипта
Далее, каждая строка скрипта превращается в делегат. Например вот метод, обрабатывающий команду scale:
Распарсив каждую строку, Engine складывает сегенерированные делегаты в список List<Action>, который по сути представляет собой скрипт в откомпилированном виде. Поскольку мы генерируем делегаты внутри Engine, то замыкания автоматически захватывают переменные и поля класса Engine, что позволяет им манипулировать ими в дальнейшем. Теперь мы можем вызвать скрипт на выполнение методом Engine.Draw. Он выглядит очень просто:
Класс Engine также содержит открытые словари такого типа:
Например метод отрисовки лейбы, в которой показывается скорость корабля выглядит примерно так:
Команды делятся на операторы присвоения и на все остальные. Присвоения задают парметры класса Engine или переменные для общения с внешней программой. Параметры определяют такие вещи как цвет бордера, цвет заливки, толщина линий, размер шрифта и так далее. Одному и тому же параметру можно присваивать разные значения в процессе выполнения скрипта. Все команды отрисовки, следующие после задания параметра будут выполняться с данным значением (это кстати удобное отличие от предыдущей версии редактора, в котором нужно было задавать параметры для каждого объекта отдельно). Для более удобной работы с параметрами есть две специальные команды: save и restore. Команда save запоминает в стеке все текущие настройки графики, а команда restore - возвращает их из стека. Таким образом, если отдельный фрагмент скрипта хочет поменять параметры, он предварительно может вызвать save, сделать изменения параметров рисования, отрисоваться и затем вернуть все параметры в прежнее значение, вызвав restore. Это очень удобно - не нужно заботиться о том что один кусок скрипта испортит отрисовку другого куска. Кроме параметров, команды save и restore также запоминают и восстанавливают матрицу трансформации для Graphics. Это значит что если мы сделали например вращение Graphics, то команда resore вернет состояние графикса в прежнее состояние и остальная часть будет отрисовываться уже без вращения. Далее, для отрисовки используется два типа команд - одни просто соединяют маркеры линиями и фактически просто создают GraphicsPath. Это команды poly, circle, rays, text. Смысл которых - понятен из названия. Команда poly - задает набор маркеров, которые соединяются линией (полигон). Это может выглядеть так:
Обратите внимание, что эти команды еще не рисуют примитивы, они только создают GraphicsPath. Для того же, что бы отрисовать примитив используются еще две команды без параметров: draw и fill. Команда draw рисует только бордеры тех Path, которые были заданы до этого. Команда fill закрашивает полигоны, созданные выше. Для одних и тех же Path можно вызывать как draw, так и fill, а также и оба сразу. Таким образом, простейший скрипт, рисующий линию, выглядит так:
Скрипт Hello world будет выглядеть так:
Несмотря на то, что команд задания графики всего 4, на самом деле они могут иметь много параметров - как числовых так и маркеров, и в зависимости от них работать по разному. Например, команда text может принимать один маркер (тогда она рисует текст просто возле точки), два маркера (тогда текст рисует вдоль линии, соединяющей макеры), три маркера (тогда текст рисуется внутри прямоугольника, натянутого на три точки). Кроме того, на выполнение команды влияют текущие параметры, например параметр TextAlign - задающий выравнивание текста, FontSize - размер шрифта и так далее. Далее, самая многочисленная группа команд - это команды трансформаций. Они могут смещать, вращать, масштабировать и двигать графику и отдельные маркеры. Среди этих команд такие: rotate, offset, scale, move, rotateMarker, scaleMarker, offsetMarker, moveMarker. Смысл понятен из названия. Я не буду подробно останавливаться на каждом из них. Их действие можно понять из примеров скриптов, приведенных внизу страницы. Опишу только простое перемещение графики:
Еще есть специальная команда timer, которая автоматически меняет значения переменных во времени. Она позволяет делать анимации с автоматическим движением заданных фрагментов изображения. Теперь немного о эффектах, реализованых в движке. На данный момент реализовано два эффекта - Halo и Repeat. Первый - создает эффект размытия, второй - повторяет контур несколько раз. Для размытия я изначально планировал рендерить графику в растр, а затем размывать уже сам растр, но быстродействие так полчалось не очень, да и не очень это хорошо. Поэтому я сделал эффект размытия следующим образом. Контур рисуется с полупрозрачаностью и несколько раз поверх друг друга но с разной толщиной линий. Получается эффект размытия. Техника понятна если внимательно посмотреть увеличенную картинку ниже: Аналогично реализован эффект Repeat. Он тоже отрисовывает контур несколько раз, но смещая его каждый раз на некое расстояние. Теперь о редакторе в целом. Выглядит он вот так: Сделана подсветка синтаксиса, IntelliSense, динамическая компиляция при изменении текста. Ошибки выводятся в нижнем статус баре. Для текстового редактора использовался FCTB. Панель редактора маркеров поддерживает масштабирование (колесико мыши), групповое перемещение маркеров (с зажатым Ctrl), выставление новых маркеров (двойной щелчок мыши). Также, редактор поддерживает просмотр результата, так, как он будет выглядеть в целевом приложении (F5). Когда редактор сохраняет файл скрипта, он автоматически вставляет в него координаты маркеров, выставленных на панели маркеров. Ниже приведен полный код редактора, а также набор скриптов-примеров, и тестовое приложение, подключающее скрипты и отображающее в своем окне. Внутри нашей игры это выглядит примерно таким образом: Интерфейс HUD пока не дорисован. Кстати, кто умеет рисовать и у кого есть идеи оформления в стиле futuristic - могут помочь в написании скриптов HUD. Присылайте скрипты. Теперь что касается интеграции HUD в игру. Поскольку моя цель не просто сделать приложение, а сделать высокоэффективное и быстрое приложение, то все уперлось в быстродействие. Сначала я внедрил отрисовку HUD прямо в цикл отрисовки основной графики игры. Но поскольку этот цикл работает с высоким быстродействием - 65 fps, то HUD начал его подтормаживать и я получил снижение до 40 fps, а может и ниже, если нагрузить HUD элементами. Это меня не очень устроило, потому что в шутере FPS отрисовки очень важен, и я не хочу его снижать. Тут нужно работать в двух направлениях - либо оптимизировать движок HUD, либо вынести отрисовку HUD из главного цикла отрисовки. Оптимизацией движка HUD я займусь чуть позже, а вот саму отрисовку я все таки вынес в отдельный поток. Работает это так. В цикле отрисовки графики не вызывается отрисовка из Engine, а отрисовывается буферный битмап. В то время, как этот битмап рисуется, Engine, отрисовывает себя в другом битмапе, в отдельном потоке. Потом буферы меняются местами, и все повторяется. Классическая двойная буферизация. Я не использовал встроенный BufferedGraphics потому что он кривоватый во фреймворке и использует неправильный формат пикселей. В общем - не годится. Поэтому был написан собственный класс для двойной буферизации: DoubleGraphBuffer
Это позволило оставить FPS главной отрисовки на приемлемом уровне (60 fps) и вынести отрисовку HUD в отдельный поток и с более низким FPS (HUD обновляется не чаще 20 fps). Это хорошо, потому что HUD совсем не обязательно отрисовывать по 60 раз в секунду. Он меняется относительно медленно в процессе игры, и потому его можно обновлять не чаще 20 fps. Исходник и бинарник редактора, примеры скриптов здесь. Текущая версия игры здесь (бинарник) и здесь (исходник). | |||||||||||||||||||||||||||||||||||||||||||||||||||||||
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
Всего комментариев 7
Комментарии
-
Стал разбираться в исходниках, но наткнулся на несколько проблем:
Во-первых, я не нашел в архиве, собственно, самого главного-исходника библиотекиFControls.dll. Может, вы его забыли положить?
Или я жестко туплю.
Во-вторых, существует проблема с точками(видимо, из-за локалей), из-за чего у меня запустился только один(первый) из тестовый скриптов, который не содержит точек. Для остальных строка с точками "имела неверный формат", а место, где это происходит, я не вижу, ибо оно внутри библиотеки
Запись от EvilFromHell размещена 02.04.2016 в 03:14
-
Да, и игра у меня, видимо по той же причине, выглядит не так, как должна согласно статье: из всего, что видно на скрине, у меня отображается только выбор оружия.Запись от EvilFromHell размещена 02.04.2016 в 03:26
-
Запись от Storm23 размещена 02.04.2016 в 12:22
-
Запись от Phoelix размещена 31.05.2016 в 12:48
-
Запись от ashsvis размещена 20.02.2019 в 17:51
-
offset - просто сдвиг координат
move - это команда относится к анимации. Она сдвигает графику вдоль path
Например вот такой скрипт двигает окружность вдоль сторон треугольника:
Code 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
m1 = 280;232 m2 = 347;103 m3 = 399;237 m4 = 235;437 //задаем таймер TIME = 0 timer TIME 0.2 0 1 //задаем путь для анимации poly m1 m2 m3 //двигаем последующую графику вдоль пути (по таймеру TIME) move TIME //отрисовываем окружность circle m4 20 fill
Запись от Storm23 размещена 20.02.2019 в 23:32
-
Запись от ashsvis размещена 21.02.2019 в 18:45

Или я жестко туплю.

