В этом уроке будет продемонстрирован варварский и веселый способ быстрого получения игрового приложения на примере преобразования урока 30 «Определение столкновений и моделирование законов физики» в урок X4 «Игры с УФО» с помощью уроков 07, 11, 20, 32, 34.
Все действия будут производиться в среде Visual Studio Professional 2010. С небольшими модификациями все эти манипуляции могут быть осуществлены в любой другой версии Visual Studio от 7 и выше. Для человека, имеющего некоторый опыт работы с Visual Studio, не составит труда проделать все это независимо от версии. В среде Visual Studio Professional 2010 можете повторять все этапы, как они здесь описаны.
Этапы преобразования:
1. Преобразование проекта Collision в новый проект UFOgame (урок 30).
2. Замена цилиндрических колонн на ландшафт поверхности (урок 34).
3. Применение сменных текстур к стенкам куба и небу (урок 07).
4. Применение сменных текстур к ландшафту поверхности (урок 11).
5. Придание шарам вида НЛО.
6. Замена фиксированных массивов с шарами на объектные списки.
7. Команды вращения изображения и изменения количества шаров.
8. Выбор НЛО в качестве объекта наблюдения (урок 32).
9. Нанесение текстуры на НЛО (урок 20).
10. Управление ракурсом наблюдения выбранного НЛО.
1. Преобразование проекта Collision в новый проект UFOgame (урок 30).
Вот последовательность действий:
- выбираем в директории LessonsGLCode_2010 урок 30 Lesson30 и делаем рядом его копию (например, LessonXN);
- в этой вновь созданной папке и во всех вложенных в нее во всех названиях меняем словосочетание Collision на UFOgame;
- файлы UFOgame.vcproj, UFOgame.vcxproj, UFOgame.vcxproj.filters, UFOgame.rc, UFOgame.sln открываем с помощью обычного Notepad и везде заменяем словосочетание Collision на UFOgame (команда CTRL + H);
- во всех файлах проекта заменяем словосочетание Collision на UFOgame (команда CTRL + SHIFT + H).
Теперь мы можем запустить отладку (команда «Запуск без отладки»(Start Without Debugging)) и убедиться в том, что в новом проекте UFOgame выполняются все функции исходной программы Collision. То есть мы получили заготовку, в которой все свойства Collision сохраняются, но уже нигде в тексте это название не употребляется, вместо него используется UFOgame. Для полноты картины мы сделаем во всех файлах проекта замену идентификатора _30 на _XN(команда CTRL + SHIFT + H), название файла 30_UFOgameDrawInit.cpp на XN_UFOgameDrawInit.cpp, название файла 30_GLHelp.txt на XN_GLHelp.txt, в файле XN_GLHelp.txt поменяем 30 на XN.Теперь наш новый проект под идентификатором XN будет виден в перечне программ из любой другой программы из директории LessonsGLCode_2010 и может быть из неё запущен, как и все остальные.
Все дальнейшие текстовые преобразования будут производиться в XN_UFOgameDrawInit.cpp .
2. Замена цилиндрических колонн на ландшафт поверхности (урок 34).
Следующее наше действие: заменить цилиндрические колонны на ландшафт поверхности. Убрать колонны очень просто: в файле XN_CollisionDrawInit.cpp в подпрограмме DrawGLScene_XN нужно убрать (или заключить в комментарии) блок, относящийся к render columns (cylinders).
Для изображения ландшафта обратимся к уроку 34 «Построение красивых ландшафтов с помощью карты высот»:
- в меню «Проект->Существующий элемент» присоединим к нашему проекту файл 34_HeightMapDrawInit.cpp;
- в папку Data копируем файл Terrain.raw из урока 34;
- в начале файла вставим объявления из 34_HeightMapDrawInit.cpp:
#define MAP_SIZE 1024 // Size Of Our .RAW Height Map
#define STEP_SIZE 16 // Width And Height Of Each Quad
#define HEIGHT_RATIO 1.5f //Ratio That The Y Is Scaled According To The X And Z
static BYTE g_HeightMap[MAP_SIZE*MAP_SIZE]; // Holds The Height Map Data
static float scaleValue = 0.15f; // Scale Value For The Terrain
extern RECT m_viewRect;
void RenderHeightMap(BYTE pHeightMap[]); // This Renders The Height Map As Quads
void LoadRawFile(LPSTR strName, int nSize, BYTE *pHeightMap);
int Height(BYTE *pHeightMap, int X, int Y); // This Returns The Height From A Height Map Index
void SetVertexColor(BYTE *pHeightMap, int x, int y);// Sets The Color Value For A Particular Index,
// Depending On The Height Index
static bool bRender = TRUE;
Чтобы привести размеры ящика к размерам ландшафта, введем объявление
GLfloat aLen = 512; // len of wall(original 320);
и заменим все значения 320 на aLen(команда CTRL + H);
Вставляем в конец процедуры int InitGL_XN(void) процедуру:
LoadRawFile("Data/Terrain.raw", MAP_SIZE * MAP_SIZE, g_HeightMap); // (Lesson 34)
Для изображения ландшафта вставляем в конец процедуры int DrawGLScene_XN(GLvoid) строки:
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
glTranslatef(-aLen,-aLen,-aLen);
RenderHeightMap(g_HeightMap); // Render The Height Map
glPopMatrix();
Запускаем отладку (команда «Запуск без отладки»(Start Without Debugging)) и убеждаемся в том, что в новом проекте UFOgame появился ландшафт. Но пейзаж бледноват, и мы переходим к следующему этапу.
3. Применение сменных текстур к стенкам куба и небу (урок 07).
Введем объявления:
extern CWordArray m_globTexture; // список всех текстур
extern CDWordArray m_maskTexture; // список текстур, имеющих маски
static CWordArray m_groundTexture; //список текстур ландшафта
static CWordArray m_skyTexture; //список текстур стенок и неба
static GLuint m_idPortHoles; //текстура иллюминатора УФО
static GLuint m_idCrossHair; ; //текстура перекрестия прицела
static GLuint m_idSpark; //текстура взрыва
static GLuint filterF = 0; //текущая текстура ландшафта
static GLuint filterG = 0; //текущая текстура стенок и неба
void LoadGLTexturesImages(CStringArray * sArray); // Создание текстур из изображений в файлах
void RemoveAllTextures(void);
int LoadGLTextureImage(CString fName, BOOL bMask);
GLuint LoadGLTextureImage(CString fName);
COLORREF ColorRandom(int rMin, int rMax);
и процедуру void LoadGLTextures_XN() заменим на следующую:
void LoadGLTextures_XN()
{
CStringArray sArray; //список имен файлов для текстур
sArray.Add("data/boden.jpg");
sArray.Add("data/marble.jpg");
sArray.Add("data/Base.JPG");
sArray.Add("data/Bumps.JPG");
sArray.Add("data/Glass.JPG");
sArray.Add("data/Lights.JPG");
sArray.Add("data/Wand.JPG");
sArray.Add("data/Logo.bmp");
sArray.Add("data/zodiak.jpg");
sArray.Add("data/sky_01.jpg");
sArray.Add("data/sky_02.jpg");
sArray.Add("data/sky_03.jpg");
sArray.Add("data/sky_04.jpg");
sArray.Add("data/sky_05.jpg");
sArray.Add("data/sky_06.jpg");
sArray.Add("data/Logo.bmp");
LoadGLTexturesImages(&sArray);
sArray.RemoveAll();
for( int i = 0; i < 8; i++)
m_groundTexture.Add(m_globTexture.GetAt(i));
for( int i = 0; i < 8; i++)
m_skyTexture.Add(m_globTexture.GetAt(i+8));
m_idPortHoles = LoadGLTextureImage("data/PortHoles_1.bmp", TRUE);
m_idCrossHair = LoadGLTextureImage("data/Crosshair.bmp", TRUE);
m_idSpark = LoadGLTextureImage("data/Spark.bmp");
}
Естественно, все перечисленные в LoadGLTextures_XN файлы должны находиться в директории Data (или вы можете поместить вместо них любые другие файлы изображений, только не забудьте вписать их в текст).
В процедуре DrawGLScene_XN перед отрисовкой стен заменим фильтр установки текстур на следующий:
//render walls(planes) with texture
filterF %= m_skyTexture.GetSize();
glBindTexture(GL_TEXTURE_2D, m_skyTexture.GetAt(filterF));
В процедуре ProcessKeyBoard_XN добавим команду изменения текстур стенок и неба:
else if (strCmnd == "F") filterF++;
Запускаем отладку (команда «Запуск без отладки»(Start Without Debugging)) и убеждаемся в том, что в новом проекте UFOgame на стенках появилось изображение, и оно меняется при нажатии клавиши F. Однако на небе изображения не появилось, и изображения получаются перевернутыми. Все дело в порядке следования текстурных вершин:
//Top Face
glTexCoord2f(0.0f, 0.0f); glVertex3f(-aLen,aLen,-aLen);
glTexCoord2f(1.0f, 0.0f); glVertex3f(aLen,aLen,-aLen);
glTexCoord2f(1.0f, 1.0f); glVertex3f(aLen,aLen,aLen);
glTexCoord2f(0.0f, 1.0f); glVertex3f(-aLen,aLen,aLen);
Можете сами поупражняться поменять их порядок, но для экономии времени просто скопируйте эти блоки из урока X4. Теперь с изображениями на стенках и на небе все должно быть в порядке, а для изображения взрыва заменим выбор текстуры на следующий:
glBindTexture(GL_TEXTURE_2D, m_idSpark);
Запускаем отладку (команда «Запуск без отладки»(Start Without Debugging)) и убеждаемся в том, что в новом проекте UFOgame текстура стенок и неба изменяется, но ландшафт пока еще без текстуры.
4. Применение сменных текстур к ландшафту поверхности (урок 11).
Чтобы применить сменные текстуры к ландшафту, возьмем процедуру RenderHeightMap из урока 34 и преобразуем ее, используя технологии урока 11:
void RenderHeightMap(BYTE pMountMap[], GLuint texid) //Lesson 44 Визуализация карты высоты с помощью квадратов
{
GLuint B_LIGHTING = glIsEnabled(GL_LIGHTING);
if(texid > 0)
glDisable(GL_LIGHTING);
else
glEnable(GL_LIGHTING);
if(texid > 0)
{
glEnable(GL_TEXTURE_2D);
filterG %= m_groundTexture.GetSize();
glBindTexture(GL_TEXTURE_2D, texid);
}
if(!pMountMap) return; // Данные корректны?
if(bRender) // Что хотим визуализировать?
glBegin( GL_QUADS ); // Полигоны
else
glBegin( GL_LINES ); // Линии
for ( int X = 0; X < MAP_SIZE; X += STEP_SIZE )
for (int Y = 0; Y < MAP_SIZE; Y += STEP_SIZE )
{
// Получаем (X, Y, Z) координаты нижней левой вершины
int X1 = X+STEP_SIZE;
int Y1 = Y+STEP_SIZE;
float xx = X/(float)MAP_SIZE;
float yy = Y/(float)MAP_SIZE;
float xx1 = X1/(float)MAP_SIZE;
float yy1 = Y1/(float)MAP_SIZE;
//vertex X,Y1:
int y = Height(pMountMap, X, Y1 );
if(texid <= 0) SetVertexColor(pMountMap, X, Y1);
else glTexCoord2f(xx, yy1);
glVertex3i(X, y, MAP_SIZE - Y1);
//vertex X,Y:
y = Height(pMountMap, X, Y );
if(texid <= 0) SetVertexColor(pMountMap, X, Y);
else glTexCoord2f(xx, yy);
glVertex3i(X, y, MAP_SIZE - Y);
//vertex X1,Y1:
y = Height(pMountMap, X1, Y );
if(texid <= 0) SetVertexColor(pMountMap, X1, Y);
else glTexCoord2f(xx1, yy);
glVertex3i(X1, y, MAP_SIZE - Y);
//vertex X1,Y1:
y = Height(pMountMap, X1, Y1 );
if(texid <= 0) SetVertexColor(pMountMap, X1, Y1);
else glTexCoord2f(xx1, yy1);
glVertex3i(X1, y, MAP_SIZE -Y1);
}
glEnd();
glColor4f(1.0f, 1.0f, 1.0f, 1.0f); // Сбрасываем цвет
B_LIGHTING ? glEnable(GL_LIGHTING): glDisable;
}
В процедуре ProcessKeyBoard_XN добавим команду изменения текстуры ландшафта:
else if (strCmnd == "G") filterG++;
В процедуре DrawGLScene_XN изображение ландшафта:
filterG %= m_groundTexture.GetSize();
RenderHeightMap(g_HeightMap, m_groundTexture[filterG]); // Render The Height Map
Запускаем отладку (команда «Запуск без отладки»(Start Without Debugging)) и убеждаемся в том, что в новом проекте UFOgame текстура ландшафта изменяется . Но пока мы видим только шарики, а не НЛО.
5. Придание шарам вида НЛО
Cделаем теперь наши шарики более похожими на НЛО:
void DrawUFO(void)
{
glPushMatrix();
glScalef(1,.3f,1);
gluSphere(cylinder_obj,20,20,20);
glPopMatrix();
}
Вставим эту процедуру вместо gluSphere(cylinder_obj,20,20,20):
DrawUFO();
Запускаем отладку (команда «Запуск без отладки»(Start Without Debugging)) и убеждаемся в том, что в новом проекте UFOgame шарики уже больше похожи на НЛО.
6. Замена фиксированных массивов с шарами на объектные списки.
Чтобы иметь больше возможностей манипулировать цветом НЛО, введем структуру цвета:
struct myColor
{
GLfloat clr[3];
};
Чтобы иметь возможность изменять количество НЛО, заменим фиксированные массивы на объектные списки:
static CPtrArray ArrayVel; // holds velocity of balls
static CPtrArray ArrayPos; // position of balls
static CPtrArray OldPos; // old position of balls
static CPtrArray m_colorArray; // color of balls
Чтобы иметь возможность прыгать с одного НЛО на другое, введем номер текущей тарелки:
static int m_curObj = 0;
Доступ к информации о каждом НЛО в зависимости от его номера задается процедурами:
myColor * GetMyColor(int i) // цвет НЛО
{
if(i < 0)
return NULL;
if(i >= m_colorArray.GetSize())
return NULL;
return (myColor *) m_colorArray.GetAt(i);
}
TVector * GetVel(int i) //скорость НЛО
{
if(i < 0)
return NULL;
if(i >= ArrayVel.GetSize())
return NULL;
return (TVector *) ArrayVel.GetAt(i);
}
TVector * GetPos(int i) //позиция НЛО
{
if(i < 0)
return NULL;
if(i >= ArrayPos.GetSize())
return NULL;
return (TVector *) ArrayPos.GetAt(i);
}
TVector * GetOldPos(int i) //старая позиция НЛО
{
if(i < 0)
return NULL;
if(i >= OldPos.GetSize())
return NULL;
return (TVector *) OldPos.GetAt(i);
}
Чтобы иметь возможность наблюдать свое НЛО со стороны, введем переменную:
static TVector m_Var(25,25,0); //позиция с которой наблюдатель смотрит на свое НЛО
Чтобы иметь возможность управлять вращением сцены, введем переменную:
static BOOL m_bRotate = FALSE; //флаг вращения сцены
Процедура установки позиции камеры меняется:
void CameraLookAt_XN(void)
{
//set camera in hookmode
if (hook_toball1)
{
TVector unit_followvector=(* GetVel(m_curObj));
unit_followvector.unit();
gluLookAt((* GetPos(m_curObj)).X()+m_Var.X(),(* GetPos(m_curObj)).Y()+m_Var.Y() ,(* GetPos(m_curObj)).Z()+m_Var.Z() ,
(* GetPos(m_curObj)).X()+(* GetVel(m_curObj)).X() ,(* GetPos(m_curObj)).Y()+(* GetVel(m_curObj)).Y() ,
(* GetPos(m_curObj)).Z()+(* GetVel(m_curObj)).Z() ,0,1,0);
}
else
{
gluLookAt(pos.X(),pos.Y(),pos.Z(), pos.X()+dir.X(),pos.Y()+dir.Y(),pos.Z()+dir.Z(), 0,1.0,0.0);
glRotatef(camera_rotation,0,1,0);
}
}
В процедуре DrawGLScene_XN поставим эту команду в самом начале вместо блока //set camera in hookmode.
Инициализация НЛО изменяется:
void AppendUFO(void)
{
myColor * pM = new myColor;
m_colorArray.Add(pM);
COLORREF rgb = ColorRandom(64, 196);
pM->clr[0] = GetRValue(rgb)/255.f;
pM->clr[1] = GetGValue(rgb)/255.f;
pM->clr[2] = GetBValue(rgb)/255.f;
TVector * pV = new TVector;
ArrayPos.Add(pV);
pV = new TVector;
ArrayVel.Add(pV);
pV = new TVector;
OldPos.Add(pV);
}
void InitUFO(void)
{
for(int i = 0; i< NrOfBalls; i++)
AppendUFO();
}
Вместо InitVars :
void InitVars_XN()
{
GLfloat tLen = aLen - 20;
//create palnes
pl1._Position=TVector(0,-tLen+200,0);
pl1._Normal=TVector(0,1,0);
pl2._Position=TVector(tLen,0,0);
pl2._Normal=TVector(-1,0,0);
pl3._Position=TVector(-tLen,0,0);
pl3._Normal=TVector(1,0,0);
pl4._Position=TVector(0,0,tLen);
pl4._Normal=TVector(0,0,-1);
pl5._Position=TVector(0,0,-tLen);
pl5._Normal=TVector(0,0,1);
pl6._Position=TVector(0,tLen,0);
pl6._Normal=TVector(0,-1, 0);
cylinder_obj= gluNewQuadric();
gluQuadricTexture(cylinder_obj, GL_TRUE);
//Set initial positions and velocities of balls
//also initialize array which holds explosions
InitUFO();
ResetUFO();
}
void ResetUFO(void)
{
//Set initial positions and velocities of balls
//also initialize array which holds explosions
ExplosionArray[0]._Alpha=0;
ExplosionArray[0]._Scale=1;
ExplosionArray[1]._Alpha=0;
ExplosionArray[1]._Scale=1;
ExplosionArray[2]._Alpha=0;
ExplosionArray[2]._Scale=1;
GLfloat r = 20;
GLfloat bLen = aLen * 0.9f;
for (int i=0; i<NrOfBalls; i++)
{
GLfloat vv = 5;
(* GetVel(i)) =
TVector(vv *(1.0f - 2.0f *(float)(rand()%100)*0.01f),
vv *(1.0f - 2.0f *(float)(rand()%100)*0.01f),
vv *(1.0f - 2.0f *(float)(rand()%100)*0.01f));
(* GetPos(i))=TVector(-500+i*75, 300, -500+i*50);
ExplosionArray[i]._Alpha=0;
ExplosionArray[i]._Scale=1;
}
for (int i=10; i<20; i++)
{
ExplosionArray[i]._Alpha=0;
ExplosionArray[i]._Scale=1;
}
}
В процедуре DrawGLScene_XN изображение НЛО:
for (int i=0;i<NrOfBalls;i++)
{
myColor * pM = GetMyColor(i);
glPushMatrix();
glTranslated((* GetPos(i)).X(),(* GetPos(i)).Y(),(* GetPos(i)).Z());
glColor3f(pM->clr[0],pM->clr[1],pM->clr[2]);
glEnable(GL_DEPTH_TEST);
glEnable(GL_LIGHTING);
glEnable(GL_AUTO_NORMAL);
glEnable(GL_NORMALIZE);
glDisable(GL_TEXTURE_2D);
DrawUFO();
//ApplyMaskTextureToUFO(LOWORD(m_maskTexture.GetAt(0)),HIWORD(m_maskTexture.GetAt(0)));
glPopMatrix();
}
Процедура FindBallCol_XN тоже соответственно изменяется:
int FindBallCol_XN(TVector& point, double& TimePoint, double Time2, int& BallNr1, int& BallNr2)
{
TVector RelativeV;
TRay rays;
double MyTime=0.0, Add=Time2/150.0, Timedummy=10000, Timedummy2=-1;
TVector posi;
//Test all balls against eachother in 150 small steps
for (int i=0;i<NrOfBalls-1;i++)
{
for (int j=i+1;j<NrOfBalls;j++)
{
RelativeV=(* GetVel(i))-(* GetVel(j));
rays=TRay((* GetOldPos(i)),TVector::unit(RelativeV));
MyTime=0.0;
if ( (rays.dist((* GetOldPos(j)))) > 40) continue;
while (MyTime<Time2)
{
MyTime+=Add;
posi=(* GetOldPos(i))+RelativeV*MyTime;
if (posi.dist((* GetOldPos(j)))<=40)
{
point=posi;
if (Timedummy>(MyTime-Add)) Timedummy=MyTime-Add;
BallNr1=i;
BallNr2=j;
break;
}
}
}
}
if (Timedummy!=10000) { TimePoint=Timedummy;
return 1;
}
return 0;
}
Вместо процедуры idle тоже соответственно будет idle_XN, но она длинная, поэтому скопируйте сразу ее из проекта урок X4 «Игры с УФО», и в ней все изменения связаны только со сменой адресации НЛО.
Запускаем отладку (команда «Запуск без отладки»(Start Without Debugging)) и убеждаемся в том, что в новом проекте UFOgame картинка не сильно изменилась. Но теперь мы можем управлять количеством НЛО и выбирать дно из них.
7. Команды вращения изображения и изменения количества шаров.
Для управления количеством НЛО добавим процедуры:
void IncreaseUFOnum(void)
{
if( NrOfBalls >= 50)
return;
AppendUFO();
GLfloat vv = 5;
(* GetVel(NrOfBalls)) =
TVector(vv *(1.0f - 2.0f *(float)(rand()%100)*0.01f),
vv *(1.0f - 2.0f *(float)(rand()%100)*0.01f),
vv *(1.0f - 2.0f *(float)(rand()%100)*0.01f));
GLfloat bLen = aLen - 20;
GLfloat xx = -bLen + 2*bLen*(rand()%100)*0.01f;
GLfloat yy = -bLen + 200 + (2*bLen - 200)*(rand()%100)*0.01f;
GLfloat zz = -bLen + 2*bLen*(rand()%100)*0.01f;
(* GetPos(NrOfBalls))= TVector(xx, yy,zz);
NrOfBalls++;
}
void DecreaseUFOnum(void)
{
if(ArrayPos.GetSize() <=1)
return;
TVector * pV = (TVector *)ArrayPos.GetAt(0);
ArrayPos.RemoveAt(0);
delete pV;
pV = (TVector *)ArrayVel.GetAt(0);
ArrayVel.RemoveAt(0);
delete pV;
pV = (TVector *)OldPos.GetAt(0);
OldPos.RemoveAt(0);
delete pV;
myColor * pC = (myColor *)m_colorArray.GetAt(0);
m_colorArray.RemoveAt(0);
delete pC;
NrOfBalls --;
if(m_curObj>=NrOfBalls)
m_curObj = 0;
}
Теперь в процедуре ProcessKeyBoard_XN добавим команды:
else if (strCmnd == "A") IncreaseUFOnum();
else if (strCmnd == "D") DecreaseUFOnum();
else if (strCmnd == "B") m_bRotate = !m_bRotate;
Запускаем отладку (команда «Запуск без отладки»(Start Without Debugging)) и убеждаемся в том, что в новом проекте UFOgame вновь введенные команды работают.
Если вы добрались до этого момента, и у вас все получилось, то это означает, что вы уже все поняли из того, что я хотел рассказать в этом уроке. Поэтому, чтобы не терять время на дальнейшее изложение, вы можете просто скопировать участки программы, соответствующие пунктам 8, 9, 10 из проекта урок X4 «Игры с УФО» и у вас получится точная его копия, в которой вы можете навешивать свои собственные эффекты и команды. И, если вы добрались до конца этого урока, то вы уже точно можете это делать.
Код к уроку
LessonsCode_2010.7z (5.09 Mb)
© Владимир Петров
petrov@msun.ru
Моя страница