вторник, 8 июля 2014 г.

Интерфейс

В этой статье пойдет речь о том что я делаю для того чтобы Fallout 2 был кросс-платформенным.

Что такое интерфейс?

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

Где он может использоваться?

Есть такая проблема в мире - существует множество платформ, под которые должна работать ваша программа. Например, вы хотите чтобы ваша программа работала под Android, Windows, Linux, Macintosh. Как этого достичь? Неужели иметь 4 копии исходного кода, и когда что-то меняется вносить изменения во все 4 проекта?

Конечно нет. Достаточно вынести части программы, которые отвечают за вызовы системных функций в отдельный интерфейс. После этого достаточно сделать реализацию интерфейса под 4 операционных системы - и все. Весь остальной код использует вызовы функций интерфейса - его не надо переписывать.

Интерфейс к операционной системе в моем движке Falout 2

Так исторически сложилось что интерфейс к операционной системе похож на интерфейс других моих приложений, ну просто мне так удобнее. Я совсем не использую стандартную библиотеку языка с++ (только такие вещи как operator new, operator delete и другие служебные функции, которые нельзя не использовать) - так мне понятнее мой код и так он мне кажется гибче.

Глобальное пространство имен


  1. char chlower(char c) - возвращает символ 'c' в нижнем регистре.
  2. char chupper(char c) - возвращает символ 'c' в верхнем регистре.
  3. bool ischa(int c) - возвращает true если символ 'c' является буквой.
  4. int timeticks() - возвращает текущее время тиков со старта системы. Используется для инициализации генератора случайных чисел.

Пространство имен ui


  1. void timer(int milliseconds) - текущий ввод, процедура Input(), будет стараться возвращать событие InputTimer каждые 'milliseconds' миллисекунд.
  2. bool create(const char* title, bool full_screen_mode) - создать окно с заголовком 'title' возможно в полноэкраном режиме.
  3. void caption(const char* text) - смена заголовка окна на другой.
  4. void usepal(unsigned char* pal) - устанавливает палитру для текущего окна. Игра Fallout 2 использует палитру из 256 цветов. Эти цвета надо передать в эту функцию по 3 байта на цвет в порядке B,G,R.
  5. int input() - ключевая процедура. Останавливает выполнение программы до следующего события. Используя эту функцию программа не будет использовать 100% процессорного времени.

Реализуя вышеперечесленные функции можно сделать игру класса Fallout 2.

четверг, 12 июня 2014 г.

Ориентация

Во всех играх у существ необходимо знать направление их взгляда в зависимости от этого меняется их спрайты анимации и они как бы поворачиваются в сторону куда смотрят.


Тип переменной где храниться ориентация удобно сделать перечислением, например так:

enum directions Left, LeftUp, Up, RightUp, Right, RightDown, Down, LeftDown, Center};


Вообще в некоторых играх существует 16 направлений и для них созданы спрайты, в других 8 - по направлениям, указанным выше в перечислении. А вот Fallout 2 использует только 6 - по количеству сторон шестиугольника на которые разбито все игровое поле.

Анимация существ Fallout пронумерованы следующим образом:

0 - RightUp
1 - Right
2 - RightDown
3 - LeftDown
4 - Left
5 - LeftUp

То есть если персонаж смотрит вправо, он должен использовать 1 набор анимации.

Как определить направление взгляда существа?

Определяется оно очень просто. Обычно он смотрит туда куда вы кликните мышкой. Пускай у нас есть точка на экране x1, y1 в которой отображается кадр анимации существа и точка x2, y2 куда вы кликнули мышкой. Тогда функция для получения направления взгляда для Fallout следующая:

directions direction(int x1, int y1, int x2, int y2)
{
static const directions orientations[25] =
{
LeftUp, LeftUp, RightUp, RightUp, RightUp,
LeftUp, LeftUp, RightUp, RightUp, Right,
Left, Left, Center, Right, Right,
LeftDown, LeftDown, RightDown, RightDown, Right,
LeftDown, LeftDown, LeftDown, RightDown, RightDown
};
int dx = x2 - x1;
int dy = y2 - y1;
int div = imax(iabs(dx), iabs(dy));
if(!div)
return Center;
if(div>3)
div /= 2;
int ax = dx/div;
int ay = dy/div;
return orientations[(ay+2)*5+ax+2];
}

int d2o(directions d)
{
static int dt[] = {4, 5, 0, 0, 1, 2, 2, 3, 0};
return dt[d];
}

Алгоритм вычисления построен на разбитии всей области на квадраты с центром посредине. Затем мы вычисляем пропорциональные координаты и получаем индекс из массива. Для того чтобы формула работала с 8 или 16 возможными направлениями взгляда - необходимо переделать содержимое массива 'orientations'.

понедельник, 9 июня 2014 г.

Изометрия

Множество старых игр выводят графику используя изометрическую проекцию и Fallout 2 входит в их число. В этой статье я расскажу об изометрии и о сложностях, которые возникают при ее использовании на примере движка Fallout 2.


Тайлы Fallout

Каждый тайл Fallout 2 состоит из изображения размером 80 на 36 точек. Как видно из рисунка тайл сохраняет некоторые пропорции:


В пропорциональном соотношении он имеет:

int width = 5*sx;
int height = 3*sy;

Где sx = 16, sy = 12. Эти величины используются при расчете всех остальных координат. Если допустим вы делаете свою игру с другими размерами тайла, но тайл сохраняет те же пропорции, вы можете поменять константы sx, sy и все координаты будут считаться под ваши размеры.

Центр тайла расположен ровно посредине картинки, с координатами (40, 18). Перевод координат работает с центром тайла.

Перевод координат

Первая задача с которой я столкнулся при написании вывода графики это естественно перевод координат из декартовой системы в изометрическую. Сложность состоит еще и в том, что Fallout использует два вида карт:
  • Тайловый вид  карт используется для вывода ландшафта.

  • Гексагональный для вывода персонажей, объектов и вещей, лежащих на карте. Также к гексагональному виду карт привязан гексагон, который появляется под курсором на карте и указывает точку назначения при движении.

Перевод из тайловых индексов в координаты экрана

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

int tx = t%cols;
int ty = t/cols;
int x = 2*stx*ty - 3*stx*tx;
int y = 2*sty*ty + sty*tx;

где:

cols - это константа, показывающая ширину карты в тайлах. Допустим она равна 100.
t - это индекс тайла, координаты которого необходимо найти.

Обращаем внимание, что угол с координатами (0,0) соответствует правому верхнему углу карты и формула это учитывает. При выводе к полученному x и y необходимо добавить текущее положение камеры.

Перевод из гексоганальных индексов в координаты экрана

Учитывая то, что хэксовые индексы отличаются от тайловых тем, что в их в два раза больше и нечетные ряды немного сдвинуты получаем следующую формулу:

int tx = t%(cols*2);
int ty = t/(cols*2);
int x = stx*(ty - tx - tx/2) - 8;
int y = sty*(ty + tx - tx/2) - 32;

Как и в предыдущем случае, при выводе к полученному x и y необходимо добавить текущее положение камеры.

Перевод из координат экрана в тайловые индексы

Такой перевод необходим для осуществления так называемого "хиттеста" то есть проверки в каком тайле в данный момент находится мышка. Для получения формулы перевода воспользуемся существующими формулами, описанными выше и решим два уравнения с двумя неизвестными. Стоит отметить - учитывая то, что тайлы несколько сдвинуты по координатам изменим входящие координаты x, y чтобы они были относительно того места, где мы выводим вершину тайла. В итоге получим:

x -= 8;
y += 20;
int tx = (x-4*y/3)/64;
int ty = (x+4*y)/128;

int ti = ty*cols - tx;

Перевод координат экрана в индексы хэксовой карты

Также используется для определения индекса хекса над которым парит мышка. Чтобы вычислить формулу перевода преобразуем имеющиеся формулы и в результате получим:

x += 8;
y += 32;
int tx = (y/sty - x/stx)/2;
int ty = (3*y/sty + x/stx)/4;
int hi = ty*cols*2 + tx;

Но при такой формуле возникают ошибки из-за целочисленного деления и округления до целого. Поэтому данные формулы лучше преобразовать в следующий вид:

x += 8;
y += 32;
int tx = (y*stx - x*sty)/(2*stx*sty);
int ty = (3*y*stx + x*sty)/(4*stx*sty);

return ty*cols2 + tx;

Первый шаг

Как подключиться

  1. Скачать Microsoft Visual Studio Express Edition для c++. Ее можно скачать вот здесь. Для того чтобы удачно сказать надо иметь учетную запись Visual Studio .
  2. Подключиться к проекту https://github.com/Pavelius/fallout2.