Fallout 2: remake
Концепция движка игры Fallout 2. Инструкция по созданию Fallout 2 своими руками.
понедельник, 23 марта 2015 г.
среда, 11 марта 2015 г.
Сборка под GCC без зависимостей STL
Так можно поступать если ваш проект не использует стандартные библиотеки. Если это так, то вам не нужны некоторые компоненты, которые надо устанавливать на компьютер для запуска вашего приложения. Например, microsoft runtime.
Путем проб и ошибок понял, что адрес выше обозначенных массивов в компиляторе GCC можно получить так:
typedef void (*func_ptr)();
extern func_ptr __CTOR_LIST__[]; // конструкторы
extern func_ptr __DTOR_LIST__[]; // деструкторы
Как избавиться от ненужных зависимостей
- Не использовать стандартную библиотеку в коде. Это условие выполняется в рамках данного проекта.
- Добавить к проекту файл который содержит ваш служебный код, эмулирующий старт приложения с++.
- Компилировать проект в gcc со специальными опциями.
Специальные опции компиляции
Путем проб и ошибок выявил следующии опции компилятора, которые должны присутствовать в release сборке проекта:
- -fno-rtti. Данный ключ выключает использование возможностей динамического приведения языка c++. Иными словами не получится использовать директиву dynamic_cast. Но тогда из кода уберется информация о классах, что уменьшит объем выходного файла. Также из кода уберутся вызовы к служебным функциям, которые используются
для работы с этой информацией.
- -fno-exceptions. Данный ключ запрещает использование исключений. Если нет исключений, то компилятор не будет генерировать служебные функции, связанные с ними что незначительно ускорит программу и избавит нас от дополнительных зависимостей.
- -fno-access-control. В некоторых случаях компилятор генерит вызов служебной функции на проверку возможности доступа. Очевидно что нам этого не надо. Избавим код от дополнительных зависимостей и незначительно увеличим производительность.
- -fno-threadsafe-statics. Судя из описания данный ключ запрещает компилятору генерить вызов функций, который делают безопасными обращения к статическим переменный из разных потоков. Если мы не используем многопоточность можем смело использовать этот ключ. Если же мы используем многопоточность придется писать механизм защиты статических переменных самому. В данном проекте не используется многопоточность, поэтому можно применить данный ключ.
Специальные опции линкера
Также как и с компилятором у линкера должны быть свои опции.
- -e<имя_процедуры>. Стартовая точка входа в программу. Например, если объявить точку входа вида extern "C" entry(), в параметре надо указать -e_entry. Если не указывать эту опцию линкер создаст программу с точкой входа равной 0x410000 то есть первому байту в секции кода. Хорошо, если у нас один файл и наша процедура входа находится сверху и будет первой в секции кода. Но как показывает практика этого никогда не бывает.
- -nostartfiles. Признак того что не надо использовать стандартные процедура запуска среды окружения в программе. Мы ведь пишем свои.
- -nostdlib. Не использовать файлы стандартных библиотек и файлы начальной инициализации.
Файл со служебным кодом
Приведенными выше ключами мы отключили некоторые служебные функции, которые генерятся компилятором при определенных условиях. Плюс в том что теперь нам их не надо реализовывать. Но все равно необходимо реализовать ряд служебных функций, связанных с особенностями языка с++. В задачи этого кода входит:
- Запуск конструкторов статических классов.
- При выходе запуск деструкторов статических классов.
- Реализация некоторіх служебніх механизмов, например функции atexit().
Путем проб и ошибок понял, что адрес выше обозначенных массивов в компиляторе GCC можно получить так:
typedef void (*func_ptr)();
extern func_ptr __CTOR_LIST__[]; // конструкторы
extern func_ptr __DTOR_LIST__[]; // деструкторы
- atexit(). Сохраняет функцию для последующего вызова при выходе из программы. Для этого используется внутренняя структура, содержащая ссылку на функцию.
- entry(). Наша предопределенная точка входа в программу. Задача процедуры вызвать конструкторы, выполнить разбор коммандной строки, вызвать старт программы, затем вызывать деструкторы.
- __chkstk_ms(). Функнция вызывается если у локальной процедуры слишком много локальный параметров и есть опасение переполнение стека.
- __cxa__pure_virtual(). Адрес этой функции указывается в таблице чисто вируальных методов класса. Например, если в классе объявлен метод void virtual redraw() = 0 (как чисто виртуальный) и он будет случайно вызван, то будет вызвана эта функция. Ее задача сделать бесконечный цикл, чтобы обратить внимание пользователя что есть проблема.
вторник, 8 июля 2014 г.
Интерфейс
В этой статье пойдет речь о том что я делаю для того чтобы Fallout 2 был кросс-платформенным.
Что такое интерфейс?
Вообще интерфейс это некоторый набор функций, которые хорошо документированы и которые должен использовать конечный программист-пользователь для выполнения действий с определенным контекстом. Идея в том что реализация этих функций скрыта от программиста. Это позволяет в случае необходимости написать интерфейс к другому контексту без изменения основной программы.
Где он может использоваться?
Есть такая проблема в мире - существует множество платформ, под которые должна работать ваша программа. Например, вы хотите чтобы ваша программа работала под Android, Windows, Linux, Macintosh. Как этого достичь? Неужели иметь 4 копии исходного кода, и когда что-то меняется вносить изменения во все 4 проекта?
Конечно нет. Достаточно вынести части программы, которые отвечают за вызовы системных функций в отдельный интерфейс. После этого достаточно сделать реализацию интерфейса под 4 операционных системы - и все. Весь остальной код использует вызовы функций интерфейса - его не надо переписывать.
Интерфейс к операционной системе в моем движке Falout 2
Так исторически сложилось что интерфейс к операционной системе похож на интерфейс других моих приложений, ну просто мне так удобнее. Я совсем не использую стандартную библиотеку языка с++ (только такие вещи как operator new, operator delete и другие служебные функции, которые нельзя не использовать) - так мне понятнее мой код и так он мне кажется гибче.
Глобальное пространство имен
- char chlower(char c) - возвращает символ 'c' в нижнем регистре.
- char chupper(char c) - возвращает символ 'c' в верхнем регистре.
- bool ischa(int c) - возвращает true если символ 'c' является буквой.
- int timeticks() - возвращает текущее время тиков со старта системы. Используется для инициализации генератора случайных чисел.
Пространство имен ui
- void timer(int milliseconds) - текущий ввод, процедура Input(), будет стараться возвращать событие InputTimer каждые 'milliseconds' миллисекунд.
- bool create(const char* title, bool full_screen_mode) - создать окно с заголовком 'title' возможно в полноэкраном режиме.
- void caption(const char* text) - смена заголовка окна на другой.
- void usepal(unsigned char* pal) - устанавливает палитру для текущего окна. Игра Fallout 2 использует палитру из 256 цветов. Эти цвета надо передать в эту функцию по 3 байта на цвет в порядке B,G,R.
- int input() - ключевая процедура. Останавливает выполнение программы до следующего события. Используя эту функцию программа не будет использовать 100% процессорного времени.
Реализуя вышеперечесленные функции можно сделать игру класса Fallout 2.
четверг, 12 июня 2014 г.
Ориентация
Во всех играх у существ необходимо знать направление их взгляда в зависимости от этого меняется их спрайты анимации и они как бы поворачиваются в сторону куда смотрят.
Тип переменной где храниться ориентация удобно сделать перечислением, например так:
enum directions { Left, LeftUp, Up, RightUp, Right, RightDown, Down, LeftDown, Center};
Тип переменной где храниться ориентация удобно сделать перечислением, например так:
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;
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;
y += 20;
int tx = (x-4*y/3)/64;
int ty = (x+4*y)/128;
int ti = ty*cols - tx;
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;
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;
Первый шаг
Как подключиться
- Скачать Microsoft Visual Studio Express Edition для c++. Ее можно скачать вот здесь. Для того чтобы удачно сказать надо иметь учетную запись Visual Studio .
- Подключиться к проекту https://github.com/Pavelius/fallout2.
Подписаться на:
Сообщения (Atom)