The first tutorial was really just an introduction to what we are doing. It was designed to be extremely simple,
and to start off on ground that you may already be familiar with. In this tutorial we will take it a little bit
further, and we will clean up our code(which will be important as our project grows).
We are now going to have two seperate files, rpg.c and rpg.h.
In the first version of our Special Edition tutorials we some quick and dirty xform routines, and we will also
clean those up in this lesson. Most importantly we will add the building blocks for our viewing environment. Before
we get into how to do this, I felt I should clear up our new movement options.
Since this is going to be a Role Playing Game and we are going to use the mouse to select options on the screen(much
much later) we are not always going to want to move when we move the mouse. I have chosen to allow players to rotate
left/right and look up/down by holding down the right mouse button. I am not decided on how to allow a player to
move forward/backwards and strafe at this time. Most games seem to have gone the route of using the arrow keys,
but I also want to seek an alternate(and more efficient) means of doing so. Any suggestions can be mailed to erasmushurt@earthlink.net. The goal is to somehow allow users to do
everything without ever having to touch the keyboard(if that is at all possible). In the meantime you can move
forward or backward by using the mouse position up or down without pressing the right mouse button.
There are quite a few changes in this tutorial so I will simply give the complete listing for both rpg.c
and rpg.h.
-----------> Start Of Code(rpg.c) #include <windows.h> #include <stdio.h> #include "rpg.h" //our include file #include "include\Genesis.h" void MoveCamera(void); void LoadPrefs(void);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpszCmdParam, int nCmdShow)
{
LoadPrefs();
//CreateMainWindow is copies from GTest.
mainwindowhandle = CreateMainWindow(hInstance, "RPG", CWidth, CHeight);
//Start the Genesis Engine
Engine = geEngine_Create(mainwindowhandle, "RPG", NULL);
//get the DriverSystem object. Sort of a container for the valid video drivers
//on your system
DrvSys = geEngine_GetDriverSystem(Engine);
if (!DrvSys) MessageBox(NULL,"No DrvSys", "Error",MB_OK);
//by specifying NULL in the 2nd param we get the first
//valid video driver object in the DrvSys container
//For this miserable hack we just assume it is the Software driver
Driver = geDriver_SystemGetNextDriver(DrvSys, NULL);
if (!Driver) MessageBox(NULL,"No Driver","Error",MB_OK);
//Find our selected drive and use it.
while(1) {
geDriver_GetName(Driver, &drvname);
// check to see if it is the correct driver
if (drvname[0] == ourdriver) break;
Driver = geDriver_SystemGetNextDriver(DrvSys, Driver);
if (!Driver) _exit(-1);
}
//Just as the DrvSys object is a containor for the valid video drivers
//for your system, a Driver object is a container for the vaild video modes
//(320x200, 640x480, etc.) for the particular driver.
//By specifying NULL, as the 2nd param in this call we retrive an object representing
//the first video mode contained in our Driver object
Mode = geDriver_GetNextMode(Driver, NULL);
//This loop is intended to sift thru all the modes until it finds one
//with dimensions CWidth x CHeight.
while(1) {
if (!Mode) MessageBox(NULL,"No Mode","Error",MB_OK);
//get the dimension as integers from this Mode object
geDriver_ModeGetWidthHeight(Mode, &Width, &Height);
//compare the dimensions, if they are right exit the loop
//Mode pointing at this Mode object
if (Width == CWidth && Height == CHeight) break;
//otherwise get the next Mode object and try again
Mode = geDriver_GetNextMode(Driver, Mode);
}
//tell Genesis the use our selected video Driver in our selected video mode
if (!geEngine_SetDriverAndMode(Engine, Driver, Mode)) {
MessageBox(NULL,"Set Driver/Mode failed","Error",MB_OK);
_exit(-1);
}
geEngine_SetGamma(Engine, 50.0f);
//create a world object from a BSP file. Of course you'll need to edit the path.
LevelFile = geVFile_OpenNewSystem(NULL, GE_VFILE_TYPE_DOS, "maps\\genvs.bsp", NULL,
GE_VFILE_OPEN_READONLY);
World = geWorld_Create(LevelFile);
if (!World) {
MessageBox(NULL,"No World","Error",MB_OK);
_exit(-1);
}
//define the rectangle on the main window that the camera will draw into
//It's in window coordinates, <snort>, that took me a good while to figure out.
Rect.Left = 0;
Rect.Right = CWidth-1;
Rect.Top = 0;
Rect.Bottom = CHeight-1;
//create a camera object with an FOV of 2 (clueless),
//and Rect window rendering area
Camera = geCamera_Create(2.0f, &Rect);
if (!Camera) {
MessageBox(NULL,"No Camera","Error",MB_OK);
_exit(-1);
}
//build a vector operator for the camera. i guess the camera is a vector.
//an Xform is a 4x4 matrix that can have both vector rotation and vector translation
//operations encoded in to them. A vector that is "vector multipled" by one of these
//matrices will undergo the encoded operations.
//This call sets the matrix up to do exactly nothing
geXForm3d_SetIdentity(&Xform);
//These calls set the Xform matrix to first rotate about the z-axis
//then about the Y-axis, then about the X-axis. Not by much in this case, but
//you understand. One note 2nd param is in radians. (2*pi rads per 360 degs)
geXForm3d_RotateZ(&Xform, (geFloat)0.0);
geXForm3d_RotateX(&Xform, (geFloat)0.0);
geXForm3d_RotateY(&Xform, (geFloat)0.0);
//NOTICE: We have changed this value to 0,0,0. Our camera's settings will be
//changed via ViewXForm
geXForm3d_Translate(&Xform, 0, 0, 0);
//i guess this does the actual multiplication of the camera vector by the xform. //It might reset the camer vector before it does it tho, not too sure. geCamera_SetWorldSpaceXForm(Camera, &Xform);
//this loop is intended to be the main rendering and windows message pumping loop
run = 1;
while (run) {
//gather mouse motion data, move camera
MoveCamera();
//Begin the engine frame, Engine, World, & Camera better all be valid pointers
//or its gonna hang.
if (GE_FALSE == geEngine_BeginFrame(Engine, Camera, GE_TRUE)) return GE_FALSE;
//render to offscreen buffer if (GE_FALSE == geEngine_RenderWorld(Engine, World, Camera, 0.0f)) break; //end the frame, copy offscreen buffer to onscreen window if (GE_FALSE == geEngine_EndFrame(Engine)) break;
// Do the'ol message pump (Code fragemt from GTest)
//read all the windows messages in the task qeque and
//dispatch the to the window. Note that GetMessage will return false
//if it gets a WM_QUIT message. In the window message handler rountine
//we put a WM_QUIT message in our own qeque if the mouse is clicked.
while (PeekMessage( &Msg, NULL, 0, 0, PM_NOREMOVE))
{
if (!GetMessage(&Msg, NULL, 0, 0 ))
{
run = 0;
break;
}
TranslateMessage(&Msg);
DispatchMessage(&Msg);
}
}
//shut it all down and quit
geCamera_Destroy(&Camera);
geWorld_Free(World);
geEngine_ShutdownDriver(Engine);
geEngine_Free(Engine);
return (0);
}
//=====================================================================================
// CreateMainWindow
//=====================================================================================
static HWND CreateMainWindow(HINSTANCE hInstance, char *AppName, int32 Width, int32 Height)
{
WNDCLASS wc;
HWND hWnd;
RECT WindowRect;
//
// Set up and register application window class
//
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = (WNDPROC)WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
wc.lpszMenuName = (const char*)NULL;
wc.lpszClassName = AppName;
RegisterClass(&wc);
//
// Create application's main window
//
hWnd = CreateWindowEx(
0,
AppName,
AppName,
0,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
NULL,
NULL,
(HINSTANCE)hInstance,
NULL);
if (!hWnd)
{
MessageBox(0, "Could not create window.", "** ERROR **", MB_OK);
_exit(1);
}
UpdateWindow(hWnd);
SetFocus(hWnd);
SetWindowLong(hWnd,
GWL_STYLE,
GetWindowLong(hWnd, GWL_STYLE) & ~WS_POPUP);
SetWindowLong(hWnd,
GWL_STYLE,
GetWindowLong(hWnd, GWL_STYLE) | (WS_OVERLAPPED |
WS_CAPTION |
WS_SYSMENU |
WS_MINIMIZEBOX));
SetWindowLong(hWnd,
GWL_STYLE,
GetWindowLong(hWnd, GWL_STYLE) | WS_THICKFRAME |
WS_MAXIMIZEBOX);
SetWindowLong(hWnd,
GWL_EXSTYLE,
GetWindowLong(hWnd, GWL_EXSTYLE) | WS_EX_TOPMOST);
WindowRect.left = 10;
WindowRect.top = 10;
WindowRect.right = 10+Width;
WindowRect.bottom = 10+Height;
SetWindowPos(hWnd,
HWND_TOP,
10,
10,
Width+10,
Height+10,
SWP_NOCOPYBITS | SWP_NOZORDER);
//
// Make window visible
//
ShowWindow(hWnd, SW_SHOWNORMAL);
return hWnd;
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT iMessage,
WPARAM wParam, LPARAM lParam)
{
switch(iMessage)
{
case WM_RBUTTONDOWN: //is RMB down?
spin = 1; //if so then start spinning
}
switch(iMessage)
{
case WM_RBUTTONUP: //is it back up?
spin = 0; //then stop spinning
}
switch(iMessage)
{
case WM_LBUTTONUP:
case WM_LBUTTONDOWN:
{
PostMessage(hWnd, WM_QUIT, 0, 0);
}
default:
return DefWindowProc(hWnd, iMessage, wParam, lParam);
}
return 0;
}void MoveCamera(void)
{
POINT pos;
geVec3d Pos; //this value will be used later when we translate our ViewXform
GetCursorPos(&pos);
ScreenToClient(mainwindowhandle, &pos);
// are we in spin mode(RMB down) then check for angles
if (spin ==1)
{
// checks where the pointer is at(left, right, up, or down)
// notice that we adjust it according to our height and width, that way
// it is proper in every resolution;
if (pos.x > ((CWidth/2) + 50)) { // is it to the left?
Angles.Y = Angles.Y-(geFloat)((pos.x-(CWidth/2))*.00005); //if so spin left
}
else if (pos.x < ((CWidth/2) - 50)) {//is it to the right?
Angles.Y = Angles.Y+(geFloat)(((CWidth/2)+pos.x)*.00005); //if so spin right
}
if (pos.y > ((CHeight/2) + 50)) {//is it to the top?
Angles.X = Angles.X-(geFloat)((pos.y-(CHeight/2))*.00005); //if so look up
}
if (pos.y < ((CHeight/2) - 50)) {
Angles.X = Angles.X+(geFloat)(((CHeight/2)+pos.y)*.00005); //if so look down
}
}
else // we are not in spin mode
{
if (pos.y > ((CHeight/2) + 50)) { //is it to the top?
Xform.Translation.Z += 3.0f; //move forward
}
if (pos.y < ((CHeight/2) - 50)) { //is it to the bottom?
Xform.Translation.Z -= 3.0f; //move backward
}
} //make sure we arent looking too far up or down. If we are then fix that! if (Angles.X>.5) Angles.X = .5; if (Angles.X<-.5) Angles.X =-.5;
ViewXForm = Xform; // Copy our Xform into ViewXForm(so we can deform it to // where our player is looking etc)
Pos = ViewXForm.Translation;
// Clear the matrix geXForm3d_SetIdentity(&ViewXForm);
// Rotate then translate.
geXForm3d_RotateZ(&ViewXForm, Angles.Z);
geXForm3d_RotateX(&ViewXForm, Angles.X);
geXForm3d_RotateY(&ViewXForm, Angles.Y);
geXForm3d_Translate(&ViewXForm, Pos.X, Pos.Y, Pos.Z);
ViewXForm.Translation.Y += 140.0f; // We give a +140 adjustment to simulate an eye-high view.
geCamera_SetWorldSpaceXForm(Camera, &ViewXForm);
}
// Load our preferences from prefs.ini
void LoadPrefs(void)
{
stream = fopen("prefs.ini","r");//open as read only
fscanf(stream,"%s",&ourdriver);//get our information
fscanf(stream,"%d",&CWidth);
fscanf(stream,"%d",&CHeight);
fclose(stream); //dont forget to close
}--------> End of Code(rpg.c)
Next we create our rpg.h file. -------> Start Of Code(rpg.h)
#include <windows.h> #include <stdio.h> #include "genesis.h"
static HWND CreateMainWindow(HINSTANCE hInstance, char *AppName, int32 Width, int32 Height); LRESULT CALLBACK WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam);
int spin = 0; //whether or not we are spinning geVec3d playerpos; FILE *stream; HWND mainwindowhandle; geEngine *Engine = NULL; geDriver_System *DrvSys = NULL; geDriver *Driver = NULL; geDriver_Mode *Mode = NULL; geWorld *World = NULL; geCamera *Camera = NULL; geRect Rect; const char *modename = NULL, *drvname = NULL; MSG Msg; geXForm3d Xform; geVec3d Angles; geXForm3d ViewXForm; long run, Width, Height; geVFile *LevelFile; int CWidth =640; //Our Clients Width And Height, and driver(loaded from prefs.ini) int CHeight=480; char ourdriver = 'G';
-------> End Of Code(rpg.h)
In the next tutorial we will implement collision detect.
As always you can send comments or suggestions for this tutorial to erasmushurt@earthlink.net.
[Hyperlinkleisten stehen in diesem Web nicht zur Verfügung]