First-Person Shooter Custom Engine (2022)

From Scratch C++  -  5 developers -  8 weeks development time  -  Engine Programmer - PC/PS5

In this project, we were tasked with creating a first-person shooter game engine that could run on both PC and PS5. As I took on the role of an engine programmer, I developed a lot of tools and features to facilitate game development later such as the input system, the collision system, and the physics system.


The feature I am most proud of is the collision detection and handling system. As a team, we discussed if it would be better to use a library for this or make it from scratch but since we only needed simple collision such as Box, Capsule, and Line, we decided it would be best to implement our own.


Here is a snippet of the code that shows both Capsule-Box collision and Capsule-Capsule collision:


// Code based on https://developer.mozilla.org/en-US/docs/Games/Techniques/3D_collision_detection

bool CollisionManager::BoxOnCapsule(BoxCollider& a_boxCollider, CapsuleCollider& a_capsuleCollider, f3& a_mtv)

{

    // Get closest point on capsule to box

    f3 closestPoint = ClosestPointOnLineSegment(a_capsuleCollider->basePoint, a_capsuleCollider->headPoint, f3((a_boxCollider->max[0] + a_boxCollider->min[0]) * 0.5f, 

(a_boxCollider->max[1] + a_boxCollider->min[1]) * 0.5f, (a_boxCollider->max[2] + a_boxCollider->min[2]) * 0.5f));


    // Get box closest point to sphere center by clamping

    float x = std::max(a_boxCollider->min[0], std::min(closestPoint[0], a_boxCollider->max[0]));

    float y = std::max(a_boxCollider->min[1], std::min(closestPoint[1], a_boxCollider->max[1]));

    float z = std::max(a_boxCollider->min[2], std::min(closestPoint[2], a_boxCollider->max[2]));


    // Get squared distance between cube and sphere

    float distanceSqrd = (x - closestPoint[0]) * (x - closestPoint[0]) + (y - closestPoint[1]) * (y - closestPoint[1]) + (z - closestPoint[2]) * (z - closestPoint[2]);



    if (distanceSqrd < (a_capsuleCollider->radius * a_capsuleCollider->radius))

    {  

        // Get how much the two objects are overlaping

        float penetrationDepth = (sqrt(distanceSqrd) - a_capsuleCollider->radius);

        // Get the vector from the sphere center to the point on the box and normalize it

        f3 vecBetweenObjects = normalize(f3(x - closestPoint[0], y - closestPoint[1], z - closestPoint[2]));

        a_mtv = vecBetweenObjects * penetrationDepth;

        return true;

    }

    return false;

}


// Code based on Real-Time Collision Detection by Christer Ericson

bool CollisionManager::CapsuleOnCapsule(CapsuleCollider& a_capsule1, CapsuleCollider& a_capsule2, f3& a_mtv)

{

    // Compute (squared) distance between the inner structures of the capsules

    float s, t;

    f3 c1, c2;

    float dist2 = ClosestPtSegmentSegment(a_capsule1->basePoint, a_capsule1->headPoint, a_capsule2->basePoint, a_capsule2->headPoint, s, t, c1, c2);

    float radius = a_capsule1->radius + a_capsule2->radius;

    // If (squared) distance smaller than (squared) sum of radii, they collide

    if (radius * radius > dist2)

    {

        f3 vecBetweenCapsules = c1 - c2;

        float penetrationDepth = (radius * radius) - dist2;

        a_mtv = vecBetweenCapsules * penetrationDepth;

        return true;

    }

    return false;

}


The a_mtv is the minimum translation vector that needs to be applied to the object for the object to no longer be colliding with the other object.