Treads of Glory

Treads of Glory is a shared-screen multiplayer tank game built using C++ and OpenGL. Players compete in arena-style combat using bouncing bullets and mines.

I worked on this project as a programmer. My responsibilities included the game physics and collision.

The game was developed by a team of three programmers over three weeks. Artwork courtesy of Alfonso Callejas

View Code

[code lang=”cpp”] #pragma once

#include "EVector2.h"
#include "EUtil.h"

#define NUM_COLLISION_SHAPES 2

namespace Equinox
{

enum CollisionShape
{
SQUARE, CIRCLE
};

class CollisionObject
{
public:
CollisionObject( const EVector2& position) : position( position ) { }

virtual CollisionShape getShape() = 0;
void setPosition( const EVector2& position ) { this->position = position; }

const EVector2& getPosition() { return position; }
virtual const EVector2 getMaxBound() = 0;
protected:
EVector2 position;
};

class CollisionCircle : public CollisionObject
{
public:
CollisionCircle( const EVector2& position, float radius ) :
CollisionObject( position ),
radius( radius ) { }

float getRadius() const { return radius; };
void setRadius( float radius ) { this->radius = radius; }

CollisionShape getShape() { return CIRCLE; }

const EVector2 getMaxBound() { return EVector2( radius, radius); }

private:
float radius;
};

class CollisionSquare : public CollisionObject
{
public:
CollisionSquare( const EVector2& position, float halfWidth) :
CollisionObject( position ),
halfWidth( halfWidth ) { }

float getHalfWidth() const { return halfWidth; }

CollisionShape getShape() { return SQUARE; }

const EVector2 getMaxBound() { return EVector2( halfWidth, halfWidth); }

private:
float halfWidth;
};

struct CollisionInfoBundle
{
float depth;
EVector2 normal;
};

class CollisionManager
{
public:
CollisionManager();

// Tests whether (first) and (second) collide. If they do, depth and normal
// will be saved in (collisionInfo) and returns true. Otherwise returns false.
bool DoesCollide( CollisionObject* first, CollisionObject* second, CollisionInfoBundle& collisionInfo );

private:
typedef bool (*CollisionFunc)( CollisionObject*, CollisionObject*, CollisionInfoBundle& );
CollisionFunc m_TestFunc[ NUM_COLLISION_SHAPES ][ NUM_COLLISION_SHAPES ];

static bool UndefinedCollision( CollisionObject*, CollisionObject *, CollisionInfoBundle& collisionInfo );

static bool DoesCollideSquareSquare( CollisionObject* first, CollisionObject* second, CollisionInfoBundle& collisionInfo );
static bool DoesCollideSquareCircle( CollisionObject* first, CollisionObject* second, CollisionInfoBundle& collisionInfo );
static bool DoesCollideCircleCircle( CollisionObject* first, CollisionObject* second, CollisionInfoBundle& collisionInfo );
};

}
[/code]

[code lang=”cpp”] #include "CollisionManager.h"

#include <cassert>
#include <cmath>

namespace Equinox
{

//================================================================================
//================================================================================
CollisionManager::CollisionManager()
{
for ( unsigned int i = 0; i < NUM_COLLISION_SHAPES; ++i )
for ( unsigned int j = 0; j < NUM_COLLISION_SHAPES; ++j )
m_TestFunc[i][j] = UndefinedCollision;

m_TestFunc[0][0] = DoesCollideSquareSquare;
m_TestFunc[0][1] = DoesCollideSquareCircle;
m_TestFunc[1][1] = DoesCollideCircleCircle;
}

//================================================================================
//================================================================================
bool CollisionManager::DoesCollide( CollisionObject* first, CollisionObject* second, CollisionInfoBundle& collisionInfo )
{
if ( first == nullptr || second == nullptr ) return false;

float reverser = 1.0f;

if ( second->getShape() < first->getShape() )
{
swap( first, second );
reverser = -1.0f;
}

bool result = m_TestFunc[first->getShape()][second->getShape()]( first, second, collisionInfo );
collisionInfo.normal *= reverser;

return result;
}

//================================================================================
//================================================================================
bool CollisionManager::UndefinedCollision( CollisionObject*, CollisionObject *, CollisionInfoBundle& collisionInfo )
{
assert( false );

return false;
}

//================================================================================
//================================================================================
bool CollisionManager::DoesCollideSquareSquare( CollisionObject* first, CollisionObject* second, CollisionInfoBundle& collisionInfo )
{
CollisionSquare* squareA = static_cast< CollisionSquare* >( first );
CollisionSquare* squareB = static_cast< CollisionSquare* >( second );

return false;
}

//================================================================================
//================================================================================
bool CollisionManager::DoesCollideSquareCircle( CollisionObject* first, CollisionObject* second, CollisionInfoBundle& collisionInfo )
{
CollisionSquare* square = static_cast< CollisionSquare* >( first );
CollisionCircle* circle = static_cast< CollisionCircle* >( second );

float dist[2];

for ( int i = 0; i < 2; ++i )
dist[i] = abs( square->getPosition().GetAxis( i ) – circle->getPosition().GetAxis( i ) );

unsigned int axis = ( dist[0] > dist[1] ) ? 0 : 1;
unsigned int auxAxis = !axis;

if ( ( dist[axis] < square->getHalfWidth() + circle->getRadius() ) && ( dist[ auxAxis ] < square->getHalfWidth() ) )
{
// Collision occurred with "line" portion of square
collisionInfo.normal.SetAxis( axis, -1.0f );
collisionInfo.normal.SetAxis( auxAxis, 0.0f );

if ( square->getPosition().GetAxis( axis ) < circle->getPosition().GetAxis( axis ) )
collisionInfo.normal *= -1;

collisionInfo.depth = square->getHalfWidth() + circle->getRadius() – dist[ axis ];

return true;
}

float offset = square->getPosition().GetAxis( axis ) > circle->getPosition().GetAxis( axis ) ? -square->getHalfWidth() : square->getHalfWidth();
float auxOffset = square->getPosition().GetAxis( auxAxis ) > circle->getPosition().GetAxis( auxAxis ) ? -square->getHalfWidth() : square->getHalfWidth();

float d1 = ( square->getPosition().GetAxis( axis ) + offset ) – circle->getPosition().GetAxis( axis );
float d2 = ( square->getPosition().GetAxis( auxAxis ) + auxOffset ) – circle->getPosition().GetAxis( auxAxis );

if ( d1 * d1 + d2 * d2 < circle->getRadius() * circle->getRadius() )
{
// Collided with point
collisionInfo.normal.SetAxis( axis, d1 );
collisionInfo.normal.SetAxis( auxAxis, d2 );
collisionInfo.normal.Normalize();

collisionInfo.depth = sqrt( d1 * d1 + d2 * d2) – circle->getRadius();

return true;
}

return false;
}

//================================================================================
//================================================================================
bool CollisionManager::DoesCollideCircleCircle( CollisionObject* first, CollisionObject* second, CollisionInfoBundle& collisionInfo )
{
CollisionCircle* circleA = static_cast< CollisionCircle* >( first );
CollisionCircle* circleB = static_cast< CollisionCircle* >( second );

const EVector2& posA = circleA->getPosition();
const EVector2& posB = circleB->getPosition();

float distX = posB.X – posA.X;
float distY = posB.Y – posA.Y;
float distSq = distX * distX + distY * distY;

float combinedRadius = circleA->getRadius() + circleB->getRadius();

if ( distSq < combinedRadius * combinedRadius )
{
collisionInfo.depth = combinedRadius – sqrt(distSq);

collisionInfo.normal.X = distX;
collisionInfo.normal.Y = distY;
if ( distX == 0 && distY == 0 )
{
collisionInfo.normal.Y = -1;
}
else
{
collisionInfo.normal.Normalize();
}

return true;
}
else
{
return false;
}
}

}
[/code]