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
#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 ); }; }
#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; } } }