3D Shooting Game

Download as pdf or txt
Download as pdf or txt
You are on page 1of 54

Overview

• Our Goal
• Why make a game
• Thanks Giving to “OpenGL Game
Programming” Book by Dave Astle
• Storyline
• Design
• Problems
• Screenshots
Our Goal
The main goal of our project was to make a 3D First Person Shooter Game.
Although there are lots of such games already in the market. We wished to
make it our own way. Initially When we opted for our project we knew only
about how to make a Room. But later Studying various Books and Websites
we got the ideas to load an MD2 Model which would be the monster in
our game. We could have designed our own MD2 Model. But due to lack of
time, We used the Models that were developed for Quake2 game.
Our Game World Consists of Various Rooms. These Rooms have a Sliding
door which gets automatically opened when the player is near it, and gets
closed when player moves away from it.
Our Project is implemented in C++/OpenGL. C++ takes care of the Backend
Part
OpenGL takes care of the Frontend part.
Why Make a Game?
• Something We always wanted to do
• More fun than previous projects
• Graphics programming
• Uses concepts learned in CS courses
Thanks to
“OpenGL Game Programming” for It’s
Invaluable Ideas for our Project
Storyline
You’ve been trapped inside a Monster
House. You Got to Kill all the Sod
monsters of Level 1 and get into the
Level 2. The Level 2 brings you to attack
with Ogro Monsters.
That’s It! You are set free from
the Monster house: GAME OVER
Design
Data Structures/Algorithms:
• Our Game Engine
• Trees
• CObject and Collision Detection
• Game Engine Core
• Artificial Intelligence
• Game Mode
Our Game Engine
Our Game Engine’s Class Design
Example of Game World Architecture
Tree Data Structure
To make a Tree we need Nodes to get attached to it, So we introduce the
concept of CNode class. An CNode would look something like this:
Attaching Nodes
void AttachTo(CNode *newParent)
{
// if this node is already attached to another node, then detach
if (parentNode)
Detach();
parentNode = newParent;
if (parentNode->childNode)
{
prevNode = parentNode->childNode->prevNode;
nextNode = parentNode->childNode;
parentNode->childNode->prevNode->nextNode = this;
parentNode->childNode->prevNode = this;
}
else
{
parentNode->childNode = this; // this is the first child
}
}
CObject

To make anything a part of our 3D World, it should first be inherited from


CObject class. Later the inherited object can have different properties.

For Example: a Room and a Monster are not the same. Room is static
whereas Monster attacks the player, it has animation in it and also some
amount of AI.

CObject is basically derived from the CNode, which makes the Tree Data
Structure.
The Attributes of an Object are:
Position Vector: Where the object is in the 3D World
Velocity Vector: With what velocity the object is moving
Acceleration Vector: Acceleration of the object
maxX: This determines the maximum value of x component from the origin of the
object
minX: This determines the minimum value of x component from the origin of the object
maxY: This determines the maximum value of y component from the origin of the
object
minY: This determines the minimum value of y component from the origin of the object
maxZ: This determines the maximum value of z component from the origin of the object
minZ: This determines the minimum value of z component from the origin of the object
Size: This describes the Size of the Object from the origin of the object
isDead: This determines whether the object should be in the 3D World or not.

The attributes maxX, minx, maxY, minY, maxZ, minZ describes the bounding box of the
object for Collision Detection w.r.t other objects.
The CObject has the following methods:

Protected Member Functions:

virtual void OnAnimate(scalar_t deltaTime)


virtual void OnDraw(CCamera *camera)
virtual void OnCollision(CObject *collisionObject)
virtual void OnPrepare()

Public Member Functions:


void Draw(CCamera *camera)
void Animate(scalar_t deltaTime)
void ProcessCollisions(CObject *obj)
void Prepare()
CObject *FindRoot()
The OnPrepare () has the following default code:
virtual void OnPrepare()
{
ProcessCollisions(FindRoot()); // perform collisions starting
// with root world object
}

The code of void Draw(CCamera *camera) is as Shown:


void Draw(CCamera *camera)
{
// push modelview matrix on stack
glPushMatrix();
OnDraw(camera); // draw this object
if (HasChild()) // draw children
((CObject*)childNode)->Draw(camera);
glPopMatrix();

// draw siblings
if (HasParent() && !IsLastChild())
((CObject*)nextNode)->Draw(camera);
}
The Trace for Animate(deltaTime) is as shown below:

The Tree is Traversed in a Preorder Fashion


The void ProcessCollisions(CObject *obj) also does something similar to Draw()
and Animate() but it checks for the condition of Collision Occurrence first.
The Following is the code:
// perform collision detection
void ProcessCollisions(CObject *obj)
{
if(obj->maxX > position.x
&& obj->minX < position.x
&& obj->maxY > position.y
&& obj->minY < position.y
&& obj->maxZ > position.z
&& obj->minZ < position.z)
{
OnCollision(obj); // perform this object's collisionwith obj

// test child collisions with obj


if (HasChild())
((CObject*)childNode)->ProcessCollisions(obj);

// test sibling collisions with obj


if (HasParent() && !IsLastChild())
((CObject*)nextNode)->ProcessCollisions(obj);
}
// if obj has children, check collisions with these children
if (obj->HasChild())
ProcessCollisions((CObject*)(obj->childNode));

// if obj has siblings, check collisions with these siblings


if (obj->HasParent() && !obj->IsLastChild())
ProcessCollisions((CObject*)(obj->nextNode));
}
The ProcessCollisions(CObject *obj) is never directly called, instead it is called via
OnPrepare().
The code of void Prepare() is as shown:
// prepare object
void Prepare()
{
OnPrepare(); // prepare this object

if (HasChild()) // prepare children


((CObject*)childNode)->Prepare();

if (HasParent() && !IsLastChild()) // prepare siblings


((CObject*)nextNode)->Prepare();
}

This makes up an Object!


THE GAME ENGINE CORE
Our Game Engine Core is nothing but than the display Callback GameCycle ()
The typical game cycle consists of the following steps:
1. Gather input: This is taken care by the glutKeyboardFunc (OnKeyPress)
2. Move player
3. Perform artificial intelligence (AI)
4. Calculate physics
5. Render the scene
And the code performing the same is shown below:
// prepare objects and perform collisions
gameWorld->Prepare();
// move/orient camera. This is same as Player
gameCamera->Animate(deltaTime);
// move/orient objects. This wud let Monsters to think //and doors to slide
gameWorld->Animate(deltaTime);
// draw objects
gameWorld->Draw(gameCamera);
//swap buffers. This Renders the Scene
glutSwapBuffers();
Handling Input
The Keyboard Callback OnKeyPress is as shown for the GAMESTART mode:
switch(key) {
case GLUT_KEY_UP:
gameCamera->velocity += CVector(0,0,5);
break;
case GLUT_KEY_DOWN:
gameCamera->velocity += CVector(0,0,5);
break;
case GLUT_KEY_RIGHT:

gameCamera->yaw += 2.5; break;


case GLUT_KEY_LEFT:

gameCamera->yaw -= 2.5; break;


case GLUT_KEY_PAGE_UP: mouseSensitivity += 0.05f; break;
case GLUT_KEY_PAGE_DOWN: mouseSensitivity -= 0.05f;
if (mouseSensitivity < 0.05)
mouseSensitivity = 0.05;
break;
case 27: mode=MMENU;
}
The Mouse Callback OnMouseMove is as shown for the GAMESTART mode:
diffx=x-lastx;
diffy=y-lasty;
lastx=x;
lasty=y;

gameCamera->yaw += (float)diffx;
gameCamera->pitch -= (float)diffy;
glutPostRedisplay();
Here every time we OnMouseMove () is called we find diffx and diffy. We store the x
and y in lastx and lasty for the next call to OnMouseMove ().
Next we update the gameCamera’s yaw by diffx and gameCamera’s pitch by diffy.
The Texture Class

class CTexture
{
private:
long int scaledWidth;
long int scaledHeight;

public:
int width;
int height;
int bitDepth;
unsigned int texID;
unsigned char *data;
CTexture() { data = NULL; count++; printf("%d " , count ); }
~CTexture() { Unload(); }
void LoadTexture(char *filename);
void Unload()
{
glDeleteTextures(1, &texID);

if (data != NULL)
free(data);
data = NULL;

}
};

The LoadTexture(filename) lets to load the desired texture, and the texture id would
be stored in texID.
The Game World
Finally, here we describe about the class which is letting us to walk into.
We named it as CWorld class.
It has the following attributes:
All the Objects of the world. This mainly includes the Room Objects. Rooms can be
of FRoom, LRoom, RRoom or Broom type.
It has a pointer to the current position of the Game Camera: camera
It has a CGUI class object which lets us to print scores and other stuff onto the
screen.
And other miscellaneous objects as per the Game Development.
It has the following Methods:
void LoadRooms() : It loads all the rooms of the world into the memory.
void LoadWorld() : This calls LoadRooms() and also creates some monsters as per
necessity.
void Animate(float deltaTime) : This function lets the Preorder Traversal of all the
Objects connected to the main Object “house” to be animated.
void Draw(CCamera *camera) : This function lets the Preorder Traversal of all the
Objects connected to the main Object “house” to be drawn onto the Screen.
void Prepare() : This function lets the Preorder Traversal of all the Objects
connected to the main Object “house” to be prepared for the next GameCycle.
void FadeScreen() : This function Fades the screen by blending. We use this
Function to display Game Over.
And Other miscellaneous methods as per the Game Development.
The Game Camera
As expected, the CCamera class defines the camera viewing system for the
engine and is responsible for determining how you view the world. The
CCamera class is defined as follows:

class CCamera
{
public:
//CVector oldpos;
CVector position; // position of camera
CVector velocity; // velocity of camera
CVector acceleration; // acceleration of camera
CVector lookAt; // lookat vector

// up, forward, right vectors


CVector up;
CVector forward;
CVector right;

// yaw and pitch angles


float yaw;
float pitch;
CCamera();
// do physics calculations
void Animate(scalar_t deltaTime);
};

The attributes are changed via:


yaw : This determines the rotation around the Vertical axis i.e., y axis
pitch : This determines the rotation around the Horizontal axis i.e., x axis
velocity :This determines the speed and direction in which the camera is moving

The other attributes i.e., position, lookAt , acceleration and velocity are altered or
changed by the Animate().
The code of void Animate(scalar_t deltaTime) is:

void Animate(scalar_t deltaTime)


{

if (yaw >= 360.0f)


yaw = 0.0f;
if (yaw < 0.0f)
yaw = 360.0f;

if (pitch > 60.0f)


pitch = 60.0f;
if (pitch < -60.0f)
pitch = -60.0f;

float cosYaw = (scalar_t)cos(DEG2RAD(yaw));


float sinYaw = (scalar_t)sin(DEG2RAD(yaw));
float sinPitch = (scalar_t)sin(DEG2RAD(pitch));
float speed = velocity.z * deltaTime;
float strafeSpeed = velocity.x * deltaTime;

if (speed > 15.0)


speed = 15.0;
if (strafeSpeed > 15.0)
strafeSpeed = 15.0;
if (speed < -15.0)
speed = -15.0;
if (strafeSpeed < -15.0)
strafeSpeed = -15.0;

if (velocity.Length() > 0.0)


acceleration = -velocity * 1.5f;

velocity += acceleration*deltaTime;

position.x += float(cos(DEG2RAD(yaw + 90.0)))*strafeSpeed;


position.z += float(sin(DEG2RAD(yaw + 90.0)))*strafeSpeed;
position.x += float(cosYaw)*speed;
position.z += float(sinYaw)*speed;
lookAt.x = float(position.x + (cosYaw));
lookAt.y = float(position.y + sinPitch);
lookAt.z = float(position.z + (sinYaw));

gluLookAt(position.x, position.y, position.z,


lookAt.x, lookAt.y, lookAt.z,
0.0, 1.0, 0.0);
}
Loading MD2 Models
This was the toughest part of our project. We learn the concepts of an MD2 Model
from various websites. But none taught us completely. Finally we got a clearer
picture of the concept from “OpenGL Game Programming”. Much of the code
model loading we have used is directly from this book. Thanks to Dave Astle for
making this happen ☺.
We named this class as CMD2Model. The CMD2Model is inherited from CObject
MD2 Header is:
typedef struct
{
int ident; // identifies as MD2 file "IDP2"
int version; // mine is 8
int skinwidth; // width of texture
int skinheight; // height of texture
int framesize; // number of bytes per frame
int numSkins; // number of textures
int numXYZ; // number of points
int numST; // number of texture
int numTris; // number of triangles
int numGLcmds;
int numFrames; // total number of frames
int offsetSkins; // offset to skin names (64 bytes each)
int offsetST; // offset of texture s-t values
int offsetTris; // offset of triangle mesh
int offsetFrames; // offset of frame data (points)
int offsetGLcmds; // type of OpenGL commands to use
int offsetEnd; // end of file
} modelHeader_t;
To Animate the Model we use the concept of Keyframe Interpolation.
For instance, suppose you have an animation of a bullet model, where the bullet
travels from the
Point (x0, y0, z0) to the point (x1, y1, z1). The modeler can create a keyframe
animation
Sequence of just two frames from the bullet animation: one frame with the bullet
located at (x0,y0, z0), and the next frame with the bullet located at (x1, y1, z1).
Our job as a programmer Would be to then calculate all the points through which the
bullet will travel in between (x0, y0,z0) and (x1, y1, z1) for a certain number of frames
to produce a smooth animation of the traveling Bullet.

We use interpolation to determine frames


between two keyframe. And the formula is:
Xi + interpolatePercentage * (Xf - Xi)
The more specific Start State to End State are listed below
Frame# Action
----------------
0-39 idle
40-46 running
47-60 getting shot but not falling (back bending)
61-66 getting shot in shoulder
67-73 jumping
74-95 idle
96-112 getting shot and falling down
113-122 idle
123-135 idle
136-154 crouch
155-161 crouch crawl
162-169 crouch adjust weapon (idle)
170-177 kneeling dying
178-185 falling back dying
186-190 falling forward dying
191-198 falling back slow dying
Artificial Intelligence for Monsters
The Monsters are made to rotate around a desired path. It may be to move around
a room or to just to act as a watchman at doors.
Our AI design snapshot is something like this:
Clockwise Angle Changes: AntiClockwise Angle Changes:
Other AI Designs
The Player

The player is the interactive part of the game and he can be moved around the
world with the mouse and keyboard.
We should keep track of old coordinates to know how far the player should move
and in which direction.
Sliding Door
Door slides automatically to open, when the player is near the door and closes as the
player moves away from it, this is implemented with the help of distance of player from the
door. If this distance is equal or less than the minimum distance then door opens and when
distance is greater than this minimum distance it closes.
ROOMS

The Rooms are nothing but than an modified version of a cube. Having various
ways of entrances
Entrance at Front: FROOM
Entrance at Back: BROOM
Entrance at Left: LROOM
Entrance at Right: RROOM

Each Rooms are made out of a still more basic Unit called as Wall.
A CWall produces a Quad with position at the center.
If Orientation=0 then it’s parallel to x-axis
Else if Orientation=1 then it’s parallel to y-axis
Rooms Design:
Walls Design:
Front Entrance Design:
Fog

float fogColor[4] = {0.1, 0.1, 0.1, 0}; // Let's make the Fog Color black too

glFogi(GL_FOG_MODE, GL_EXP2 ); // Set The Fog Mode


glFogfv(GL_FOG_COLOR, fogColor); // Set The Fog Color
glFogf(GL_FOG_DENSITY, 0.015f); // Set How Dense Will The Fog Be
glHint(GL_FOG_HINT, GL_NICEST ); // Set The Fog's calculation accuracy
glFogf(GL_FOG_START, 10); // Set The Fog's Start Depth
glFogf(GL_FOG_END, 150); // Set The Fog's End Depth

glEnable(GL_FOG);
Snapshots
CONLUSION

3D computer games includes the concept of vectors, projections, lightening and


texture mapping.Game is basically divided into these areas:
Graphics
Input
Game logic and
Artificial intelligence
User interface and menuing system

You might also like