1.
Interloper is a turn based strategy game where each player tries to reach the opposing ‘base’ or eliminate all opponents. One could almost think of it as a post nuclear raid in an underground bunker, eliminating the unwanted intruders. As the teams make their way through the tunnels, they have to ration their time units to fire, or hide or duck back along corridors. Audio clues from enemies firing their guns or from the brief glimpses around corners warn you that you’re not alone in this game. Currently the game is set up for one human player to play against a computer controlled opponent. A two player hot-seat version is playable, as well as an AI against AI fight, but isn’t very entertaining to watch, as it takes a while for a player to move his team through the maze.
The goal of the game was to mimic the old school XCOM games using 3D graphics. The feeling of frustration when you’re close to an enemy but can’t fire, the methodical tactics -- moving your units out slowly to support one another, these place this game from a simpler time. Our final result wasn’t an emotional chiller, nor as dark as we may have wanted, but it still remained fun to write. Multiple camera angles, 3DS generated Models (WITH Texture coordinates that we couldn’t find texture maps that would fit on said models…J), sound, dynamic terrain generation, visibility and unit targeting, these are most of the entertaining features of our game. While we’ve made a try at physics based movement and collision detection, we still retained the original tile based movement scheme.
Controls for this game are straightforward. The middle mouse button will provide intuitive movement for a unit, with the left button providing fire control. The right button drops down the list of keyboard commands as well as acting as a multiple-choice mouse option. Targeting different units and switching between units are normally handled via the ‘f’ and ‘/tab’ keys, with the ‘w’ button switching between the viewpoints. At the end of the turn, you can signal being done by pressing the ‘e’ key and ending your turn.
Major Modules:
Players – Contained a large amount of the code. All unit actions that a player (human or
computer) could do or needed to do was encapsulated in this class
class Players {
public:
Players();
~Players() {}
//! Initialize the player
bool
Init(struct fVec3 *pos, int
face, int color);
void ResetTimer() { timeLeft = maxTime;}
//! Render a Player
bool
LoadModel(char * name);
bool
Render() const;
void UpdateState(float timeElapsed);
//!PrintFunctions
(stdout)
void PrintStats(); //Print attributes
void PrintEnemyList(); //Print the visible list of enemies
void InsertVisList(Players * enemy_ptr);
void ClearVisList();
int
GetNumSeen() {return vis_enemies.size();}
void SetRenderMode(Render_Mode mode);
//!ScreenPrint
Functions
string GetHealthString();
string GetTUString();
//! Return Vectors
struct
fVec3 Facing() const;
int
RotationFacing() const {return rotationFacing;}
struct fVec3 GetPosition()
const {return position;}
//! movement Calls
bool
Forward();
bool
TurnLeft();
bool
TurnRight();
//! Combat Calls
bool
Shoot();
Weapon weapon;
bool
LoseHealth(int x);
//! Enemy Calls
Players* NextCurrent();
Players* GetCurrentEnemy();
Players* NextCurrentEnemy();
//! Camera following
void FollowPlayer(Camera *cam);
Camera * ThisCamera() { return myCamera;}
void ReleaseCamera();
void UpdateFollower(float timeElapsed);
void ChangeView() { myView = !myView;}
void ResetView() { myView = 0;}
int
ViewType() { return myView;}
bool
isDead() const
{return
(hp<=0);}
bool
isAlive() const
{return
(hp>0);}
bool
isCommanded();
bool
notUpdated();
bool
atEnd();
bool
active() {return AI;}
bool
setActive(bool value) {AI =
value; return AI;}
bool shortCircuit;
int
Sonar();
void setEnd(char x) { endPos = x;}
private:
struct
fVec3 position;
struct
fVec3 velocity;
struct
fVec3 facing;
struct
fVec3 destination;
Quad rotation;
Quad finalRotation;
short rotationFacing;
Camera * myCamera;
int
myView;
bool
command;
bool
facingOK;
bool
updated;
bool
AI;
list<Players*>
vis_enemies;
int
unitColor;
int
timeLeft;
int
maxTime;
int
hp;
Players* currentEnemy;
char endPos;
Render_Mode render_mode;
Model3ds model;
};
Game – The highest level of our code, this was the display and hardware level.
public:
Game();
virtual ~Game();
//! default game
window size and title
static const int DEFAULT_WINDOW_WIDTH
= 640;
static const int DEFAULT_WINDOW_HEIGHT = 480;
static const int MIN_WINDOW_SIZE = 5;
static const char*
WINDOW_TITLE;
//! initialize game
data
bool
Init(const char* levelFilename);
//! exectute
main game loop
void Run();
//! Switches Turns
void TurnOver();
private:
//! initialize main
window
bool
_InitWindow();
//! initialize the
popup menu
bool
_InitPopupMenu();
//! read level
description file
bool
_ReadLevel(const char* levelFilename);
//! renders world
contents to windows
void _Render();
The World level dealt with the
different teams of the game individually, containing both the active camera
baring units as well as keeping track of who was alive on each team of Players.
//! Initialize the world
bool
Init(const char* skyboxPath, const char* courseFilename);
//! Render the world
bool
Render();
bool
Collision(fVec3 position, fVec3 target, float radius);
//! update world
state for single game loop iteration
// timeElapsed
is in seconds
void UpdateState(float timeElapsed);
void TurnOver();
bool
allDead();
bool
StillMoving();
bool
gameOver() { return won;}
bool
playerTurn() { return currentTurn;}
Players* ViewChange();
void UpdateEnemyList(Players * activePlayer,
bool playerTurn);
bool
InSight(fVec3 p_pos, fVec3 en_pos);
void PrintEnemyList();
void CreateProjectile(struct fVec3 src, struct fVec3 targ,int dmg, float rge,$
float blast_rad, float speed, int time );
void UpdateProjectiles(int time);
void PrintProjectileList();
void Action(int action);
Players *player1;
Players *player2;
private:
Skybox
_skybox; //!< textured skybox cube
// Course _course; //!< course map == singleton,
_course
// Needs to use course, where did it go
again?
void runLights();
int
getNearest(struct fVec3
*location, struct fVec3 *dest);
int
getLoc(struct fVec3
*location, int val);
int
UnitReplace();
Octal
*_Octal;
bool
won;
bool
currentTurn;
Players team1[NUM_UNITS];
Players team2[NUM_UNITS];
list<Projectile>
bullets;
//AI information
void AI(Players *,
Players **);
void AI2(Players *,
Players **); //Dumb AI
bool
TeamMoving(Players*);
int
AIcount;
int
currentAI;
bool
team1AI;
bool
team2AI;
};
Support Modules
Sound – Straightforward Singleton class. This controlled playing sound within the game code. An example of not well utilized code, as we only had a few background sounds.
Artifact – Handled the majority of the objects in the world. Consisted of mainly display lists, and rendering instructions.
//! Turns on all the objects
in the world
bool InitWorldTexture(const char* texturePath);
void displaySquare(int type); //Helper
function to create square objects
void displayObjCreate(int nameAndTexture, int secondTexture);
void displayFloor();
void displayInter();
void displayHorizontal();
void displayVertical();
void displayWall();
void displayDoor();
Object * makeFloor(float x, float y,
float z);
Object * makeInter(float x, float y,
float z);
Object * makeHorizontal(float x,
float y, float z);
Object * makeVertical(float x, float
y, float z);
Object * makeWall(float x, float y,
float z);
Object * makeDoor(float x, float y,
float z);
Weapon – a modular class to encapsulate weapons. Due to time constraints and other limitations, we ended up with a single generic weapon for our game.
class Weapon{
public:
Weapon();
Weapon(string nme, int dam,float
rge, int cost, int blast_rad, float bull_ra$
void SetWeapon(string nme, int dam,float rge,
int cost, int blast_rad, float$
void Fire(struct fVec3 source, struct fVec3
target);
string GetName()
{return name;}
int GetDamage()
{return damage;}
float GetRange()
{return range;}
float GetBlastRadius()
{return blast_radius;}
int
GetTimeUnitCost()
{return tu_cost;}
float GetRadius()
{return radius;}
float GetSpeed()
{return speed;}
private:
string name;
int
damage;
float range;
int
tu_cost;
float blast_radius;
float radius;
float speed;
};
Octal – An octal tree for geometry clipping
class Octal {
public:
Octal() {Init();}
~Octal()
{Destroy();}
//! Initialize/Destroy the Quad tree
void Init();
void Destroy();
//! Exposing values
struct
float Size() {
return myEdge;}
//! Node/Leaf
bool
setCenter(struct fVec3 newCenter, float side);
bool
IsNode() { return isNode;}
//! Add element to the Quad tree
void Add(Object *a);
//! Prints out in order lowest block
void PrintOctal(Octal *root);
//! Temporary drawing functions
void drawAll(struct fVec3 *ptr, int total);
Camera – A simple class that made the turn based concept MUCH simpler by allowing us to “pass it” to units within the world rather than giving everyone one. This is a great idea that I wish we had implemented more. It handled who saw who as well as visibility stuff.
struct CameraState
{
float eye[3]; //! eye position
float
target[3]; //! 'lookAt' target
float up[3]; //! camera 'up'
direction
};
Quad – a Quaternion class for the third person camera. Barely modified from previous incarnations
class Quad {
public:
Quad();
Quad Inverse()
const;
float Norm() const;
struct
fVec3 AxisOfRotation() const;
float AngleOfRotation() const;
void Quad::AngleToQuad(float angle);
Quad operator*(Quad a);
bool
Quad::operator!=(Quad a) const;
bool
Quad::operator==(Quad a) const;
void setQuad(Quad a);
void quad_from_matrix(float *mat);
void matrix_from_quad(float *mat) const;
void matrix_from_quad(float *mat, struct
fVec3 pos) const;
struct
fVec3 Quad::fromZAxis() const;
float AngleBetween(Quad a) const;
Object – a derived class that served as a utility for rendering
class Object {
public:
Object() {Init();}
//
virtual ~Object() {};
//! Initialize an object
virtual bool Init();
virtual bool Init(float, float, float, Object*, GLuint,
int, float, float);
virtual bool Destroy();
//! Render an object
void Render()
const;
//! update state
information
void UpdateState(float timeElapsed);
//! return
internals
struct
fVec3 Position() const {return position;}
int
Type() const {return type;}
float Radius()
const {if(type == SPHERE) return arm; return -1;}
float Side() const
{if(type == CUBE) return arm; return -1;}
float Base() const
{if(type == BOX) return arm; return -1;}
float BaseHeight() const {if(type == BOX) return arm2; return
-1;}
//! Linked List calls
Object *childOf() const {return
child;}
bool
addChild(Object *Child);
Object *removeHead();
Course – we used the same duplicate design from labs for our game it seemed in the spirit of XCOM and efficient
class Course {
public:
inline static
Course& getInstance() {
if(!s_pCourse) s_pCourse = new
Course();
return *s_pCourse;
}
static Course *s_pCourse;
Course();
virtual ~Course();
//! Initialize the map
bool
Init(const char* courseFilename);
//! Render the course using the current
viewing transform
bool
Render() const;
//! access the
course map
unsigned char GetMapLocation(int x, int y) const
{ assert(x >= 0
&& x < _courseWidth && y >= 0
&& y < _courseLength);
return _courseMap[(y * _courseWidth) +
x]; }
void SetMapLocation(int x, int y, unsigned char value)
{ assert(x >= 0
&& x < _courseWidth && y >= 0
&& y < _courseLength);
_courseMap[(y * _courseWidth) + x] = value;
}
unsigned short Width() const
{ return _courseWidth;}
unsigned short
Height() const
{ return _courseLength;}
unsigned short maxDim() const
{
if(_courseWidth > _courseLength)
return _courseWidth;
return _courseLength;
}
//! Output the course to a stream
bool
WriteToStream(ostream& os) const;
//! Takes course and puts it into a vector
point list
bool
Course::MakeList(struct
fVec3 ** output, int * total);
float Course::GetHeight(int i, int j);
bool
Course::isValidPos(float x, float y, float z);
struct
fVec3 * Course::fillOctal(Octal * head);
private:
// Data
unsigned char* _courseMap; //!<
course map buffer
unsigned short _courseWidth;
//!< size of course grid width
unsigned short _courseLength;
//!< size of course grid length
Vector – A Vector class that provided geometric functions as needed.
struct fVec3 {
float pts[3];
};
void setVector(struct fVec3*, float, float, float);
void setVector(struct fVec3*,struct fVec3*);
//Adds X + Y => X
void addVector(struct fVec3*,struct fVec3);
void multVector(struct fVec3*,float);
void divVector(struct fVec3*, int);
float absF(float x);
3DS – a simple 3DS file parser/class stolen from the web implemented and modified for use in our own model class
class CLoad3DS
{
public:
CLoad3DS(); // This inits the data members
// This is the
function that you call to load the 3DS
bool
Import3DS(t3DModel *pModel, char *strFileName);
private:
// This reads in a
string and saves it in the char array passed in
int
GetString(char *);
// This reads the
next chunk
void ReadChunk(tChunk *);
// This reads the
next large chunk
void ProcessNextChunk(t3DModel *pModel,
tChunk *);
// This reads the
object chunks
void ProcessNextObjectChunk(t3DModel *pModel,
t3DObject *pObject, tChunk
*);
// This reads the
material chunks
void ProcessNextMaterialChunk(t3DModel *pModel,
tChunk *);
// This reads the
RGB value for the object's color
void ReadColorChunk(tMaterialInfo *pMaterial, tChunk *pChunk);
// This reads the
objects vertices
void ReadVertices(t3DObject *pObject, tChunk *);
// This reads the
objects face information
void ReadVertexIndices(t3DObject *pObject,
tChunk *);
Union-find && mazegen: These were fairly simple union find algorithm on a character array that produces a solution from one start point to one endpoint. Cell based so not especially attractive but Utilitarian Practical and was quick to get up and running. We intentionally separated from the rest of the code as to leave it modularly producing text files. Also we could generate our own maps for the purpose of debugging.
Content
All of the models and textures were ‘borrowed’ from various web sources. Although we may have been able to create our own units, time constraints and lack of ability hampered our creative endeavors. In other words, most of the materials we do have aren’t up to the high standards of most current games. Our sound was provided courtesy of the fmod sound library, and the 3DS loader was modified from an example given on Digiben’s website. As our code runs on a cluster computer, we used OpenGL GLUT the SDL and FMOD as our exterior libraries, as well including files from several game programming tutorial sites.
3. Technical Challenges
Perhaps the most difficult part of our assignment was the large amount of half written and partially implemented code we generated. Although we borrowed a 3DS Loader, this had to be rewritten to align to our personal data structures and how we accessed our data. We have two different methods of generating a map, and the implementation we finally decided upon was the third generation of map symbols and how we were to implement walls in our game.
Reusing code from other sources required us to understand and integrate large amounts of modular code. Everything from the sound libraries to the 3DS loader could easily have been a project on their own. Thankfully, we were able to bring everything together in time to get our project finished on time.
Two of the most difficult sections of our game weren’t supposed to be challenging at all: Artificial intelligence and shooting opponents.
Intelligent AI Remains a fun exercise in tweaking constants to get right. Although the current implementation depends heavily on random chance, a much larger algorithm was designed and implemented… however the tweaking didn’t go quite as well. The random AI seemed to beat the actually programmed one with such ease and we removed our AI from the final version of the game. We would have liked to have more time to work on these constants, but problems with model loading and terrain mapping interfered with anything more than the bare bones implementation of computer sentience.
Shooting/Visibility was to be a core component of our game. However, this section of our code took much longer than we had scheduled for it. Instead of being completed before the Thanksgiving break, we were forced to continue working on errors well into December. There were problems with how we allowed weapons fired, and what constituted “valid” targets. Although we intended multiple weapons, I’m afraid we ended this project with a single generic ‘weapon.’
4.
Eli:
Three Things that Went Right:
· Got an early start
· Met our design spec with a few extras on time
· Enjoyed the project
Three Things that Went Wrong:
· The Workload was far from even some days we were putting in 16 hours or more others days went by without anyone touching the code. Some times one person put in significantly more time than other. Very separate and isolated work environment.
· Quite a few hang ups where a person felt there hands were tied
· A lot of late nights and a few frustrating moments
Wish I could have hired an artist since neither me nor my partner were one. Perhaps rethought the notion of a 5 minute presentation to make a more immediate response sort of game. Turn based strategy actually requires a great deal of planning and over head despite the time based movement
Paul:
Three Things that Went Right:
· Have a fully working game
· Learned a lot about the different sections of a game, the modular design of our code made is easy to add and remove sections at a time.
· We’re NOT using teapots!
Three Things that Went Wrong:
· We had the tendency to not work together on code. I tended to code in the graphics cluster, while Eli worked from home. This led to a large lack of communication, and some confusion among code.
· Not following our time charts. There were a lot of things I’d have liked to have working fully. Up until recently, lots of our code was in partially functional, or almost working modules that weren’t implemented fully.
· Lack of ‘prettyness.’ This game doesn’t quite have the polish on it that I would have liked. In addition, we cut a few features that I had thought would have been needed to instead work on not quite essential features.
The largest problem was the lack of communication and focus. I know I spent quite a few nights as the only one in the graphics cluster, and I know that Eli spent awhile at home coding as well. But there were too many times that I solved a days long problem in Eli’s code or he fixed an error that was bugging me, that should have been fixed a lot earlier. Wasted time was quite a large problem. The planning of our code, while nice, did have to be taken apart and revamped from time to time when new features were added. AI required a rewrite of all the input functions and the addition of a limited finite state machine to handle commands. Original plans weren’t implemented and instead were simply added into existing structures. This project was entertaining, but I would have liked a project that stuck closer to the design specifications and time line than this one did.