Search code examples
c++optimizationcollision-detectiongame-physics

Need help optimizing C++


I have programmed a simple top-down car driving game that resembles the first GTA, on the GameBoyAdvance. I have used only vector graphics for doing so, and the GBA doesn't handle it very well; basically with 5 pedestrian instances it lags.

I don't have much experience in optimizing code, so I would like to know if there are some tweaks I could make to my code in order to make it run faster, not depending on the fact that it runs on a GBA.

The collision testing I use is SAT (separating axis theorem) as I've found it to be the easisest one for collision check with vector graphics; the game is very simple itself.

Here is the code:

/*
GTA Vector City
Author: Alberto Taiuti
Version: 2.0
*/

#include "Global.h"
#include <string.h>
#include <cstdio>
#include "font.h"
#include "CVector2D.h"
#include "CCar.h"
#include "CPed.h"
#include <vector>
#include <memory>

/* GLOBAL VARIABLES */
void CheckCollisionsRect(CRect *test_a, CRect *test_b);
std::vector<CVector2D> PrepVectors(CRect *shape);
CVector2D GetMinMaxShape(std::vector<CVector2D> vect_shape, CVector2D axis);
void CheckCollisionRectVSPoint(CRect *test_a, CVector2D *point);

/* MAIN */

// The entry point for the game
int main()
{
// Frame counter
uint32_t frames = 0;

// Previous & current buttons states
static uint16_t prev_buttons = 0, cur_buttons = 0;

  // Put the display into bitmap mode 3, and enable background 2.
REG_DISPCNT = MODE4 | BG2_ENABLE;

    // Set up the palette.
SetPaletteBG(BLACK, RGB(0, 0, 0)); // black
SetPaletteBG(WHITE, RGB(31, 31, 31)); // white
SetPaletteBG(GREY, RGB(15, 15, 15)); // grey
SetPaletteBG(RED, RGB(31, 0, 0)); // red
SetPaletteBG(GREEN, RGB(0, 31, 0)); // green
SetPaletteBG(BLUE, RGB(0, 0, 31)); // blue

// Create car instance
CCar *car = new CCar(50,50);

// Create a building
/*CRect *test_b = new CRect(100.0f, 100.0f, 30, 30);
CRect *test_c = new CRect(120.0f, 120.0f, 30, 30);
CRect *test_d = new CRect(30.0f, 30.0f, 30, 30);*/

// Pedestrian instances
int ped_number = 10; // Number of pedestrians
std::vector<CPed*> peds; // Ped. entities container (made of smart pointers)
typedef std::vector<CPed*>::iterator p_itor; // Iterator

for(int i = 1; i <= ped_number; i++)
{
    peds.push_back(new CPed(i, RED, 2.0f));
}

// Check whether the game is over
bool end = false;

// Main loop
while (!end)
{
    // Flip the screen
    FlipBuffers();

    //Clear the screen
    ClearScreen8(BLACK);

    // Update frame counter
    frames ++;  

    // Get the current state of the buttons.
    cur_buttons = REG_KEYINPUT;

    // Handle Input
    car->HandleInput(prev_buttons, cur_buttons);

    // Logic

    car->Update();
    for(int i = 0; i < ped_number; i++)
    {
        peds[i]->Update();
    }

    for(int i = 0; i < ped_number; i++)
    {
        CheckCollisionRectVSPoint(car->shape, peds[i]->pos);
    }

    /*CheckCollisionsRect(car->shape, test_b);
    CheckCollisionsRect(car->shape, test_c);
    CheckCollisionsRect(car->shape, test_d);
    CheckCollisionRectVSPoint(car->shape, test_ped->pos);*/

    // Render
    car->Draw();
    for(int i = 0; i < ped_number; i++)
    {
        peds[i]->Draw();
    }
    /*test_b->DrawFrame8(GREEN);
    test_c->DrawFrame8(WHITE);
    test_d->DrawFrame8(RED);
    test_ped->Draw();*/


    prev_buttons = cur_buttons;

    // VSync
    WaitVSync();
}



// Free memory
delete car;
//delete test_b; delete test_c; delete test_d;
//delete test_ped;
for(p_itor itor = peds.begin(); itor != peds.end(); itor ++)// Delete pedestrians 
{
     peds.erase(itor);
}

return 0;
}

void CheckCollisionsRect(CRect *test_a, CRect *test_b)
{
// If the two shapes are close enough, check for collision, otherways skip and save calculations to the CPU
//if((pow((test_a->points[0]->x - test_b->points[0]->x), 2) + pow((test_a->points[0]->y - test_b->points[0]->y), 2)) < 25.0f) 
{

    // Prepare the normals for both shapes
    std::vector<CVector2D> normals_a = test_a->GetNormalsAsArray();
    std::vector<CVector2D> normals_b = test_b->GetNormalsAsArray();

    // Create two containers for holding the various vectors used for collision check
    std::vector<CVector2D> vect_test_a = PrepVectors(test_a);
    std::vector<CVector2D> vect_test_b = PrepVectors(test_b);

    // Get the min and max vectors for each shape for each projection (needed for SAT)
    CVector2D result_P1 = GetMinMaxShape(vect_test_a, normals_a[1]); //
    CVector2D result_P2 = GetMinMaxShape(vect_test_b, normals_a[1]); //
    // If the two objects are not colliding
    if(result_P1.y < result_P2.x || result_P2.y < result_P1.x)
    {
        return;
    }
    CVector2D result_Q1 = GetMinMaxShape(vect_test_a, normals_a[0]); // First axis couple
    CVector2D result_Q2 = GetMinMaxShape(vect_test_b, normals_a[0]); // 
    if(result_Q1.y < result_Q2.x || result_Q2.y < result_Q1.x)
    {
        return;
    }
    CVector2D result_R1 = GetMinMaxShape(vect_test_a, normals_b[1]); //
    CVector2D result_R2 = GetMinMaxShape(vect_test_b, normals_b[1]); //
    if(result_R1.y < result_R2.x || result_R2.y < result_R1.x)
    {
        return;
    }
    CVector2D result_S1 = GetMinMaxShape(vect_test_a, normals_b[0]); // Second axis couple
    CVector2D result_S2 = GetMinMaxShape(vect_test_b, normals_b[0]); // 
    if(result_S1.y < result_S2.x || result_S2.y < result_S1.x)
    {
        return;
    }


    // Do something
    PlotPixel8(200, 10, WHITE);
    PlotPixel8(200, 11, WHITE);
    PlotPixel8(200, 12, WHITE);

}
}

// Check for collision between an OOBB and a point
void CheckCollisionRectVSPoint(CRect *test_a, CVector2D *point)
{
// Prepare the normals for the shape
std::vector<CVector2D> normals_a = test_a->GetNormalsAsArray();

// Create a container for holding the various vectors used for collision check
std::vector<CVector2D> vect_test_a = PrepVectors(test_a);

// Get projections for the OOBB (needed for SAT)
CVector2D result_P1 = GetMinMaxShape(vect_test_a, normals_a[1]); 
float result_point = point->DotProduct(normals_a[1]); 
// If the two objects are not colliding on this axis
if(result_P1.y < result_point || result_point < result_P1.x)
{
    return;

}
CVector2D result_Q1 = GetMinMaxShape(vect_test_a, normals_a[0]);
result_point = point->DotProduct(normals_a[0]);
// If the two objects are not colliding on this axis
if(result_Q1.y < result_point || result_point < result_Q1.x)
{
    return;

}

// Do something
PlotPixel8(200, 10, WHITE);
PlotPixel8(200, 11, WHITE);
PlotPixel8(200, 12, WHITE); 
}

// Returns a container with projection vectors for a given shape
std::vector<CVector2D> PrepVectors(CRect *shape)
{
std::vector<CVector2D> vect;

// Create vectors for projection and load them into the arrays
for( uint16_t i=0; i < 5; i++)
{       
    // Get global position of vectors and then add them to the array
    vect.push_back(shape->GetVectorGlobal(i));
}

return vect;
}

CVector2D GetMinMaxShape(std::vector<CVector2D> vect_shape, CVector2D axis)
{
// Set initial minimum and maximum for shape's projection vectors
float min_proj = vect_shape[1].DotProduct(axis);
float max_proj = vect_shape[1].DotProduct(axis);
// Calculate max and min projection vectors by iterating along all of the corners
for(uint16_t i = 2; i < vect_shape.size(); i ++)
{
    float current_proj = vect_shape[i].DotProduct(axis);
    // Select minimum projection on axis
    if(current_proj < min_proj) // If current projection is smaller than the minimum one
        min_proj = current_proj;
    // Select maximum projection on axis
    if(current_proj > max_proj) // If current projection is greater than the minimum one
        max_proj = current_proj;
}

return (CVector2D(min_proj, max_proj)); // Return a vector2D as it is a handy way for returning a couple of values
}

Many thanks in advance to everyone and sorry for the messy code!


Solution

  • One thing leaps out at me (apart from the continuous passing of vectors by value rather than reference, which will be incredibly costly!)

    In you collision detection, you're seeing if the car hits each pedestrian

    for(int i = 0; i < ped_number; i++)
    {
        CheckCollisionRectVSPoint(car->shape, peds[i]->pos);
    }
    

    Then, in the collision detector, you're repeating a lot of the same processing on the car shape every time:-

    // Prepare the normals for both shapes
    std::vector<CVector2D> normals_a = test_a->GetNormalsAsArray();  
    
    // Create two containers for holding the various vectors used for collision check
    std::vector<CVector2D> vect_test_a = PrepVectors(test_a);
    

    .. etc...

    You should rework that loop to create the normals etc for the car just once, and then reuse the results for each check against a pedestrian.