Создание 3D игр на C++
Часть1. Урок 8
Создаем, рисуем уровень и ходим по нему
Итак, теперь, когда мы прошли основы трехмерной графики, пора начать воплощать саму игру. Начнем мы с того, что научимся создавать уровни. Хранить мы их будем в файлах в следующем формате: до первого нулевого байта идет название карты. Затем два байта - ширина и длина. Потом - высота. Затем - уровень гравитации (ниже - подробней), дальше - номер текстуры с небом. 0 будет голубым, 1 - красным. Дальше номер текстуры с полом. Их всего 3. Потом скорость движения неба по оси Ox, потом по оси Oy. Дальше количество стартовых точек, потом пары байт, затающие эти самые точки (координаты x и y). Потом количество объектов. Объект - это плоская стена (соответственно кубик - это шесть объектов). Небо и пол не надо задавать, их и так нарисуем. Объекты задаются следующим порядком байт: первые три - это координаты левого-верхнего угла. Следующие три - координаты правого нижнего угла. Потом три - веркор нормали, и последний - номер текстуры для объекта (всего три будет их).
Возможно, тут пригодится редактор вроде UltraEdit или HexEdit, в общем, что угодно, лишь бы писать байтами. На крайний день напишите сами на BCB что-нибудь вроде заполнения формы и сохранения в карту (это, наверное, идеальный вариант).
Начнем с прорисовки уровней. Создадим новый модуль, назовите его col_map.
Создадим стркутру для объекта в файле col_map.h,
struct object_t
{
char x1, y1, z1;
char x2, y2, z2;
char xn, yn, zn;
float w, h;
char TexIndex;
};
Структуру для стартовых точек:
struct sp_t {char x, y;};
Сразу же зададим необходимые переменные в col_map.cpp:
//==============================================================================
// Объявление глобальных переменных
AnsiString MapName;
unsigned char MapWidth, MapHeight, MapZ;
char MapSky, MapFloor;
char SkyDX, SkyDY;
char MapGravity;
float SkyX, SkyY;
char SPCount; // StartPointCount
sp_t SPs [MAX_START_POINTS];
char ObjCount;
object_t Obj [MAX_MAP_OBJECTS];
И подгрузим несколько необходимых заголовочных файлов:
#include "col_gl.h"
#include "col_main.h"
В col_map.h объявим прототипы функций и объявим переменные как внешие:
bool LoadMap (AnsiString fName);
void DrawMap ();
extern AnsiString MapName;
extern unsigned char MapWidth, MapHeight, MapZ;
extern char MapGravity;
extern char MapSky, MapFloor;
extern char SkyDX, SkyDY;
extern char SPCount; // StartPointCount
extern sp_t SPs [MAX_START_POINTS];
extern char ObjCount;
extern object_t Obj [MAX_MAP_OBJECTS];
MapZ - это высота. MapWidth и MapHeight - длина и ширина.
MapGravity - гравитация.
MapSky и MapFloor - текстуры для пола и неба.
SkeDX, SkyDY - скорость неба.
SPCount - количество стартовых точек.
ObjCount - количество объектов.
SPs и Obj - стартовые точки и объекты.
Теперь займемся самим модулем. Перво-неперво - загрузка карт:
//==============================================================================
// *** LoadMap ***
// Загрузка карты из файла
// Возвращает
// true - если все прошло гладко
// false - в обратном случае
//==============================================================================
bool LoadMap (AnsiString FileName)
{
AddString ("Loading " + FileName + "...", true);
FILE *f;
if ((f = fopen (FileName.c_str(), "r")) == NULL)
{
AddString ("Cannot load map " + FileName, true);
return false;
}
// Reading Map Name
char a = 'z';
MapName = "";
while (a != '\0')
{
fread (&a, 1, 1, f);
if (a != 0) MapName += a;
}
CurrStr = 0;
AddString (EXT_SEPARATOR, false);
AddString (MapName, false);
fread (&MapWidth, 1, 1, f);
fread (&MapHeight, 1, 1, f);
fread (&MapZ, 1, 1, f);
fread (&MapGravity, 1, 1, f);
fread (&MapSky, 1, 1, f);
fread (&MapFloor, 1, 1, f);
fread (&SkyDX, 1, 1, f);
fread (&SkyDY, 1, 1, f);
SkyX = 0; SkyY = 0;
fread (&SPCount, 1, 1, f);
for (int i = 0; i < SPCount; i++)
{
fread (&SPs[i].x, 1, 1, f);
fread (&SPs[i].y, 1, 1, f);
}
float n = 0;
fread (&ObjCount, 1, 1, f);
for (int i = 0; i < ObjCount; i++)
{
fread (&Obj[i].x1, 1, 1, f);
fread (&Obj[i].y1, 1, 1, f);
fread (&Obj[i].z1, 1, 1, f);
fread (&Obj[i].x2, 1, 1, f);
fread (&Obj[i].y2, 1, 1, f);
fread (&Obj[i].z2, 1, 1, f);
fread (&Obj[i].xn, 1, 1, f);
fread (&Obj[i].yn, 1, 1, f);
fread (&Obj[i].zn, 1, 1, f);
fread (&Obj[i].TexIndex, 1, 1, f);
Obj[i].w = sqrt ((Obj[i].x2 - Obj[i].x1) * (Obj[i].x2 - Obj[i].x1) + (Obj[i].y2 - Obj[i].y1) * (Obj[i].y2 - Obj[i].y1));
Obj[i].h = abs (Obj[i].z2 - Obj[i].z1);
// Normals' normalizing
n = sqrt (Obj[i].xn * Obj[i].xn + Obj[i].yn * Obj[i].yn);
Obj[i].xn = Obj[i].xn / n;
Obj[i].yn = Obj[i].yn / n;
}
fclose (f);
AddString (EXT_SEPARATOR, true);
return true;
}
Рисовать карту - не так сложно в нашем проекте. Будем просто выводить все объекты (повторю: объекты в нашем понимании - это плоские четырехугольники) с помощью GL_QUADS. Отдельно будем рисовать небо и пол. Все просто делается, кроме неба. С небом проявим капельку оригинальности: сделаем его "плывущим". Для этого просто воспользуемся уже известной вам glTexCoords. При первой прорисовке координаты текстуры неба будут такими, чтобы текстура ровно-ровно вписалась в отведенное место (то есть как раз заполнила прямоугольник). Т.е координаты в точках прямоугольника будут:
0.0, 0.0
0.0, 1.0
1.0, 1.0
1.0, 0.0
При следующей прорисовке сместим координаты:
0.1, 0.1
0.1, 1.1
1.1, 1.1
1.1, 0.1
Тогда небо "сдвинется" на одну десятую от своего размера. Это образно, конечно. На сколько сдвигать от много зависит. В первую очередь от того, сколько времени прошло между прорисовками. Но за это будет отвечать другой модуль. Тут только прорисовка:
//==============================================================================
// *** DrawMap ***
// Прорисовка карты
// Используется самый обычный метод - вывод всех объектов на экран
//==============================================================================
void DrawMap ()
{
SkyX += float (SkyDX) / 100.0f * SPF;
SkyY += float (SkyDY) / 100.0f * SPF;
glClearColor (0, 0, 0, 1);
glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
/*glPopMatrix ();
glPushMatrix ();*/
glLoadIdentity ();
gluLookAt (0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0);
glRotatef (-_Players[CurrPlr].Pitch, 1, 0, 0);
glRotatef (_Players[CurrPlr].Yaw, 0, 0, 1);
glTranslatef (-_Players[CurrPlr].X, -_Players[CurrPlr].Y, -_Players[CurrPlr].Z-1.5f);
for (int i = 0; i < ObjCount; i++)
{
glBindTexture(GL_TEXTURE_2D, textures[Obj[i].TexIndex]);
glBegin (GL_QUADS);
{
glColor3f (1, 1, 1);
glNormal3f ((float)Obj[i].xn, (float)Obj[i].yn, (float)Obj[i].zn);
glTexCoord2f(0.0f, 0.0f);
glVertex3f ((float)Obj[i].x1, (float)Obj[i].y1, (float)Obj[i].z1);
glTexCoord2f((float)Obj[i].w, 0.0f);
glVertex3f ((float)Obj[i].x2, (float)Obj[i].y2, (float)Obj[i].z1);
glTexCoord2f((float)Obj[i].w, (float)Obj[i].h);
glVertex3f ((float)Obj[i].x2, (float)Obj[i].y2, (float)Obj[i].z2);
glTexCoord2f(0.0f, (float)Obj[i].h);
glVertex3f ((float)Obj[i].x1, (float)Obj[i].y1, (float)Obj[i].z2);
}
glEnd ();
}
// Пол
glBindTexture(GL_TEXTURE_2D, textures[MapFloor + 3]);
glBegin (GL_QUADS);
{
glNormal3f (0, 1, 0);
glTexCoord2f(0.0f, 0.0f);
glVertex3f (-MapWidth, MapHeight, 0);
glTexCoord2f(MapWidth*2.0f, 0.0f);
glVertex3f (MapWidth, MapHeight, 0);
glTexCoord2f(MapWidth*2.0f, MapHeight*2.0f);
glVertex3f (MapWidth, -MapHeight, 0);
glTexCoord2f(0.0f, MapHeight*2.0f);
glVertex3f (-MapWidth, -MapHeight, 0);
}
glEnd ();
// Небо
glBindTexture(GL_TEXTURE_2D, textures[MapSky + 6]);
glBegin (GL_QUADS);
{
glNormal3f (0, 1, 0);
glTexCoord2f(SkyX, SkyY);
glVertex3f (-MapWidth, MapHeight, MapZ);
glTexCoord2f(1.0f+SkyX, SkyY);
glVertex3f (MapWidth, MapHeight, MapZ);
glTexCoord2f(1.0f+SkyX, 1.0f+SkyY);
glVertex3f (MapWidth, -MapHeight, MapZ);
glTexCoord2f(SkyX, 1.0f+SkyY);
glVertex3f (-MapWidth, -MapHeight, MapZ);
}
glEnd ();
glFlush ();
SwapBuffers(hDC);
}
Про текстуры пара слов. Все текстуры хранятся в двух файлах: в одном стены и пол, в другом - небо. Вот они:
Текстуры
Запишем мы их в массив текстур. Первые три - стены. Потом три - полы, и последние две - небеса. Итого восемь.
Объевим в файле col_gl.cpp:
GLuint textures [8];
Объявим в файле col_gl.h:
extern GLuint textures [];
В модуле col_gl.cpp напишем две процедуры. Первая - InitOneTex - будет загружать одну текстуру. Так как все текстуры у нас в одном файле, придется передавать функции еще и координаты текстуры. А так как стены имеют размер 64*64, а небо - 128*128, то придется еще и высоту с шириной. Функция чем-то будет походить на то, как мы зыгружали текстуру на прошлом уроке:
//==============================================================================
// Загрузка одной текстуры
// На входе - номер текстуры и ее координаты на текущем содержимом bitmap
//==============================================================================
void InitOneTex (int index, int x, int y, int w, int h)
{
GLubyte bits[128*128][4];
for(int i = 0; i < w; i++)
{
for(int j = 0; j < h; j++)
{
bits[h*j+i][0]= (GLbyte)GetRValue(bitmap->Canvas->Pixels[i+x][j+y]);
bits[h*j+i][1]= (GLbyte)GetGValue(bitmap->Canvas->Pixels[i+x][j+y]);
bits[h*j+i][2]= (GLbyte)GetBValue(bitmap->Canvas->Pixels[i+x][j+y]);
bits[h*j+i][3]= (GLbyte)255;
}
}
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
glGenTextures(1, &textures[index]);
glBindTexture(GL_TEXTURE_2D, textures[index]);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, bits);
}
Теперь напишем процедурку, которая загрузит все текстуры:
//==============================================================================
// Загрузка всех текстур
//==============================================================================
void InitTex ()
{
bitmap = new Graphics::TBitmap;
bitmap->LoadFromFile (fDir + "Data\\textures\\map.bmp");
InitOneTex (0, 0, 0, 64, 64);
InitOneTex (1, 64, 0, 64, 64);
InitOneTex (2, 128, 0, 64, 64);
InitOneTex (3, 0, 64, 64, 64);
InitOneTex (4, 64, 64, 64, 64);
InitOneTex (5, 128, 64, 64, 64);
bitmap->LoadFromFile (fDir + "Data\\textures\\sky.bmp");
InitOneTex (6, 0, 0, 128, 128);
InitOneTex (7, 128, 0, 128, 128);
glEnable(GL_TEXTURE_2D);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
}
Как видно, картинки с текстурами надо хранить в паке (ИГРА)\Data\Textures
Вот теперь осталось вызвать функцию InitTex в extglInit:
if(wglMakeCurrent(hDC, hGLRC) == false)
{
AddString ("OpenGL Error: Cannot MakeCurrent", true);
return false;
}
// Две новые строки:
AddString (" Loading Textures...", true);
InitTex ();
AddString (" Configuring OpenGL...", true);
glEnable (GL_ALPHA_TEST);
И все, с текстурами разобрались. Карту тоже рисовать научились. Пора взяться за игру.
Для работы с игроками создадим еще один модуль. Сегодня мы учтем только нашего текущего игрока, о врагах поговорим позже. Модуль назовем col_plr
В файле col_plr.cpp пока просто объявим пару перемнных:
unit_t _Players [MAX_PLR_COUNT];
int CurrPlr;
int PlrCount;
В файл col_plr.h объявим класс игрока, да переменные как внешние объявим.
const MAX_PLR_COUNT = 16;
//==============================================================================
struct unit_t
{
bool Left, Right, Forward, Back;
float X, Y, Z;
float Pitch, Yaw; // Угла просмотра
float DX, DY, DZ; // Летим...
float DPitch, DYaw;
float Health, Armor;
bool AI;
};
extern unit_t _Players [MAX_PLR_COUNT];
extern int CurrPlr;
extern int PlrCount;
Теперь перейдем к модулю col_main.h. Нам надо загрузить карту какую-нибудь при загрузке. Вот одна из таких:
isengard.zip
Ее и загрузим при запуске. Карту надо расположить в папку (GAME)\Data\Maps
Кроме того, уставновим начальные параметры нашего игрока.
Вот что получилось:
// Обновим консоль
frmCon->Refresh();
if (!CanRun)
{// Если были ошибки
frmCon->Hide(); // Скроем окно консоля
frmCon->ShowModal(); // И покажем его вновь, но модально!
Application->Terminate();
}
else
{
LoadMap (fDir + "Data\\Maps\\Isengard.map");
CurrPlr = 0;
PlrCount = 1;
_Players[0].X = SPs[0].x;
_Players[0].Y = SPs[0].y;
_Players[0].Z = 0;
_Players[0].Pitch = 0;
_Players[0].Yaw = 0;
_Players[0].DX = 0;
_Players[0].DY = 0;
_Players[0].DZ = 0;
_Players[0].Health = 100;
_Players[0].Armor = 0;
_Players[0].Left = false;
_Players[0].Right = false;
_Players[0].Forward = false;
_Players[0].Back = false;
}
::SetCursorPos (400, 300);
Этими строками кончается конструктор формы frmMain.
Ну и главное: зададим функцией Idle у нашего приложения AppIdle, которую сейчас и напишем:
Application->OnIdle = AppIdle;
Событие OnIdle происходит каждый раз, как только освобождаются ресурсы процессора.
Сначала приведу код, а потом разберу:
//==============================================================================
// Основной цикл игры.
void __fastcall TfrmMain::AppIdle(TObject *Sender, bool &temp)
{
temp = false;
SPF = GameTimer.GetDelta(1);
// Мышку обрабатываем
::GetCursorPos (MPos);
int MDX = MPos->x - 400;
int MDY = MPos->y - 300;
::SetCursorPos (400, 300);
// Учтем движение мыши
_Players[CurrPlr].DYaw = (float)MDX*(float)_SEN*3;
_Players[CurrPlr].DPitch = -(float)MDY*(float)_SEN*3;
// Цикл по игрокам
for (int i = 0; i < PlrCount; i++)
{
_Players[i].Yaw += _Players[i].DYaw * SPF;
_Players[i].Pitch += _Players[i].DPitch * SPF;
float NDX = 0, NDY = 0, NewX, NewY;
float FSIN = sin (_Players[i].Yaw*M_PI/180.0f);
float FCOS = cos (_Players[i].Yaw*M_PI/180.0f);
// Какие должны быть приросты по Ox и Oy
if (_Players[i].Left) {NDX -= 5.0f * FCOS * CL_SIDESPEED / 250.0; NDY += 5.0f * FSIN * CL_SIDESPEED / 250.0; }
if (_Players[i].Right) {NDX += 5.0f * FCOS * CL_SIDESPEED / 250.0; NDY -= 5.0f * FSIN * CL_SIDESPEED / 250.0; }
if (_Players[i].Forward) {NDX += 5.0f * FSIN * CL_FORWARDSPEED / 250.0; NDY += 5.0f * FCOS * CL_FORWARDSPEED / 250.0; }
if (_Players[i].Back) {NDX -= 5.0f * FSIN * CL_FORWARDSPEED / 250.0; NDY -= 5.0f * FCOS * CL_FORWARDSPEED / 250.0; }
// Сравним с текущими и иземним
if (_Players[i].DX != NDX) _Players[i].DX = float (_Players[i].DX*(17.0 + 20.0f*(float)_Players[i].Z) + NDX) / (18.0f + 20.0f*(float)_Players[i].Z);
if (_Players[i].DY != NDY) _Players[i].DY = float (_Players[i].DY*(17.0 + 20.0f*(float)_Players[i].Z) + NDY) / (18.0f + 20.0f*(float)_Players[i].Z);
NewX = _Players[i].X + _Players[i].DX * SPF;
NewY = _Players[i].Y + _Players[i].DY * SPF;
// for (int j = 0; j < ObjCount; j++)
// RecalcPoint (_Players[i].X, NewX, Obj[j].x1, Obj[j].x2, _Players[i].Y, NewY, Obj[j].y1, Obj[j].y2, _Players[i].Z, _Players[i].Z, Obj[j].z1, Obj[j].z2, Obj[j].xn, Obj[j].yn, Obj[j].zn, NewX, NewY, 2);
_Players[i].X = NewX;
_Players[i].Y = NewY;
while (_Players[i].Yaw > 360) _Players[i].Yaw -= 360;
while (_Players[i].Yaw < 0) _Players[i].Yaw += 360;
if (_Players[i].Pitch > 90) _Players[i].Pitch = 90;
if (_Players[i].Pitch < -90) _Players[i].Pitch = -90;
// Рассчет силы по OY
_Players[i].Z += _Players[i].DZ * SPF;
if (_Players[i].Z < 0) {_Players[i].Z = 0; _Players[i].DZ = 0;}
else _Players[i].DZ -= (float)_Players[i].Z * SPF * (float)MapGravity;
}
DrawMap ();
}
Как видите, мы узнали, чему равен SPF (обратная FPS переменная - секунд в одном кадре. Почти всегда меньше единицы). Потом обработали мышь, как делали это прежде. Переменная _SEN - это чувствительность мыши. По-моему, мы ее не объявляли. В конце урока со всем этим разберемся.
Затем мы проходимся по всем игркам. Ну, у нас он один пока. Сначала прирастим углы Yaw и Pitch. У нашего игрока DYaw и DPitch вычисляются двумя строками раньше. Дальше мы двигаем игрока. Эти формулы я подбирал методом математического тыка, то есть просто смотрел, как будет красивее. Изменяя различные числа, можно подоганть их под себя. Переменные Left, Right, Forward и Back будут задаваться у нас при опросе клавиатуры. Если хоть одна из них задана, то попросту зададим приросты по X и Y. Перемнные DX и DY у игрока трогать не будем, так как если мы идем вперед, а потом нажали кнопку назад, то это далеко не значит, что игрок должен сразу ринуться назад. А потому дальше мы рассчитываем изменение настоящих приростов. Оно зависит от выстоы нашего игрока. Чем мы выше, тем сложнее будет изменить направление.
Дальше закомментированные строки пока пропустите. Они нам пригодятся на следующем уроке.
В конце проверяем, не вылезли углы за пределы и...
Дальше идет рассчет падения. Формулу падения, думаю, знают все. Скорость с каждой секундой увеличивается в G раз. G на нашей планете равно 9,81. На уровнях же задается переменной MapGravity, о которой я говорил в начале. На карте Изенгард, которую вы можете скачать, гравитация намного меньше.
В конце функции вызываем обычную прорисовку.
Дело за малым: опросить клавиатуру.
Как вы понмите, в col_input мы задавали команды по умолчанию:
// Зададим самые стандартные клавиши!!!
_Keys [192].Action = "console";
_Keys [ 27].Action = "exit";
_Keys [112].Action = "help; console";
_Keys [ 37].Action = "+moveleft";
_Keys [ 38].Action = "+forward";
_Keys [ 39].Action = "+moveright";
_Keys [ 40].Action = "+back";
_Keys [ 71].Action = "+moveleft";
_Keys [ 89].Action = "+forward";
_Keys [ 74].Action = "+moveright";
_Keys [ 72].Action = "+back";
_Keys [ 32].Action = "jump";
Вот их-то и обработаем в консоли. А еще добавим обработку пары новых команд.
Перво наперво напишем процедурку
//==============================================================================
// *** SetVal ***
// Устанавливает значение или показывает его значение
//==============================================================================
void __fastcall SetVal (AnsiString fName, AnsiString fVal, int MIN, int MAX, float &fVar)
{
if (fVal == "" || !IsNumber (fVal)) AddString (fName + "=" + IntToStr (int (fVar)), true);
else
{
int iVal = StrToInt(fVal);
if (iVal < MIN || iVal > MAX) AddString (AnsiUpperCase (fName) + " Must be between [" + IntToStr (MIN) + "; " + IntToStr (MAX) + "]", true);
else fVar = float (iVal);
}
}
Ее надо поместить ДО функций TypeString1 и TypeString. Для тех, кто не понял, что она делает, поясню: ей мы передаем название перемнной, какой ее должен знать пользователь (fName), значение, которое ей нужно присвоить (fVal), в каких рамках должно быть это значение (MIN и MAX) и какую переменную надо изменить (fVar). Если переменная fVal пуста, или не является числом, то напишем, чему переменная fVar равна сейчас. В противном же случае сравним, входит ли значение в диапазон. Если входит, то изменим. Иначе выдадим ошибку.
Теперь посмотрите на новую функцию TypeString1. Команд нынче обрабатывается куда как побольше!
//==============================================================================
// *** TypeString1 ***
// Конечная обработка строки!!! Принимает одну команду и обрабатывает ее!!!
//==============================================================================
void __fastcall TypeString1 (AnsiString S)
{
if (S == ";") return;
S = S + " ";
int _Start;
AnsiString _Values[5] = {"", "", "", "", ""};
int i = 1; int j = 0;
// Читаем команду
while (j < 5 && i < S.Length())
{
while (S[i] == ' ' && i < S.Length()) i++;
if (S[i] != ' ')
{
_Start = i;
while (S[i] != ' ' && i < S.Length()) i++;
if (S[i] != ' ') i++;
_Values[j] = S.SubString(_Start, i-_Start);
}
j++;
}
// И обрабатываем ее
if (AnsiLowerCase(_Values[0]) == "exit" || AnsiLowerCase(_Values[0]) == "quit")
Application->Terminate ();
else if (AnsiLowerCase(_Values[0]) == "console")
frmCon->Show ();
else if (AnsiLowerCase(_Values[0]) == "about")
{
CurrStr = 0;
AddString (EXT_SEPARATOR, false);
AddString (" About Coliseum 3D", false);
AddString ("", false);
AddString (" Programming: Skidanov \"SHD-ALakazam\" Alexandr;", false);
AddString (" Music: SHD-ALakazam;", false);
AddString (" Sound: Taken from different games;", false);
AddString (" Art: Many thanks to Merkushev Egor;", false);
AddString (" DXLibrary: Nuke_DX.lib;", false);
AddString (" GLLibrary: Standard opengl32.dll;", false);
AddString ("", false);
AddString (" mailto: shd@bk.ru;", false);
AddString (" homesite: http://morgeyz.narod.ru/", false);
AddString ("", false);
AddString (" End.", false);
AddString (EXT_SEPARATOR, true);
}
else if (AnsiLowerCase(_Values[0]) == "help")
{
CurrStr = 0;
AddString (EXT_SEPARATOR, false);
AddString (" Coliseum 3D commands:", false);
AddString ("", false);
AddString (" Console - show console;", false);
AddString (" About - show informations about us;", false);
AddString (" Help - show help;", false);
AddString (" Exec [filename] - load commands from file;", false);
AddString (" Enemies [value] - set count of enemies;", false);
AddString (" Fov [value] - set field of view;", false);
AddString (" Sensitivity [value] - set sensitivity;", false);
AddString (" cl_forwardspeed [value] - set run speed;", false);
AddString (" cl_sidespeed [value] - set strafe speed;", false);
AddString (" Bind [key] [command] - assign command to key;", false);
AddString (" Save [filename] - save game;", false);
AddString (" Load [filename] - load game;", false);
AddString (" New - new game;", false);
AddString (" Exit (or Quit) - exit from game;", false);
AddString ("", false);
AddString (" End.", false);
AddString (EXT_SEPARATOR, true);
}
else if (AnsiLowerCase(_Values[0]) == "fov")
{
if (_Values[1] == "" || !IsNumber (_Values[1])) AddString ("FOV=" + IntToStr (_FOV), true);
else if (StrToInt (_Values[1]) <= MAX_FOV && StrToInt (_Values[1]) >= MIN_FOV)
{
_FOV = StrToInt (_Values[1]);
glMatrixMode (GL_PROJECTION);
glLoadIdentity ();
gluPerspective (_FOV, GLdouble(4.0/3.0), 1.0, 512.0);
glMatrixMode (GL_MODELVIEW);
glLoadIdentity ();
gluLookAt (0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0);
}
else AddString ("Must be between [" + IntToStr (MIN_FOV) + "; " + IntToStr (MAX_FOV) + "]", true);
}
else if (AnsiLowerCase(_Values[0]) == "cl_forwardspeed") SetVal ("cl_forwardspeed", _Values[1], MIN_SPD, MAX_SPD, CL_FORWARDSPEED);
else if (AnsiLowerCase(_Values[0]) == "cl_sidespeed") SetVal ("cl_sidespeed", _Values[1], MIN_SPD, MAX_SPD, CL_SIDESPEED);
else if (AnsiLowerCase(_Values[0]) == "sensitivity") SetVal ("sensitivity", _Values[1], MIN_SEN, MAX_SEN, _SEN);
// Движение и вращение
else if (AnsiLowerCase(_Values[0]) == "+moveleft") _Players [CurrPlr].Left = true;
else if (AnsiLowerCase(_Values[0]) == "+moveright") _Players [CurrPlr].Right = true;
else if (AnsiLowerCase(_Values[0]) == "+forward") _Players [CurrPlr].Forward = true;
else if (AnsiLowerCase(_Values[0]) == "+back") _Players [CurrPlr].Back = true;
else if (AnsiLowerCase(_Values[0]) == "+up") _Players [CurrPlr].DPitch = +5;
else if (AnsiLowerCase(_Values[0]) == "+down") _Players [CurrPlr].DPitch = -5;
else if (AnsiLowerCase(_Values[0]) == "+right") _Players [CurrPlr].DYaw = 5;
else if (AnsiLowerCase(_Values[0]) == "+left") _Players [CurrPlr].DYaw = -5;
else if (AnsiLowerCase(_Values[0]) == "-moveleft") _Players [CurrPlr].Left = false;
else if (AnsiLowerCase(_Values[0]) == "-moveright") _Players [CurrPlr].Right = false;
else if (AnsiLowerCase(_Values[0]) == "-forward") _Players [CurrPlr].Forward = false;
else if (AnsiLowerCase(_Values[0]) == "-back") _Players [CurrPlr].Back = false;
else if (AnsiLowerCase(_Values[0]) == "-up" || AnsiLowerCase(_Values[0]) == "-down") _Players [CurrPlr].DPitch = 0;
else if (AnsiLowerCase(_Values[0]) == "-left" || AnsiLowerCase(_Values[0]) == "-right") _Players [CurrPlr].DYaw = 0;
else if (AnsiLowerCase(_Values[0]) == "jump") _Players [CurrPlr].DZ = 5;
else if (AnsiLowerCase(_Values[0]) == "bind")
{
if (_Values[2] != "")
BindKey (_Values [1], _Values [2] + " " + _Values [3] + " " + _Values [4]);
else if (_Values[1] != "")
{
int _TEMP = GetKey (_Values[1]);
if (_TEMP == -1) AddString ("Unknown key " + _Values [1], true);
else
AddString (_Keys[_TEMP].Name + "=" + _Keys[_TEMP].Action, true);
}
else
AddString ("BIND [KEY] [ACTION]", true);
}
else if (frmCon->Visible)
AddString ("Unknown command " + _Values[0], true);
}
Тут осталась небольшая недоработка. При нажатии кнопки прыжка в воздухе игрок прыгает снова, что позволяет улетать далеко вверх. Эту оплошность мы исправим на следующем уроке.
Теперь можете задавать чувствительность мыши, а так же скорости передвижения игрока командмами
Sensitivity
cl_forwardspeed
cl_sidespeed
Можете исправить тот текст, который выводится при команде About. Но имейте совесть, не забудте написать и о моем участии :о)
Скорее всего, многие переменные из тех, что здесь встречаются, мы еще не задали. К сожалению, я плохо помню содержимое предыдущих уроков. Вот какие переменные объявлены у меня в col_main:
//==============================================================================
float CL_FORWARDSPEED = 300;
float CL_SIDESPEED = 480;
//==============================================================================
TfrmMain *frmMain;
tagPoint *MPos;
AnsiString fDir;
float SPF; // Seconds per Frame
float _SEN = DEF_SEN; // Чувствительность мыши;
Все они, если я не ошибаюсь, при этом объявлены внешними в файле col_main.h.
Еще в модуле col_main.h надо объявить вот эти константы:
const MIN_SEN = 1;
const MAX_SEN = 25;
const MIN_SPD = 1;
const MAX_SPD = 1000;
const DEF_SEN = 7;
Сегодня я попытался сделать первый шаг в сторону игры, и мог многое пропустить. Потому всем советую в случае ошибок посмореть на этот вот архив. Там все исходники в том виде, в каком они должны быть на текущий момент.
issue8.zip
Не обращайте внимания на строки о том, что файлы нельзя использовать без резрешения автора. Автор я и я разрешаю.
В архиве есть небольшое отличие от уроков: файлы начинаются не на ext а на col.