Welcome to the first in a series of Special Edition tutorials. These tutorials will take you step by step through
game creation. We are using the Genesis 3D engine to create a Role Playing Game(the cat is out of the bag), and
these tutorials will take you step by step through the process. Each tutorial will tackle a bit more advanced stage
of our final application. This tutorial(#1) will set up our basic environment, opening a screen resolution and
driver type that you previously set(we will be releasing the source to a set up program in the future as this routine
becomes more advanced) and allowing you to fly aroud inside of it.
The goal of these tutorials is to take you through every process step by step, to ensure that you understand each
individual step before you reach the next one.
This tutorial has been updated to v1.0 standards by Geoffrey W. Curtis
This example is based on the earlier minimum application by Royce
Pipkens. This example will create an application very similar to that in the minimum application. However,
this one will look for a file called prefs.ini to load
your preferences. The prefs.ini file is a simple 3 line text file that you need to create. It's syntax goes as
follows:
<driver to use>
<x resolution>
<y resolution>
This is pretty self explanatory. We are using a quick and dirty check(as used by Royce) on the first letter of
the driver name, so <driver to use> should be set to either G(for glide), S(for software), or ((for Direct3D). For example if you wished
to create a Direct3D 640x480 application our 3 lines would read:
(
640
480
Likewise an 800x600 Glide application(Voodoo2 only) would read as follows:
G
800
600
Now we need to create our project workspace. Many people have mailed me with problems in the past getting their
project set up. A few simple things to remember.
#1) Don't forget to include all of the Genesis lib, and include directories into your project.
#2) Make sure you set up your executable and the work directory to a directory that contains the Genesis3D files(drivers,
maps, etc). The only things that need be in this directory are your driver files.
#3) This example searches for a map at location (workdir)\maps\genvs.bsp. So you must create a directory named
maps, and copy a map to that location(genvs.bsp is default but you can place any map there).
#4) Remember that this file is a .c file not a .cpp. If you name it as .cpp you will get errors.
Our project only needs to contain one file and this is the one. I have tried to comment everything I have added.
-----------> Start Of Code(rpg.c)
#include <windows.h>
#include <stdio.h>
#include "Genesis.h"
FILE *stream;
geVFile *Level = NULL;
static HWND CreateMainWindow(HINSTANCE hInstance, char *AppName, int32 Width, int32 Height);
LRESULT CALLBACK WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam);
void MoveCamera(void);
void LoadPrefs(void);
HWND mainwindowhandle;
geEngine *Engine = NULL;
geDriver_System *DrvSys = NULL;
geDriver *Driver = NULL;
geDriver_Mode *Mode = NULL;
geWorld *World = NULL;
geCamera *Camera = NULL;
geRect Rect;
char *modename = NULL, *drvname = NULL;
MSG Msg;
geXForm3d Xform;
int run, Width, Height;
// The following three values are values that we will load from our prefs.ini file.
// Also set here to be used as default values if a prefs.ini file is not found
// Set to reasonable defaults pertaining to your target audience
int CWidth =640;
int CHeight=480;
char ourdriver = '(';
geBitmap *Black = NULL;
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdParam, int nCmdShow)
{
//Load preferences from file
LoadPrefs();
//CreateMainWindow is copies from GTest.
mainwindowhandle = CreateMainWindow(hInstance, "RPG", CWidth, CHeight);
//Start the Genesis Engine
Engine = geEngine_Create(mainwindowhandle, "RPG", ".");
if (!Engine) MessageBox(NULL,"No Engine", "Error",MB_OK);
//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 to 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, 2.0f);
//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);
//create a world object from a BSP file. Of course you'll need to edit the path.
Level = geVFile_OpenNewSystem(NULL, GE_VFILE_TYPE_DOS, "levels\\genvs.bsp", NULL, GE_VFILE_OPEN_READONLY); // | GE_VFILE_OPEN_DIRECTORY);
//World = geWorld_Create("levels\\genvs.bsp");
World = geWorld_Create(Level);
//Close the Level File
geVFile_Close(Level);
if (!World) {
MessageBox(NULL,"No World","Error",MB_OK);
_exit(-1);
}
// connect world to engine
if (geEngine_AddWorld(Engine, World)== GE_FALSE)
MessageBox(NULL, "engine cannot use this level", "Error", MB_OK);
//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, (geFloat)0.0, (geFloat)0.0, (geFloat)0.0);
//geXForm3d_SetTranslation(&Xform, (geFloat)0.0, (geFloat)0.0, (geFloat)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);
// geCamera_SetAttributes(Camera, 2.0f, &Rect);
//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, World, Camera)) break;
if (GE_FALSE == geEngine_BeginFrame(Engine, Camera, GE_FALSE)) break;
//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(HANDLE hInstance, char *AppName, int32 Width, int32 Height)
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,
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:
case WM_LBUTTONUP:
case WM_RBUTTONUP:
case WM_LBUTTONDOWN:
{
PostMessage(hWnd, WM_QUIT, 0, 0);
}
default:
return DefWindowProc(hWnd, iMessage, wParam, lParam);
}
return 0;
}
//Moves the camera base on our mouse position
void MoveCamera(void)
{
POINT pos;
GetCursorPos(&pos);
ScreenToClient(mainwindowhandle, &pos);
// checks where the mouse is(left, right, up, or down)
// then change our camera location accordingly.
// Notice: We replaced the 320,200 part with CWidth/CHeight.
// This allows this routine to work properly in any screen resolution.
// In essence it checks to see where the mouse location is, and then depending on how far it is
// to that exteme we adjust our POV.
if (pos.x > ((CWidth/2) + 50)) {
geXForm3d_Translate(&Xform, (float)((pos.x - (CWidth/2))/10.0), 0.0, 0.0);
}
if (pos.x < ((CWidth/2) - 50)) {
geXForm3d_Translate(&Xform, (float)((pos.x - (CWidth/2))/10.0), 0.0, 0.0);
}
if (pos.y > ((CHeight/2) + 50)) {
geXForm3d_Translate(&Xform, 0.0, 0.0, (float)((pos.y - (CHeight/2))/10.0));
}
if (pos.y < ((CHeight/2) - 50)) {
geXForm3d_Translate(&Xform, 0.0, 0.0, (float)((pos.y - (CHeight/2))/10.0));
}
geCamera_SetWorldSpaceXForm(Camera, &Xform);
}
// Load our preferences from prefs.ini
void LoadPrefs(void)
{
stream = fopen("prefs.ini","r");//open as read only
// Checks to see if prefs file was be opened and if not
// then the function is exited and the defaults (values set earlier)
// are used.
if(stream == NULL)
{ MessageBox(NULL, "Prefs.ini file not found, using defaults...", "No Prefs", MB_OK);
return;
}
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)
In the next tutorial we will clean up our code, add the ability to spin sideways, look up and down, and add
a proper XForm routine. We will later use these changes to implement collision detect.
As always you can send comments or suggestions for this tutorial to erasmushurt@earthlink.net.
A big thanks go to Geoffrey W. Curtis for converting this code to Genesis v1.0 specifications.