Форум программистов
 
Контакты: о проблемах с регистрацией, почтой и по другим вопросам пишите сюда - alarforum@yandex.ru, проверяйте папку спам! Обязательно пройдите активизацию e-mail.

Вернуться   Форум программистов > разработка игр, графический дизайн и моделирование > Gamedev - cоздание игр: Unity, OpenGL, DirectX
Регистрация

Восстановить пароль
Повторная активизация e-mail

Здесь нужно купить рекламу за 20 тыс руб в месяц! ) пишите сюда - alarforum@yandex.ru
Без учёта ботов - 20000 человек в день, 350000 в месяц.

Ответ
 
Опции темы
Старый 03.03.2019, 01:00   #1
SaiLight
Форумчанин
 
Аватар для SaiLight
 
Регистрация: 10.01.2009
Сообщений: 131
Хорошо Galaxy Boom mini: Процесс разработки

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

Оба эти проекта были достаточно тепло восприняты и так же тепло забыты по причине отсутствия обновлений. Но в этой теме, с позволения администрации, я исправлю данную оплошность и постараюсь осветить процесс разработки новой версии Galaxy Boom mini, включающей в себя огромное количество подпроектов. Уже 3 года прошло с момента появления на свет GBM и почти 5 лет теме о графическом движке, но время не было потрачено впустую: все эти годы ушли на подготовку и получение знаний, необходимых для разработки проекта такой сложности. А самому миру Galaxy Boom и всем его основным персонажам уже около 10 лет, но это уже совсем другая история.

Итак, пара слов о том, что планируется в итоге. Сетевая 2D-игра (возможно, с сюжетной кампанией) с роботами, бомбами и интересными, на наш взгляд, геймплейными решениями. Технически: разрабатывается на Delphi XE; сетевая часть, скорее всего, будет написана с использованием библиотеки Synapse и TCP-протокола; сама игра будет реализована с использованием нового для нас ECS (Entity Component System)-шаблона проектирования. Вот некоторые из модулей, включенных в проект, над которыми в данный момент ведется работа:
  • Графический движок Perfect Engine 3 (новая версия).
  • Система 'резинового' графического интерфейса.
  • Менеджер ECS.

Теперь вкратце о каждом из них.

Perfect Engine 3. С момента разработки второй версии движка многое изменилось. Теперь здесь есть полноценный OpenGL-рендер с буферными объектами (против GLBegin/GLEnd в предыдущей версии), рендером в текстуру и шейдерными эффектами. Сам движок стал более структурным, а место кучи callback-функций в нем занял абстрактный рендер со своим набором свойств и методов, от которого и наследуются остальные рендеры.

Система UI. В данный момент, как раз, ведется ее разработка. Это модуль с классами - прототипами будущих элементов графического интерфейса и модуль менеджера UI с управлением сценами, а также, возможностью загрузки интерфейса из json-файла и его стилизации при помощи файла стилей. Для автоматической стилизации компонентов используются методы встроенной библиотеки RTTI.

Менеджер ECS. Набор классов, реализующий взаимодействие между Сущностями, Компонентами и Системами и удобную работу с ними посредством Менеджера событий.

Игра пока находится на самой ранней стадии; разработка продвигается медленно, но верно. Ранее мы не сталкивались ни с сетевыми играми, ни с ECS-шаблоном, и таких крупных проектов до сего момента на себя не брали. В финале первого этапа разработки должна получиться тестовая сетевая игра без графики и геймплея будущей игры, которая даст нам уверенность в правильности выбранных нами инструментов и знания относительно программирования сетевых игр. Уже в рамках разработки этой простой игры мы столкнулись с необходимостью написания ECS и UI-менеджеров, а также, других незначительных модулей. В дальнейшем, за счет правильно построенной структуры, планируется довести этот сырой продукт до полноценной, запланированной нами изначально, игры.

Всех, кому интересен процесс разработки, приглашаю в данную тему. В сообщениях ниже чуть более подробно собираюсь осветить некоторые аспекты уже готовых модулей ну и просто буду стараться не забрасывать эту тему, по возможности, выкладывая обновления и сообщая об основных новостях разработки.
SaiLight вне форума Ответить с цитированием
Старый 04.03.2019, 18:02   #2
SaiLight
Форумчанин
 
Аватар для SaiLight
 
Регистрация: 10.01.2009
Сообщений: 131
По умолчанию



В третьей версии движок Perfect Engine и рендер для него были полностью переписаны, код проекта перенесен на Delphi XE и использует новые возможности языка. В данный момент доступен только OpenGL-рендер, его функциональность, как и функциональность самого движка, сильно расширены. Сейчас Perfect Engine 3 находится на стадии тестирования и доработки.

Изменения коснулись не только основных файлов движка, но и дополнительных модулей и объектов. В частности, доработан объект камеры, упразднен тип TAlphaColor, добавлены типы TRect и TRGBA и многое другое. Начнем по порядку.

Камера.

Самое незаметное изменение здесь - в методе 'shake' (тряска): теперь камера плавно перемещается в выбранную точку при раскачивании (вместо резкого перемещения в PE2). Также, появились новые удобные свойства - slideInfo и shakeInfo с информацией о текущем состоянии перемещения (скольжения) и раскачивания, что дает возможности, к примеру, применять размытие кадра в движении при быстром перемещении камеры:

Код:
TCameraSlide = record
  targX, targY: Single;//Положение цели
  speed: Integer;//Скорость (в процентах, от 0 до 100)
end;

TCameraShake = record
  shiftX, shiftY: Single;//Максимальное смещение
  curShiftX, curShiftY: Single;//Текущее смещение
  steps: Integer;//Общее количество раскачиваний
  curStep: Integer;//Текущий шаг раскачивания
  strength: Integer;//Сила раскачивания
  isShaking: Boolean;//Качается ли камера в данный момент?
end;
Пожалуй, это все: камера, по-прежнему, самый скромный объект движка. Вот список ее методов:

Код:
move(x, y: Single);//Моментально переместить в указанную позицию
slide(x, y: Single);//Плавно переместить в указанную позицию
shake(strength, steps: Integer);//Трясти камеру
При помощи свойства 'isActive' можно отключить камеру для последующих выводимых объектов (например, графический интерфейс). Метод 'apply' используется движком для преобразования координат всех выводимых объектов с учетом координат камеры и ее смещения при раскачивании.

Рендер.

OpenGL-рендер, используемый в движке, теперь работает намного быстрее и имеет огромное количество новых возможностей. Вместо старых glBegin/glEnd здесь используются буферные объекты (VBO), есть возможность рендера в текстуру и смены буфера вывода, присутствуют шейдеры и шейдерные эффекты (цветокоррекция, размытие, размытие в движении, резкость, ...). Вот список всех фильтров, доступных на данный момент:
  • Тонирование изображения (tone). Цветные участки изображения окрашиваются в указанный цвет.
  • Смена цвета (recolor). Красный цвет на изображении перекрашивается в указанный.
  • Цветокоррекция (correction). Коррекция цвета с учетом параметров смещения компонентов цвета в системе HSV (цветовой тон, насыщенность, яркость).
  • Заливка (fill). Полная заливка текстуры указанным цветом с сохранением прозрачных участков и указанием степени заливки.
  • Размытие (blur, motionBlur, fullBlur). Различные варианты размытия.
  • Резкость (sharp).
  • Рассеянное свечение (bloom).
  • Наложение узора (scanline). По умолчанию это эффект чересстрочной развертки (горизонтальные линии), но можно установить любое изображение, заменив его в папке с шейдерами (например, шум).
  • Искажение (displace). Искажение финального кадра в соответствии с картой искажений (например, для имитации взрывов).

В данный момент система фильтров дорабатывается для более удобного использования. Не лишним будет сказать, что теперь объект рендера наследуется от специального абстрактного класса TAbstractRender; этим решением избегается необходимость в куче callback-функций движка, назначаемых при инициализации рендера, как это было в PE2.

Движок.

Сам движок тоже претерпел значительные изменения. В функцию инициализации теперь передается объект рендера:

Код:
TPerfectEngine.init(handle, clientWidth, clientHeight, TOpenGlRender.create());
Perfect Engine, по-прежнему, может выводить как одиночные изображения, так и кадры анимации (спрайта), причем:
  • Процесс разбиения на кадры происходит при загрузке изображения, а не перед каждым выводом, как было раньше.
  • Теперь поддерживаются прямоугольные кадры (PE2 работал только с квадратными).

Что касается шрифта - он, как и прежде, может либо браться из системы, либо, загружаться из .ttf-файла в папке с программой: рендер сгенерирует в памяти спрайт с символами шрифта, который будет использоваться при выводе текста, подстраиваясь под нужные размеры с помощью технологии Mipmap. Помимо стандартных методов вывода (textOut) и подсчета ширины строки (getTextWidth) теперь появились методы для работы с форматированными строками: textOutF() и getTextWidthF():

Код:
pe.textOutF('Perfect [#ffff00ff]Engine[#]');//Здесь слово 'Engine' будет покрашено в желтый цвет
Также, движок теперь умеет считать FPS - информацию о количестве кадров в секунду можно получить, обратившись к специальному одноименному свойству. Работа с Perfect Engine, по-прежнему, проста и удобна:


Код:
const
  MAP: TArray<TArray<Byte>> = [
    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    [1, 2, 2, 2, 3, 9, 9, 1, 2, 2],
    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    [0, 0, 0, 10, 8, 8, 8, 8, 10, 0],
    [0, 0, 0, 4, 12, 12, 12, 12, 4, 0],
    [0, 0, 0, 5, 12, 12, 12, 12, 5, 0],
    [0, 0, 0, 5, 0, 13, 13, 0, 5, 0],
    [0, 0, 0, 6, 0, 0, 0, 0, 6, 0],
    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
  ];

...

procedure TFrmMain.draw();
var
  i, j: Integer;
begin
  for i := 0 to high(MAP) do begin
    for j := 0 to high(MAP[i]) do
      pe.drawFrame(land, MAP[j][i], i * 64, j * 64, 64, 64);
  end;
end;

procedure TfrmMain.formCreate(sender: TObject);
begin
  TPerfectEngine.init(handle, clientWidth, clientHeight, TOpenGlRender.create());
  pe.onDraw := draw;
  land := pe.loadTexture(appPath + '/images/land.png', 64, 64);
end;

procedure TfrmMain.FormDestroy(Sender: TObject);
begin
  TPerfectEngine.remove();
end;
При выводе любого примитива теперь поддерживается указание необязательных параметров: поворот, масштабирование и уровень прозрачности. Параметр 'isCenter' (смещение позиции объекта в его центр) был упразднен.

По ссылке ниже - тестовая программа (без исходника), демонстрирующая некоторые возможности движка.


Последний раз редактировалось SaiLight; 04.03.2019 в 18:13.
SaiLight вне форума Ответить с цитированием
Старый 05.03.2019, 18:29   #3
SaiLight
Форумчанин
 
Аватар для SaiLight
 
Регистрация: 10.01.2009
Сообщений: 131
По умолчанию



Библиотека пользовательского интерфейса состоит из двух модулей: uiElement (реализация классов-прототипов будущих элементов интерфейса) и uiManager (менеджер интерфейса). Данная библиотека является дополнением к движку Perfect Engine 3.

uiElement

В этом модуле описана общая структура классов, предоставляющих основные возможности для реализации будущих элементов интерфейса. Система основана на подходе, заимствованном из системы VCL в Delphi. Каждый элемент может содержать в себе набор вложенных элементов так, что весь набор элементов представляет собой дерево; взаимодействие между элементами реализуется при помощи рекурсивных функций. Внешнее взаимодействие с интерфейсом достигается путем передачи корневым элементам (сценам) специальных сообщений:

Код:
TUiMessage = (
  msgUpdate,
  msgDraw,
  msgParentChangePos,
  msgParentChangeWidth,
  msgParentChangeHeight,
  msgMouseMove,
  msgMouseDown,
  msgMouseUp,
  msgKeyDown,
  msgKeyUp
);
Система интерфейса задумана как 'резиновая', координаты и размеры ее элементов могут задаваться либо в пикселях, либо, в процентах. Эта возможность реализована в корневом классе TUiElement, отвечающем за расчет координат и размеров элементов. В конструктор класса передаются специальные параметры: isPrcX, isPrcY, isPrcWidth, isPrcHeight, показывающие единицы измерения, в которых задаются данные параметры элемента.

Свойства x, y, width, height предназначены для получения и установки одноименных параметров в заданных единицах измерения. Для получения пиксельных вариантов (если параметры задаются в процентах) существуют свойства pxX, pxY, pxWidth и pxHeight. Более того, для вывода элементов и взаимодействия с ними нужно иметь информацию о реальных координатах элемента на экране. Эта информация кэшируется при каждом изменении позиции и размеров в дереве элементов; получить ее можно при помощи свойств scrX и scrY.

Также, присутствуют свойства offsetX, offsetY и origin: первые два задают пиксельное отклонение от текущей позиции, а последнее - смещает точку вывода элемента следующим образом:



Если требуется вывести элемент у правой границы родителя с отступом сверху и справа в 10 пикселей, то такой набор свойств поможет это сделать:

Код:
isPrcX := true
x := 100
origin := 1
offsetX := -10
offsetY := 10
Класс TUiInputElement реализует базовое взаимодействие с мышью и клавиатурой, а также, предоставляет потомкам доступ к полям FIsHovered, FIsPressed и FIsFocused. Основываясь на этих данных, можно удобно реализовать смену отображения при наведении мыши, нажатии или при получении элементом фокуса.

Ко всему прочему, данный класс имеет поле FLink, указывающее имя сцены для автопереключения сцен, реализованного в менеджере.

Класс TUiVisualElement является общим предком для всех визуальных элементов. Среди его свойств - isUseCamera, определяющее, использовать ли камеру движка при выводе элемента и isVisible. Невидимые элементы, как и неактивные, не обрабатывают и не распространяют сообщений по вложенным элементам.

Класс TUiTextElement реализует базовый функционал вывода текста, его свойства очень похожи на аналогичные css-свойства в веб-разработке:

Код:
padding: TUiRect;//Отступ текста от границ элемента
xPadding, yPadding: Single;//Сумма отступов по одной из осей
fontName: String;//Имя шрифта
fontSize: Integer;//Размер шрифта
fontColor: TRGBA;//Цвет шрифта
textAlign: Byte;//Выравнивание текста
lineHeight: Single;//Междустрочный интервал
autoHeight: Boolean;//Используется ли автовычисление высоты
formatted: Boolean;//Используется ли форматированный текст
При всяком изменении текста, а также, размеров элемента или отступов (padding) вызывается метод cacheRecalc, кэширующий текст в специальной структуре для более оптимизированного вывода. Этот метод разбивает текст на строки в зависимости от ширины элемента и отступов от его краев.

И, наконец, класс TUiScene наследуется от TUiInputElement и реализует сцену - корневой элемент дерева.

uiManager

Данный модуль является оберткой над uiElement и реализует работу со сценами, а также, загрузку интерфейса из json-файла и его стилизацию при помощи файла стилей. Вот набор файлов, с которыми работает менеджер:
  • markup.json - файл с разметкой и базовыми стилями
  • styles.css - файл со стилями
  • const.ini - файл с текстовыми константами

Файл с разметкой содержит в себе дерево объектов, представленное в формате json, где каждый объект имеет следующие поля:
  • class - класс элемента для автоматического создания через rtti
  • style - стиль элемента из таблицы стилей
  • scene - имя сцены-родителя, только для корневых элементов
  • isPrcX, isPrcY, isPrcWidth, isPrcHeight - описано выше, необязательные параметры
  • params - объект с перечислением свойств элемента и их значений
  • elements - объект с перечислением вложенных элементов такой же структуры

Файл стилей имеет следующую структуру:

Код:
.имя стиля
  свойство = функция(значение)
  свойство = значение
  ...
При стилизации параметры, указанные в разделе 'params' json-файла, перезаписывают одноименные параметры из таблицы стилей, если таковые имеются. Сам процесс стилизации реализован за счет методов встроенной библиотеки rtti (автоматически), благодаря чему, при появлении новых свойств в дочерних классах не требуется доработки функции записи. Свойства могут быть записаны как напрямую, так и с использованием специальной функции. На данный момент доступны следующие специальные функции:
  • rgba(r, g, b, a) - устанавливает цвет из четырехкомпонентного параметра
  • rect(left, right, top, bottom) - устанавливает свойство типа TUiRect
  • img(path) - вызывает функцию движка для загрузки изображения и записывает в свойство полученный индекс текстуры
  • font(name) - вызывает функцию движка для загрузки шрифта и записывает в свойство его имя
  • const(name) - загружает текстовую константу по имени

Вот пример работы функции:

Код:
procedure TUiStyles.setRgba(obj: TUiElement; prop: TRttiProperty; params: String);
var
  rgba: TRGBA;
  value: TValue;
  arr: TArray<String>;
begin
  arr := params.explode(',');
  rgba := TRGBA.make(
    arr[0].float(),
    arr[1].float(),
    arr[2].float(),
    arr[3].float()
  );

  TValue.make(@rgba, typeInfo(TRGBA), value);
  prop.setValue(obj, value);
end;
Также, менеджер имеет следующие публичные методы:
  • getElement(sceneName, elementName) - получение элемента
  • getConst(name) - получение текстовой константы
  • sendMessage(...) - отправка сообщения текущей и глобальной сценам
  • load(constFile, stylesFile, markupFile) - загрузка интерфейса из файлов
  • selectScene(name) - выбор сцены (создание новой при отсутствии)

Создание сцен происходит автоматически при парсинге разметки. Помимо общего набора сцен, в менеджере присутствует глобальная сцена (main), элементы которой видны всегда. В игре такая сцена может содержать, например, окно консоли, которое может быть вызвано в любой момент (как в самой игре, так и в меню). При работе со сценами, пользователю доступны специальные события: onSceneShow и onSceneHide, где могут выполняться загрузка и освобождение игровых ресурсов, а также, другие необходимые действия.

[Продолжение ниже]

Последний раз редактировалось SaiLight; 05.03.2019 в 18:40.
SaiLight вне форума Ответить с цитированием
Старый 05.03.2019, 18:30   #4
SaiLight
Форумчанин
 
Аватар для SaiLight
 
Регистрация: 10.01.2009
Сообщений: 131
По умолчанию

Ко всему прочему, менеджер организует автопереключение сцен. Если в дереве присутствует элемент с установленным свойством link, менеджер назначает ему событие onMouseDown, где и производится переключение к указанной в этом свойстве сцене.

Планы на будущее

На ближайшее будущее планируется реализация класса TUiEditElement с базовым функционалом для элементов с возможностью редактирования текста - на нем в дальнейшем будет основан элемент TEdit. Также, подумаем над возможностью подключения нескольких стилей к одному элементу.

В представленной ниже тестовой программе на примере класса TUiButton, унаследованного от TUiTextElement реализованы основные функции библиотеки, вот их примерный список:
  • Автоопределение высоты текстового элемента
  • Определение позиции и размеров в пикселях и процентах
  • Свойства origin и offset (элемент 'приклеен' к правому краю родителя с отступом)
  • Форматированный текст
  • Использование специальных функций (например, загрузка изображения)
  • Автопереключение сцен
  • Глобальная сцена (элемент виден на всех остальных сценах)


SaiLight вне форума Ответить с цитированием
Старый 08.03.2019, 22:01   #5
SaiLight
Форумчанин
 
Аватар для SaiLight
 
Регистрация: 10.01.2009
Сообщений: 131
По умолчанию

Это последнее из трех моих вводных сообщений, здесь повествуется о реализации игровой системы. Впервые в своей практике мы столкнулись с ECS-подходом к написанию игр и решили использовать его, так как такой подход обещал нам возможность избежать некоторых структурных проблем при проектировании. Также, говорят, он позволяет обеспечить хорошую расширяемость.

Общие сведения

ECS - Entity-Component-System - шаблон проектирования, который позволяет разделить всю игровую логику на три части: Сущность, Компонент и Система. При проектировании с помощью ECS-подхода в игре перестают существовать объекты в нашем привычном понимании: боевые единицы, здания, декорации, а вместе с тем, уходит на второй план и привычное наследование, дерево классов. Все это заменяется набором сущностей и компонентов, которые обрабатываются системами.

Компонент - это набор данных без возможности управления ими. Для того, чтобы вычленить из общего списка компонентов разных классов определенные 'объекты', используются сущности: компоненты с одним ID сущности относятся к одному 'объекту'. Системы, в свою очередь, занимаются обработкой всех игровых процессов и, по необходимости, имеют доступ к списку компонентов для работы с ними.

К примеру, есть render-система, обрабатывающая вывод объектов на экран. Каждый шаг таймера эта система проходит по списку render-компонентов и, используя данные в них (например, цвет, размер, форму, если говорим о простых фигурах), выводит изображение на экран. Если нам требуется добавить в игру объект, имеющий графическое представление, мы просто добавляем в набор еще один render-компонент и связываем его с ID той сущности, к которой он должен относиться.

Вообще, тема сложная. Выше описано мое собственное представление, ниже - то, как это реализовано у нас. Идею своей реализации мы почерпнули, изучая статью о ECS-системах, представленную на одном из англоязычных форумов. Если у кого-либо появятся вопросы по нашей реализации, буду рад ответить на них в этой теме.

Итак, наша реализация.

В своей реализации мы разбили всю библиотеку на 5 модулей:
  • ecsEntity
  • ecsComponent
  • ecsSystem
  • ecsEvent
  • ecsManager

Сущность

В модуле ecsEntity описан класс-прототип всех игровых сущностей TEcsEntity, у которого всего одно свойство - id и один метод - конструктор. Все классы игровых сущностей будут наследоваться от этого класса и в собственном уникальном методе конструктора создавать весь необходимый набор компонентов. Это сделано для того, чтобы собрать код создания компонентов для разного типа объектов в модулях с описанием этих типов. Вот, к примеру, код конструктора для сущности 'figureEntity' (прототип боевой единицы в нашей игре):

Код:
constructor TFigureEntity.create(x, y: Single; color: Cardinal; control: Boolean = false);
var
  transformComponent: TTransformComponent;
  renderComponent: TRenderComponent;
  damageComponent: TDamageComponent;
  moveComponent: TMoveComponent;
  physicComponent: TPhysicComponent;
  controlComponent: TControlComponent;
begin
  transformComponent := TTransformComponent.create(ecs.curEntity);
  transformComponent.x := x;
  transformComponent.y := y;
  transformComponent.width := 50;
  transformComponent.height := 50;
  ecs.addComponent(transformComponent);

  renderComponent := TRenderComponent.create(ecs.curEntity);
  renderComponent.color := color;
  ecs.addComponent(renderComponent);

  damageComponent := TDamageComponent.create(ecs.curEntity);
  ecs.addComponent(damageComponent);

  moveComponent := TMoveComponent.create(ecs.curEntity);
  moveComponent.speed := 5;
  ecs.addComponent(moveComponent);

  ...

  if (control) then begin
    controlComponent := TControlComponent.create(ecs.curEntity);
    ecs.addComponent(controlComponent);
  end;
end;
Мы видим, что в конструктор TFigureEntity передается набор параметров, уникальный для этого типа объектов. Причем, есть статичные параметры, задающиеся прямо в коде, а есть динамические - передающиеся в конструктор извне.

Также, в модуле ecsEntity описан менеджер сущностей, занимающийся хранением и обработкой списка сущностей: добавление, удаление и т.д.

Компонент

В модуле ecsComponent, также, описаны базовый класс компонента и менеджер компонентов. Базовый класс TEcsComponent содержит только одно свойство 'entity' - id сущности, к которой привязан данный компонент. Менеджер представляет собой особый интерес: помимо прочих, он имеет методы получения компонента по типу и ID сущности и списка всех компонентов определенного типа (для обхода системой):

Код:
function getComponent(cmpType: String; entity: Integer): TEcsComponent;
function getComponents(cmpType: String): TDictionary<Integer, TEcsComponent>;
Сам список компонентов представлен в виде сложной структуры, основанной на стандартном классе TDictionary (хэш-таблица):

Код:
TComponentsList = TDictionary<String, TDictionary<Integer, TEcsComponent>>;
Таблица в таблице. Здесь ключ в первой таблице - имя типа компонентов (например, TRenderComponent), а значение - список компонентов этого типа, где, в свою очередь, ключами выступают ID сущностей, к которым они привязаны.

Системы и события

Как и в предыдущих двух модулях, в ecsSystem описаны классы системы и менеджера систем. Причем, первый из них имеет только виртуальную процедуру update(), но он, также, является наследником класса TEcsListener. Что это за класс?

Его можно найти в модуле ecsEvent, который содержит всего три класса: TEcsListener, TEcsEvent и TEcsEventManager. Любой наследник класса TEcsListener может быть 'слушателем' события - таким образом, события рассылаются менеджером всем слушателям, что подразумевает возможность наличия нескольких слушателей для одного события.

Метод addListener(eventType, listener) добавляет слушателя в список для конкретного типа события (обычно вызывается при старте программы). Каждый раз при возникновении нового события (например gameStartEvent - начало игры) вызывается метод addEvent(event), куда передается объект - наследник TEcsEvent со своим набором параметров (уникальным для каждого типа события).

В обработчике update() менеджер обходит список событий, получает для каждого из них список слушателей и отсылает событие им, после чего список событий очищается. Для приема события на стороне слушателя в классе TEcsListener реализован виртуальный метод callEvent(event), который и вызывается менеджером.

Общий менеджер

Для упрощения работы с системой реализован класс общего менеджера TEcsManager со статичным методом инициализации, создающим глобальный объект 'ecs', к которому можно обратиться из любого модуля библиотеки. Этот класс является оберткой над остальными модулями: хранит в себе объекты вышеописанных менеджеров и реализует удобное обращение к ним посредством специальных методов.

Вот, пожалуй, и все. Наверняка, в этой реализации найдется достаточно подводных камней, так как это наш первый опыт написания подобной системы, и мы пока не опробовали ее на практике. Если такое случится - будем делиться информацией о возникших проблемах и о способах их решения в этой теме. Примера здесь не будет, так как пример к данному сообщению - и есть сама мини-игра - прототип Galaxy Boom mini, проект, собранный из модулей, описанных в этом и предыдущих сообщениях. Обо всех новостях я постараюсь сообщать в этой теме. Если кому-нибудь она покажется полезной или возникнут вопросы по реализации отдельных элементов программы - буду рад пообщаться.

Последний раз редактировалось SaiLight; 09.03.2019 в 10:33.
SaiLight вне форума Ответить с цитированием
Старый 27.03.2019, 17:03   #6
SaiLight
Форумчанин
 
Аватар для SaiLight
 
Регистрация: 10.01.2009
Сообщений: 131
По умолчанию

Рад, наконец, обновить эту тему. Понемногу развиваем проект, когда появляется такая возможность. Вот список изменений с момента последнего обновления темы:

1. В менеджер UI добавлен метод showPopup(), который ищет элемент с указанным именем в глобальной сцене и отображает его. Пока все всплывающие окна планируется записывать в глобальную сцену и вызывать этим методом. К таким окнам может относиться, например, меню паузы, если оно расположено не на отдельной сцене (под ним отображается игра). Насколько этот подход удобен, определим позже, по опыту его использования.

2. Исправлены проблемы с флагами isVisible и isActive. По логике библиотеки, элементы с отключенным isVisible не обрабатывают и не передают принятые сообщения. Проблема была в том, что список стилей при стилизации не поддается сортировке, и если среди стилей попадется 'isVisible = false', то это свойство может записаться раньше остальных, среди которых присутствуют и те, правильная установка которых требует обработки и передачи сообщений. В результате, установка таких свойств обрабатывалась неправильно. Проблема решена выносом isVisible/isActive за пределы стилей, в глобальную секцию разметки элемента.

3. Добавлен класс TUiEditElement - однострочный элемент с возможностью ввода текста. Движок пока не умеет обрезать выводимые примитивы (glScissor(), если говорить об openGl), что представляло самую большую трудность при разработке данного класса. Текст переносится посимвольно, а символы могут иметь разную ширину, причем, это различие может быть довольно ощутимым ('i', 'W'). В результате, при вводе или стирании текста, у правой границы элемента курсор постоянно прыгал, что выглядело очень некрасиво. Еще одна проблема: вместо исчезнувшего с левой стороны широкого символа, справа могли появиться сразу два-три узких.

Обе проблемы были решены одновременно добавлением понятия 'Направление вывода' (FDirection) и соответствующих полей FStart и FFinish, от которых отсчитываются видимые на экране символы. Текст, выводимый слева (от FStart), прижимается к левому краю элемента, а текст, выводимый справа - к правому краю, в результате чего курсор перестает прыгать при вводе символов разной ширины. Правильно регулируя значение свойства FDirection при вводе и стирании текста, удалось добиться наибольшего удобства в использовании элемента, даже несмотря на отсутствие возможности обрезки выводимого текста при разной ширине символов.

Данный элемент на сегодня имеет только базовые функции: нет возможности выделять текст, копировать, вставлять и других приятных удобств. Элемент еще будет дорабатываться, а его промежуточную версию можно посмотреть в прикрепленном архиве (пока без исходников).

4. Добавлены классы TUiImageElement и TUiBorderedElement. Первый выводит изображение, а второй - разбивает указанное изображение на 9 частей и выводит их по всей своей площади, что будет полезно, например, при создании всплывающих окон с рамкой. Для правильной работы TUiBorderedElement дописана функция img в стилях - теперь она может принимать параметры - размеры кадра при загрузке спрайта с несколькими кадрами:

Код:
image: img(folder/image.png)//Так задается стиль для TUiImageElement
image: img(folder/image.png, 16)//А так - для TUiBorderedElement
Данные классы пока, также, находятся на стадии доработки. В прикрепленном архиве можно посмотреть работу TUiBorderedElement (двойная рамка вокруг всех элементов интерфейса).

5. Добавлена возможность менять курсор при наведении на элементы управления. Для этого в классе TUiInputElement созданы поле FCursor и событие onCursorChange, вызываемое при наведении на элемент указателя мыши. Одноименное событие добавлено в менеджер UI, на которое автоматически устанавливается перенаправление с onCursorChange всех элементов, считанных из разметки. В функции, подписанной на событие менеджера в основной программе, производится смена курсора. В прикрепленном архиве можно увидеть, как меняется указатель мыши при наведении на TUiEditElement.

6. Немного доработан движок: в структуру с описанием изображения (TTexture) добавлены поля frameWidth и frameHeight.

Планы на будущее

В ближайшем будущем планируется доработать класс TUiEditElement и менеджер UI. Также, в данный момент ведется работа над движком: мы стараемся сделать более удобной работу с фильтрами постобработки и искажения кадра. Надеюсь в следующем обновлении написать об этом подробнее и, возможно, уже поделиться примерами работы этих механизмов.

SaiLight вне форума Ответить с цитированием
Старый 31.03.2019, 17:19   #7
SaiLight
Форумчанин
 
Аватар для SaiLight
 
Регистрация: 10.01.2009
Сообщений: 131
По умолчанию

Выполнены последние доработки по библиотеке UI:

1. В класс TUiElement добавлен метод iterate() - данный метод проходит по всем вложенным элементам и выполняет переданную в него callBack-функцию. Первый параметр определяет, выполнять ли функцию для корневого элемента. Так теперь реализована стилизация элементов интерфейса:

Код:
scene.iterate(
  false,
  procedure(element: TUiElement) begin
    FStyles.stylize(element);
  end
);
2. Реализован алгоритм постстилизации - стилизация всех элементов разом после парсинга разметки. Небольшой задел на будущее: когда библиотека будет поддерживать составные элементы, которые создаются в конструкторе родительского, а не считываются из файла с разметкой - такие элементы тоже будут затронуты в процессе общей стилизации.

Также, добавлена возможность множественной стилизации: теперь имена стилей можно задавать через запятую, и все они будут последовательно применены к стилизуемому элементу.

Однако, появилась и довольно серьезная проблема: постстилизация применяет к элементам стили после завершения парсинга разметки, таким образом, стили, заданные прямо в разметке, имеют меньший приоритет. Для решения этой проблемы набор стилей из разметки добавляется в общий список с уникальным именем:

Код:
//Добавление списка стилей в общий набор
FStyles.addStyle('tmp_' + owner.name, TJson.toArray(params));

//Добавление имени стиля к элементу (в самый конец)
if (owner is TUiVisualElement) then
  TUiVisualElement(owner).style.add(',tmp_' + owner.name);
Таким образом, восстановлен изначальный приоритет стилизации.

Остальные доработки по библиотеке UI было решено оставить на потом - прежде всего, это доработки по классу TUiEditElement. Основа библиотеки уже имеется, далее будем дополнять ее только по мере необходимости (в том числе, это зависит и от дизайна UI, который пока отсутствует). В планах на ближайшее будущее - доработка движка (система фильтров постобработки) и завершение внедрения ECS-библиотеки в тестовый проект.
SaiLight вне форума Ответить с цитированием
Старый 06.04.2019, 20:37   #8
SaiLight
Форумчанин
 
Аватар для SaiLight
 
Регистрация: 10.01.2009
Сообщений: 131
По умолчанию

Система UI

При описании системы UI в одном из первых сообщений забыл упомянуть об одной интересной ее особенности. Еще с самого первого класса (TUiElement) элементы интерфейса имеют параметр LifeTime - время жизни, по прошествии которого элемент удаляется со всеми вложенными в него. Это может быть полезно для динамически создаваемых элементов, например, всплывающий текст с количеством нанесенного урона рядом с целью атаки.

В данном обновлении этот параметр отвязан от глобального игрового времени и привязан к системному (во избежание передачи дополнительного параметра 'текущее время').

Менеджер ECS

Система получила новый параметр - active (флаг активности).

События в менеджере событий разделены на Моментальные и Отложенные (выполняемые после всех остальных действий). Ранее были доступны только отложенные события; пока нет четкого представления по их использованию, решение об этом мы примем при тестировании ECS на реальных задачах.

Модуль Input

Один из написанных нами модулей, позволяет отслеживать нажатие заданных клавиш с указанной частотой:

Код:
input.addKey('num1', 49, ktOnce);//Одиночное срабатывание клавиши '1'
В данном обновлении модуль стал поддерживать события мыши - mouseMove и mousePressed, необходимые для функционирования системы графического интерфейса.

Все описанные выше модули постепенно подключаются к основной программе и в ходе этого подключения дорабатываются. Каждая библиотека имеет свою тестовую программу (подобную тем, что я прилагал к предыдущим сообщениям) для более удобной и быстрой отладки. Сейчас в основной программе создается необходимый набор событий и отлаживается работа систем. Также, параллельно ведем работу над графическим движком с целью облегчить программисту использование механизма рендера в текстуру и использование шейдерных фильтров.

Последний раз редактировалось SaiLight; 06.04.2019 в 20:56.
SaiLight вне форума Ответить с цитированием
Старый 09.04.2019, 21:35   #9
SaiLight
Форумчанин
 
Аватар для SaiLight
 
Регистрация: 10.01.2009
Сообщений: 131
По умолчанию



Творческое отступление

Немного расширю тему: небольшие творческие отступления, надеюсь, сделают ее менее скучной и ближе познакомят читателей с нашей игрой. Хватит скучных программных кодов и описаний непонятных библиотек - в творческих сообщениях будут публиковаться исключительно творческие материалы по игре.

Пока рано говорить о графике, но небольшие наработки в этой области уже имеются. Так, некоторое время назад думал над картинкой для главного меню, в результате чего получился ее предварительный набросок. Есть цветной вариант, но цвета там подобраны не лучшим образом, и этим вариантом пока делиться не буду.

Что ж, отбросим временно все разногласия - ради фотографии для главного меню можно ненадолго объединить усилия. Хотя, даже приблизительно знающим историю Galaxy Boom, один из них, явно, покажется лишним...

SaiLight вне форума Ответить с цитированием
Старый 14.05.2019, 16:13   #10
SaiLight
Форумчанин
 
Аватар для SaiLight
 
Регистрация: 10.01.2009
Сообщений: 131
По умолчанию

Пришлось на некоторое время отлучиться от основного проекта, чтобы завершить другой, тоже крупный, которым занимались чуть более, чем полтора года. Завершили удачно, и теперь свободны, можем полностью (насколько позволяет работа) посвятить себя разработке игры.

Но и по текущему проекту тоже было внесено немало доработок. Я не буду описывать их все, так как сейчас мы готовим крупное обновление для этой темы, в котором будет описана структура вообще всего проекта вместе со схемами - все, что есть на данный момент, и этого уже достаточно. Хотя, на вид все очень просто - квадратик бегает по экрану и стреляет в другой квадратик, который стоит на месте. Казалось бы, работы на день, но, вместе с тем, есть очень широкий функционал, который уже сейчас позволяет быстро реализовать довольно сложные вещи: Р - Расширяемость.

1. Появился наш первый составной элемент управления - консоль (какая-никакая, она еще требует некоторых доработок). Она просто появлялась на экране и исчезала при повторном нажатии клавиши 'ё', а мне очень хотелось, чтобы консоль выезжала сверху и уезжала обратно при отключении. Как я это сделал? Реализовал в классе TUiVisualElement метод animate(), который принимает на вход имя свойства, требуемое значение и время, а затем средствами RTTI производит анимацию указанного свойства в течение указанного времени. Притом, анимироваться одновременно может сколько угодно свойств (типа Integer или Single, разумеется). Вот, как теперь производится вызов консоли:

Код:
FConsole.animate('y', 0)//Третий параметр - 200ms по умолчанию
Анимируем значение свойства 'y' до 0 (изначально оно равно -50% от высоты экрана) - консоль плавно выезжает сверху. Если потребуется, могу предоставить код метода animate(), там его немного. Использовать можно много где; самое первое, что приходит на ум, - позиция всплывающего текста в игре, например, количество нанесенного урона рядом с целью атаки. Если объединить с параметром 'Время жизни', который, также, доступен для всех элементов управления, - эта возможность реализуется, можно сказать, 'из коробки'.

2. Ничего особенного, просто сравните код:

Код:
physicCmp := ecs.getComponent('TPhysicComponent', FControlEntity) as TPhysicComponent;//Так было
ecs.getComponent<TPhysicComponent>(FControlEntity);//Так стало
Не понимаю, как сразу не додумались, но теперь эта оплошность исправлена - метод 'getComponent()' теперь сам приводит тип, и код получения компонента выглядит намного короче, чем раньше.

3. А еще SpectreZ, наконец, закончил перевод статьи по хронологической модели программирования, которой мы будем пользоваться при разработке сетевой части, чем и займемся после того, как приведем весь код в порядок.

Последний раз редактировалось SaiLight; 15.05.2019 в 10:50.
SaiLight вне форума Ответить с цитированием
Ответ
Опции темы


Похожие темы
Тема Автор Раздел Ответов Последнее сообщение
Не разрешается отладка USB (Samsung Galaxy S4) Danila7 Java Мобильная разработка (Android) 1 18.09.2016 18:05
[Аркада] Galaxy Boom mini, Perfect Light SaiLight Gamedev - cоздание игр: Unity, OpenGL, DirectX 5 14.03.2016 21:19
проблемы с samsung galaxy ace II GT-I8160 (Android 4.1.2) Андрей201421 Мобильные ОС (Android, iOS, Windows Phone) 1 15.08.2014 20:06
Региональная блокировка Samsung galaxy SIII starsv Мобильные ОС (Android, iOS, Windows Phone) 4 24.02.2014 17:21
Galaxy Boom SaiLight Gamedev - cоздание игр: Unity, OpenGL, DirectX 17 24.12.2012 14:56