Добро
пожаловать! Это первая часть учебника по Genesis3D, но она же и самая важная!
В этой части мы инициализируем движок Genesis3D, загрузим уровень, который я
создал специально для этого туториала, создадим виртуального "игрока",
а также зададим ему возможность вращать головой.
На скриншоте показано то, что вы увидите после запуска программы.
Прежде чем начать
Прежде чем начать писать программу, вам надо:
Вот вроде бы и все приготовления. Да, кстати! Если при компиляции у вас возникнут непонятные проблемы конфликтов с другими библиотеками, воспользуйтесь моей конфигурацией:
genesis.lib
kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib
shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo
/subsystem:windows /incremental:yes /pdb:"Debug/gentut1.pdb" /debug
/machine:I386 /nodefaultlib:"LIBCMT" /out:"Debug/gentut1.exe"
/pdbtype:sept /libpath:"lib"
Это конфигурация для Debug'a и естесственно нельзя просто устанавливать ее для всех ваших проектов Genesis3D, но у меня были подобные проблемы с Linking... и эта конфигурация избавляла от них.
Начнем писать программу
Писать программу мы будем на Visual C++ (у меня 6-я версия на данный момент). Я честно говоря не знаю какие проблемы могут возникнуть у других компиляторов. Вообще-то Wild Tangent делали движок именно под VC++
Я решил оформить программу в виде классов. Вы можете придумать для себя другую структуру - пожалуйста! Но во-первых, использование классов делает структуру приложения Genesis более видимой, а во-вторых, мне так больше нравится :)
Главным классом в нашей программе будет класс GenApp В нем будут находиться функции для инициализации и завершения работы приложения (инициализация движка, загрузка уровней и т.п.)
Создадим файлы App.h и App.cpp специально для этого класса. В заголовочном файле будут определения, а в файле кода собственно сами функции.
Сначала пишем файл App.h:
//*************************************************
//File: App.h
//Autor: Antiloop
//Desc: Файл объявлений главных объектов
//*************************************************//Includes
#include <windows.h>
#include <genesis.h>
#include "camera.h"
#include "player.h"
Вы можете видеть здесь два непонятных инклудных файла: camera.h и player.h - это будут еще два класса для камеры и игрока, но мы разберем их попозже, поэтому пока не отвлекайтесь на них
//Definitions
#define APPNAME "Genesis3D Tutorial"
#define APPVERSION 1.01f
Ну тут все ясно
//Tools
#define ATTEMPT(x,y,z) { if (!x) { MessageBox(NULL,z,y,MB_OK); return Cleanup(); } }
#define TRY(x,y,z) { if (!x) { MessageBox(NULL,z,y,MB_OK); return false; } }
#define MSG(x) { MessageBox(NULL,x,"Message",MB_OK); }
#define SAFE_DELETE(x) { if (x) delete(x); x = NULL; }
Здесь я определил простые макросы для упрощения дальнейшей работы. ATTEMPT - при неудаче происходит завершение программы; TRY - при неудаче просто выдается сообщение; MSG - выдать сообщение и SAFE_DELETE - удаление существующего объекта.
//Некоторые объявления
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
BOOL Cleanup(); // Убить все
VOID MainLoop(); // Главный цикл
Тут сделаны объявления функций, которые могут понадобится нам во всех частях программы: WndProc - сами знаете что; Cleanup - уничтожает все объекты перед завершением работы; MainLoop - это главный цикл программы. К слову сказать, все эти функции будут находиться в отдельном файле.
Начнем писать наш класс:
//Это объявления нашего главного класса
class GenApp
{
public:
HINSTANCE m_hInstance; // application handle
int m_nCmdShow; // application show command
HWND m_hWnd; // main window handle
POINT m_Mouse; // позиция мыши
int m_Width; // ширина экрана
int m_Height; // высота экрана
int m_FullScreen; // полноэкранное/оконное
geFloat m_MouseSensitivity; // мультипликатор
char m_OurDriver; // выбранный драйвер
Это переменные, которые характеризуют общие свойства приложения.
geEngine *Engine; // GENESIS engine structure
geDriver_System *DrvSys; // GENESIS video driver system structure
geDriver *Driver; // GENESIS video driver structure
geDriver_Mode *Mode; // GENESIS video mode structure
geWorld *World; // GENESIS World Structure
Это уже системные объекты Genesis3D.
А дальше идут объявления еще двух классов - игрока и камеры. Мы будем разбирать
их попозже.
GenCamera *Camera; // класс камеры
GenPlayer *m_Player; // класс игрокаGenApp(); // конструктор
virtual ~GenApp(); // деструктор// Простые функции для удобства чтения
HWND GetHwnd() { return m_hWnd; };
POINT GetMouse() { return m_Mouse; };
int GetWidth() { return m_Width; };
int GetHeight() { return m_Height; };
BOOL GetFullScreen() { return m_FullScreen; };
geFloat GetMouseSensitivity() { return m_MouseSensitivity; };
geEngine* GetEngine() { return Engine; };
geWorld* GetWorld() { return World; };
GenCamera* GetCamera() { return Camera; };
GenPlayer* GetPlayer() { return m_Player; };VOID SetMouseSensitivity(geFloat value) { m_MouseSensitivity = value; };
VOID SetMousePos(int x, int y) { m_Mouse.x = x; m_Mouse.y = y; };
Эти функции служат скорее для украшения
А вот ниже идут уже функции самого класса
//Еще немножко функций
BOOL Cleanup(); //Уничтожение класса
BOOL InitApp(HINSTANCE hinstance, int ncmdshow); // создать и показать окно
BOOL InitGenesis(); // инициализировать GENESIS engine
VOID LoadPrefs(); // загрузить установки
BOOL LoadDriver(); // загрузить драйвер
BOOL LoadLevel(char* filename); // загрузить уровень
VOID ScreenShot(geEngine *Engine);
BOOL RenderWorld(); // отрендерить кадр};
// Объект класса GenApp
extern GenApp *App;
Ну вот, с объявлениями покончено. Теперь мы будет заниматься главным - писать
тело класса.
Переходим в файл App.cpp
//*************************************************
//File: App.cpp
//Autor: Antiloop
//Desc: Гланый класс нашей программы - GenApp
//*************************************************#include "App.h"
Для начала пара коструктор-деструктор. Конструктор - это инициализация класса, деструктор - его уничтожение, то-есть при выходе из программы.
//Конструктор - инициализация
GenApp::GenApp()
{
//Установки по умолчанию
m_Width = 640; // ширина экрана
m_Height = 480; // высота экрана
m_FullScreen = 1; // полноэкранный режим
m_OurDriver = 'G'; // драйвер Glide
m_MouseSensitivity = 0.002f; // устанавливаем сенс мыши//Теперь загружаем установки из файла
LoadPrefs();
//Все системные объекты устанавливаем в NULL
DrvSys = NULL;
Driver = NULL;
Mode = NULL;
World = NULL;
Camera = NULL;
m_Player = NULL;
}
Как видите, наше приложение будет работать в режиме 640x480 под Glide (это
потому, что у меня Voodoo Graphics). Драйвер задается по первой букве в нашем
случае - G Также вы можете задать S (Soft) и ( - Direct3D. Я понимаю, что это
не самый лучший способ установки драйвера, но это простейший способ. Я сейчас
работаю над универсальным загрузчиком драйверов, так как способ представленный
здесь постоянно глючит. Как только я все сделаю, обязательно перепишу эту статью.
Ну вобщем пока будем использовать то, что есть.
Итак, после того, как мы определили установки по умолчанию, мы пытаемся получить
установки из файла. Это сделано для того, чтобы мы могли менять установки экрана
не перекомпилируя программу. Функция загрузки из файла показана чуть ниже.
И наконец, мы обнуляем все системные объекты Genesis3D, чтобы не дай бог не
вылезло какое-нть непонятные сообщение.
//Деструктор - уничтожение
GenApp::~GenApp()
{
Cleanup();
}
//При выходе уничтожить все
BOOL GenApp::Cleanup()
{
SAFE_DELETE(Camera);
SAFE_DELETE(m_Player);
if (World) geWorld_Free(World); World = NULL;
if (Engine)
{
geEngine_ShutdownDriver(Engine);
geEngine_Free(Engine);
Engine = NULL;
}
//Всегда возвращаем FALSE из процедуры уничтожения
return FALSE;
}
При уничтожении вызывается процедура Cleanup.
Удаляются объекты камеры и игрока, выгружается из памяти уровень. Затем "глушится"
движок и вычищается из памяти. В конце концов, функция возвращает False.
Теперь, мы будем писать функцию, которая инициализирует приложение, т.е. создает окно, переходит в полноэкранный режим, управляет созданием мира, объектов камеры и игрока, и наконец, запускает главный цикл программы.
//Инициализация приложения
BOOL GenApp::InitApp(HINSTANCE hinstance, int ncmdshow)
{
m_hInstance = hinstance;
m_nCmdShow = ncmdshow;// создать окно и зарегистрировать его
WNDCLASSEX wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = (WNDPROC)WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = m_hInstance;
wcex.hIcon = NULL;
wcex.hCursor = NULL;
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wcex.lpszMenuName = NULL;
wcex.lpszClassName = APPNAME;
wcex.hIconSm = NULL;
RegisterClassEx(&wcex);m_hWnd = CreateWindow(APPNAME, APPNAME, WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, m_Width, m_Height,
NULL, NULL, m_hInstance, NULL);
ATTEMPT(m_hWnd,"Critical Error","GenApp::InitApp() - Failed to create main window");// поместить окно в центр
RECT ScreenRect;
GetWindowRect (GetDesktopWindow(), &ScreenRect);
SetWindowPos (m_hWnd, HWND_TOP, (((ScreenRect.right + ScreenRect.left) / 2) - (m_Width / 2)),
(((ScreenRect.bottom + ScreenRect.top) / 2) - (m_Height / 2)),
m_Width, m_Height, SWP_NOCOPYBITS | SWP_NOZORDER | SWP_NOSIZE | SWP_SHOWWINDOW);
// начнем
ShowWindow(m_hWnd, m_nCmdShow);
UpdateWindow(m_hWnd);// если оконный режим, перевычислить координаты
if (!m_FullScreen)
{
RECT r;
GetClientRect(m_hWnd,&r);
m_Width = r.right;
m_Height = r.bottom;
}
// Инициализировать Genesis
ATTEMPT(InitGenesis(),"Unable to initialize Genesis","GenApp::InitApp() Critical error");// Создать камеру
ATTEMPT( (Camera = new GenCamera()),"Unable to instantiate camera object","GenApp::InitApp() Critical Error");
if (!m_FullScreen)
{
ATTEMPT( Camera->Create(2.0, 0, 0, GetWidth(), GetHeight()),"Unable to create Camera","GenApp::InitApp() Critical error");
}
else
{
ATTEMPT( Camera->Create(2.0, 0, 0, GetWidth()-1, GetHeight()-1),"Unable to create Camera","GenApp::InitApp() Critical error");
}// Загрузить уровень
ATTEMPT(LoadLevel("testroom.bsp"),"Unable to load the level","GenApp::InitApp() Critical error");// Создать игрока
ATTEMPT( (m_Player = new GenPlayer()),"Unable to instantiate Player Object","GenApp::InitApp() Critical error");
ATTEMPT( m_Player->Create(GetWorld(), GetWidth(),GetHeight(), GetMouseSensitivity()),"Unable to Create Player Object","GenApp::InitApp() Critical error");// Установить таймер
SetTimer(m_hWnd,1,1,NULL);return TRUE;
}
Как видите, эта функция только управляет созданием тех или иных объектов Genesis3D. Само создание будет происходить в отдельных функциях и методах, котоые вызывает InitApp.
Эта функция инициализирует Genesis, то есть все его системные объекты. Создается экземпляр движка, получается система драйверов, загружается драйвер (эту функцию мы пишем сами), далее выбранные драйвер и режим устанавливаются, а далее устанавливается гамма. Вы можете подстраивать это значение под ваш конкретный случай.
//Инициализировать Genesis
BOOL GenApp::InitGenesis()
{
Engine=geEngine_Create(m_hWnd, APPNAME, ".");
geEngine_EnableFrameRateCounter(Engine, GE_FALSE); //Убрать FrameRate
DrvSys = geEngine_GetDriverSystem(Engine);
LoadDriver(); //Загрузить установленный драйвер
geEngine_SetDriverAndMode(Engine, Driver, Mode); //Установить режим
geEngine_SetGamma(Engine, 1.8f); //Установить гамму
return TRUE;
}
Эта функция загружает драйвер - Glide, Direct3D или на худой конец Software.
Сначала мы ищем драйвер путем перебора всех найденных в системе. Как только
мы находим драйвер, чье имя, вернее первая буква имени соответствует букве драйвера,
которую мы установили в m_OurDriver, мы закрепляем выбор на этом драйвере и
переходи к поиску видеорежима.
Дальше все аналогично - мы перебираем все найденные режимы и как только находим
удовлетворяющий нашим условиям, закрепяем выбор на нем.
//Загрузить драйвер
BOOL GenApp::LoadDriver()
{
const char *modename = NULL, *drvname = NULL;
long Width, Height;//Ищем драйвер
Driver = geDriver_SystemGetNextDriver(DrvSys, NULL);
if (!Driver) MessageBox(NULL,"No Driver","Error",MB_OK);
while(1) {
geDriver_GetName(Driver, &drvname);
if (drvname[0] == m_OurDriver) break;
Driver = geDriver_SystemGetNextDriver(DrvSys, Driver);
if (!Driver) {
MessageBox(NULL,"No Driver","Error",MB_OK);
_exit(-1);
}
}//Ищем режим
Mode = geDriver_GetNextMode(Driver, NULL);
while(1) {
if (!Mode) MessageBox(NULL,"No Mode","Error",MB_OK);
geDriver_ModeGetWidthHeight(Mode, &Width, &Height);
if (Width == m_Width && Height == m_Height) break;
Mode = geDriver_GetNextMode(Driver, Mode);
}
return TRUE;
}
Как я уже говорил, этот метод довольно глюкав. Я сейчас работаю над другим методом и как только сделаю - заменю его в этой статье. Так же, я был бы вам очень благодарен, если бы вы прислали свой метод загрузки драйвера.
В этой функции мы загружаем уровень из файла. Вся последовательность прослеживается в теле функции. Хотелось только отметить функцию geVFile_OpenNewSystem. Здесь мы открываем файл, то есть используем константу GE_VFILE_TYPE_DOS. Эта же функция может грузить уровни из памяти, виртуальных устройств... Подробнее смотрите в G3D SDK Documentation
//Загрузить уровень
BOOL GenApp::LoadLevel(char* filename)
{
if (World) geWorld_Free(World); World = NULL;
geVFile *Level = NULL;// открыть уровень
Level = geVFile_OpenNewSystem(NULL, GE_VFILE_TYPE_DOS, filename, NULL, GE_VFILE_OPEN_READONLY);
ATTEMPT(Level,filename,"GenApp.LoadLevel() Critical error");
// создать мир из уровня
World = geWorld_Create(Level);
geVFile_Close(Level); Level = NULL;
// добавить мир в энжин
ATTEMPT(geEngine_AddWorld(GetEngine(), GetWorld()),"Unable to load level","geEngine_AddWorld() FAILED");
return true;
}
Следующая простая функция просто загрузить нам установки из файла.
//Загрузить настройки из файла
VOID GenApp::LoadPrefs()
{
FILE *stream;
stream=fopen("app.ini","r");
if (stream==NULL) MSG("Не удается прочитать файл настроек\nИспользуются настроки по умолчанию:\nGlide 640x480")
else
{
fscanf (stream, "%s", &m_OurDriver);
fscanf (stream, "%d", &m_Width);
fscanf (stream, "%d", &m_Height);
fscanf (stream, "%d", &m_FullScreen);
fclose (stream);
}
}
И, наконец, процедура рендеринга кадра. Перед рендерингом, мы должны выполнить преобразования с матрицей камеры. Все процедуры самого рендеринга помещаются между geEngine_BeginFrame и geEngine_EndFrame. В данном случае, мы рендерим мир из "объектива" камеры, которой мы создали.
//Рендерить кадр
BOOL GenApp::RenderWorld()
{
Camera->SetupViewXForm(); //Установить матрицу камеры
//Рендерить все сначала на задний буфер, затем задний буфер перевести
//на передний
ATTEMPT(geEngine_BeginFrame(Engine, Camera->Lens, GE_TRUE),"Unable to beginframe","GenApp::RenderWorld Critical Error");
ATTEMPT(geEngine_RenderWorld(Engine, World, Camera->Lens, 0.0f),"Unable to render world","GenApp::RenderWorld Critical Error");
ATTEMPT(geEngine_EndFrame(Engine),"Unable to endframe","GenApp::RenderWorld Critical Error");return true;
}
Итак, мы разобрались с классом GenApp и переходим к классу GenCamera. Этот класс будет содержать функции для работы с виртуальной камерой. Камера в этом случае представляет вид на созданный нами мир, проецируемый на экране пользователя.
Сперва, мы напишем заголовочный файл Camera.h, в который лягут объявления объектов класса.
//**********************************************
// Name: Camera.h
// Autor: Antiloop
// Desc: Заголовок модуля камеры
//**********************************************//Includes
#include <windows.h>
#include <Genesis.h>
//Объявления класса камеры
class GenCamera
{
public:
geCamera *Lens; // GENESIS camera object
geRect Rect; // область камеры
geXForm3d XForm; // матрица камеры
geVec3d Pos; // текущая позиция камеры
geVec3d Angles; // видимый угол камеры
GenCamera();
virtual ~GenCamera();// Различные процедуры
BOOL Cleanup();geCamera* GetCamera() { return Lens; };
geRect GetRect() { return Rect; };
geXForm3d GetXForm() { return XForm; };
geVec3d GetPos() { return Pos; };
geVec3d GetAngles() { return Angles; };VOID SetPos(geFloat x,geFloat y,geFloat z) { Pos.X = x; Pos.Y = y; Pos.Z = z; };
VOID SetPos(geVec3d n) { Pos.X = n.X; Pos.Y = n.Y; Pos.Z = n.Z; };
VOID SetAngles(geFloat x,geFloat y,geFloat z) { Angles.X = x; Angles.Y = y; Angles.Z = z; };
VOID SetAngles(geVec3d n) { Angles.X = n.X; Angles.Y = n.Y; Angles.Z = n.Z; };BOOL Create(geFloat fov, long left, long top, long right, long bottom);
virtual VOID SetupViewXForm();
virtual VOID SetFOV(geFloat fov);
};
Структура такова же, как и в объявлении класса GenApp.
Теперь, файл Camera.cpp - тела функций класса.
//**********************************************
// Name: Camera.cpp
// Autor: Antiloop
// Desc: Модуль камеры
//**********************************************#include "Camera.h"
Здесь мы устанавливаем камеру в нулевую позицию мира. Также, мы устанавливаем угол обзора в "начало координат".
//Конструктор - инициализация
GenCamera::GenCamera()
{
Lens = NULL;
SetPos(0,0,0);
SetAngles(0,0,0);
}
//Деструктор - вызов уничтожения
GenCamera::~GenCamera()
{
Cleanup();
}//Уничтожение объектов
BOOL GenCamera::Cleanup()
{
geCamera_Destroy(&Lens);
return false;
}
В процедуру создания камеры мы должны передать поле обзора камеры (FOV), а также прямоугольник экрана, на который будет проецироваться вид из камеры. В нашем случае, вид из камеры будет занимать весь экран.
//Создание камеры
BOOL GenCamera::Create(geFloat fov, long left, long top, long right, long bottom)
{
// сохранить прямоугольник "панорамы" камеры
Rect.Left = left;
Rect.Top = top;
Rect.Right = right;
Rect.Bottom = bottom;// создать объект камеры
Lens = geCamera_Create(fov, &Rect);
if (Lens) return true;
else return Cleanup();
}
Так как камера представлена в Genesis3D матрицей, мы должны установить ее значения. Genesis и тут нам помогает, предлагая вместо сугубо математических действий понятные функции geXForm3d_Rotate, geXForm3d_Translate... Эта функция будет вызвана сразу перед рендерингом, поэтому понятно, что задание новых углов и позиции камеры в соответствии с игроком надо будет производить перед вызовом этой функции.
//Установить матрицу камеры
VOID GenCamera::SetupViewXForm()
{
// очистить матрицу
geXForm3d_SetIdentity(&XForm);// установить угол камеры
geXForm3d_RotateZ(&XForm, Angles.Z);
geXForm3d_RotateX(&XForm, Angles.X);
geXForm3d_RotateY(&XForm, Angles.Y);// переместить камеру в новое место
geXForm3d_Translate(&XForm, Pos.X, Pos.Y, Pos.Z);// ну и закрепить это все
geCamera_SetWorldSpaceXForm(Lens, &XForm);
}
Следующая простая функция устанавливает угол обзора/увеличение камеры.
//Установить угол обзора камеры
VOID GenCamera::SetFOV(geFloat fov)
{
geCamera_SetAttributes(Lens, fov, &Rect);
}
Переходим в файл Player.h, где напишем заголовк для класса игрока GenPlayer. В этом классе помимо функций создания/уничтожения будут находиться процедуры передвижения, взаимодействия с клавиатурой, мышью и т. п. Позиция игрока задается с помощью трехмерных векторов m_OldPos и m_NewPos. Направление, куда смотрит игрок также задается трехмерным вектором m_Angles.
//**********************************************
// Name: Player.h
// Autor: Antiloop
// Desc: Заголовок модуля игрока
//**********************************************#include <windows.h>
#include <genesis.h>//Объявления класса игрока
class GenPlayer
{
public:
POINT m_Screen; // это ширина и высота окна для использования мыши
geFloat m_MouseSensitivity; // это сенса мышиgeVec3d m_OldPos, m_NewPos; // игрок до/после движения
geVec3d m_Angles; // угол зрения игрока
geXForm3d m_XForm; // матрица
geWorld* m_World; // мир
GenPlayer(); // конструктор
virtual ~GenPlayer(); // деструктор//Набор различных функций
BOOL Cleanup();
BOOL Create(geWorld *World, int width, int height, geFloat MouseSensitivity);
geXForm3d GetXForm() { return m_XForm; };
geVec3d GetNewPos() { return m_NewPos; };
geVec3d GetOldPos() { return m_OldPos; };
geVec3d GetAngles() { return m_Angles; };
geWorld* GetWorld() { return m_World; };
VOID SetNewPos(geFloat x,geFloat y,geFloat z) { m_NewPos.X = x; m_NewPos.Y = y; m_NewPos.Z = z; };
VOID SetOldPos(geFloat x,geFloat y,geFloat z) { m_OldPos.X = x; m_OldPos.Y = y; m_OldPos.Z = z; };
VOID SetAngles(geFloat x,geFloat y,geFloat z) { m_Angles.X = x; m_Angles.Y = y; m_Angles.Z = z;};
VOID SetWorld(geWorld* world) { m_World = world; };//Мы пока напишем только обработку движений головы
VOID MoveHead();};
В следующем файле: Player.cpp - ключевой будет процедура MoveHead. В ней мы обработам положение мыши и в соответствии с ним будем поворачивать голову игрока. Кроме этого, в классе GenPlayer будет еще несколько функций.
//**********************************************
// Name: Player.cpp
// Autor: Antiloop
// Desc: Модуль игрока
//**********************************************#include "player.h"
//Конструктор - инициализация
GenPlayer::GenPlayer()
{
//Установить начальную позицию игрока
SetNewPos(0,0,0);
SetOldPos(0,0,0);
SetAngles(0,0,0);//Установить матрицу
geXForm3d_SetIdentity(&m_XForm);
}
//Деструктор
GenPlayer::~GenPlayer()
{
// здесь мы будем уничтожать... позже... :)
}
//Уничтожение объектов
BOOL GenPlayer::Cleanup()
{
return false;
}
//Создание игрока
BOOL GenPlayer::Create(geWorld* World, int width, int height,geFloat MouseSensitivity)
{
//Пока просто запоминаем разные значения
SetWorld(World);
m_Screen.x=width;
m_Screen.y=height;
m_MouseSensitivity=MouseSensitivity;
return true;
}//Обработка движения головы
//В этой процедуре мы обсчитываем только углы камеры по осям в соответствии с положением мыши
VOID GenPlayer::MoveHead()
{
// временные переменные
POINT pos, Mouse;
geVec3d TempAngles;geFloat TURN_SPEED; // скорость поворота налево/направо
geFloat UPDOWN_SPEED; // скорость поворота вниз/вверх
GetCursorPos(&Mouse); // получить позицию мышт
TempAngles = m_Angles; // запомнить кгол
pos.x = m_Screen.x/2; // вычислить центр экрана
pos.y = m_Screen.y/2;// каждый шаг сбрасывать мышь в центр экрана
SetCursorPos(pos.x, pos.y);TURN_SPEED = abs(Mouse.x-pos.x) * m_MouseSensitivity; // вычислить скорость поворотов
UPDOWN_SPEED = abs(Mouse.y-pos.y) * m_MouseSensitivity;//!!! если захотите сделать инверт, просто обратите скорость по вертикали
//Теперь, мы поворачиваем камеру в соответствии с позицией мыши
if ((Mouse.x != pos.x) || (Mouse.y != pos.y))
{
if (Mouse.x > pos.x) // мышь ушла влево?
{
TempAngles.Y = TempAngles.Y-(geFloat)(TURN_SPEED); //тогда вращать камеру влево
geXForm3d_RotateY(&m_XForm, TempAngles.Y - m_Angles.Y);
}
else if (Mouse.x < pos.x) // мышь ушла вправо?
{
TempAngles.Y = TempAngles.Y+(geFloat)(TURN_SPEED); //вращать камеру вправо
geXForm3d_RotateY(&m_XForm, TempAngles.Y - m_Angles.Y);
}
if (Mouse.y > pos.y) // мышь ушла вверх?
TempAngles.X = TempAngles.X-(geFloat)(UPDOWN_SPEED); //смотреть вверхelse if (Mouse.y < pos.y) // мышь ушла вниз?
TempAngles.X = TempAngles.X+(geFloat)(UPDOWN_SPEED); //смотреть вниз//Не давать смотреть игроку слишком высоко или слишком низко
//(а то он шею свернет :)
if (TempAngles.X >0.9f) TempAngles.X =0.9f;
if (TempAngles.X <-0.9f) TempAngles.X =-0.9f;
}//Запомнить вычисленные углы, как основные
m_Angles = TempAngles;
}
В конце-концов, мы запомнили новый вектор m_Angles. Но это не значет, что мы физически сдвинули игрока/камеру. Движение камеры как вы помните происходит в классе GenCamera. Отсюда вывод - остается недостающее звено, которое свяжет все это воедино. Ответ - эти звеном будет функция MainLoop, которая обеспечит управление главным циклом программы.
Помимо упомянутой MainLoop, в этом файле будет находится еще несколько важных функций, а также системных функций, без которых вообще невозможна работа приложения - WndProc и WinMain.
//**********************************************
// Name: Main.cpp
// Autor: Antiloop
// Desc: Главные функции
//**********************************************
#include "app.h"//Объект класса GenApp
GenApp *App=NULL;
Эта функция будет использоваться для обработки движения игрока по осям в дальнейших частя учебника.
//Проверка нажатия клавиши (пока не используется)
BOOL IsKeyDown (int KeyCode)
{
if (GetAsyncKeyState(KeyCode) & 0x8000) return TRUE;
return FALSE;
}//================================
// WndProc
//=================================LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_CREATE : ShowCursor(false); break;
case WM_TIMER : if (wParam==1) MainLoop(); break;
case WM_MOUSEMOVE : App->SetMousePos(LOWORD(lParam),HIWORD(lParam)); break;
case WM_KEYDOWN :
switch(wParam)
{
case VK_ESCAPE :
Cleanup();
break;
/*case VK_F12:
App->ScreenShot(App->GetEngine());
break; */
}
break;
case WM_DESTROY : Cleanup(); break;
default : return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}//================================
// WinMain - стартовая точка программы
//=================================
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
ATTEMPT( (App = new GenApp()),"Unable to instantiate App object","WINMAIN Critical Error");
ATTEMPT(App->InitApp(hInstance, nCmdShow),"InitApp Failed","WINMAIN Critical Error");
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
//====================================
// Cleanup - уничтожить класс App и выйти из программы
//====================================BOOL Cleanup()
{
if (App) delete App; App=NULL;
PostQuitMessage(0);return TRUE;
}
Эта функция вызывается по таймеру и управляет очередным шагом программы. Сначала мы перемещаем игрока, затем устанавливаем позицию камеры в зависимости от игрока и наконец, рендерим мир.
//==================================
// MainLoop - главный цикл программы
//==================================VOID MainLoop()
{
App->GetPlayer()->MoveHead(); //Движение головы игрока
//Установить камеру
App->GetCamera()->SetPos(App->GetPlayer()->GetNewPos());
App->GetCamera()->SetAngles(App->GetPlayer()->GetAngles());
//Рендерить мир
App->RenderWorld();
}
Уфф... Очень надеюсь, что я ничего не упустил. Теперь, вы можете откомпилировать программу и если не будет ошибок - то запускать. Если у вас ошибки в процессе Linking, прочитайте рекомендации в самом начале статьи. Если же у вас проблемы с компиляцией, то наверное ошибся где-то я в наборе этой статьи (ошибка чисто механическая - я переписывал код с уже готового примера). Напишите плз. мне письмо и мы вместе исправим это.
Как бы то ни было, вы можете загрузить готовый проект:
GenTut1.zip - 398k
На этом, первая часть закончена, а в следующих частях мы будем надстраивать нашу игру.
Приятного программирования, Antiloop
[Вверх]
Posted: 28.01.2k1
Autor: Antiloop
<anti_loop@mail.ru>