Worms Post 10: The Final Stretch

Update

The last couple weeks have been a bit crazy with all my other modules needing to be handed in as well as the covid-19 pandemic throwing things into chaos.

This has meant that I’ve been working on some other projects for other modules and the stuff I have been doing for GEP has been rather dull documentation.

However with that said I have been working on something that’s GEP and interesting, which was level resetting!

The Idea

So now that we have the game resetting when everyone is dead I needed to set up the game so that the terrain reset as it should.

In theory this should be quite simple as all we need to do is make a new instance of the terrain data and render target and we should have everything reset. The problem however is that other things need our terrain in order to work properly.

The Implementation

So firstly the function GameReset exists, it’s a simple function meant to reset the turn handler and update the game state etc. but it’s here we want to update our level. So I made a function called reset level which was meant to just reset the terrain and render target. Instead we reset a bit more.

Here is what the GameReset function looks like:

void Game::GameReset()
{
    m_resetTimer = 0;
    m_GD->timeafterProjectileExplosion = 0;

    m_turnHandler->ResetHandler();
    changeTurn();
    m_GD->m_GS = GS_MENU;

    ResetLevel();

}

And in the end here is what the reset level function looks like:

void Game::ResetLevel()
{
    ClearAndDeleteListsForRestart();
    CreateHUD();
    CreateCamAndTerrain();
    CreateStartingWorms();
}

This function was broken up into chunks because it ended up being so big. The issue started with the terrain. Deleting it meant that the zordering was incorrect so other bits needed to be remade to keep the correct ordering. Then the issue was that the Worms had problems because the collision handler needed a pointer to the new terrain but that caused more issues because the HUD had pointers to the worms. In the end most things in the init function ended up in the reset level function. So after some refactoring this is what we ended up with:

ClearAndDeleteListsForRestart()

This function was responsible for deleting any data that we want to reset later, but also for clearing the gameobject 2d and hud list as well as the handler list.

void Game::ClearAndDeleteListsForRestart()
{
    m_GameObjects2D.clear();
    m_GameObjectsHUD.clear();
    handlers.clear();

    if (crosshair)
    {
        delete crosshair;
        crosshair = nullptr;
    }

    if (m_weaponHUD)
    {
        delete m_weaponHUD;
        m_weaponHUD = nullptr;
    }

    if (cursor)
    {
        delete cursor;
        cursor = nullptr;
    }

    if (m_terrain)
    {
        delete m_terrain;
        m_terrain = nullptr;
    }

    if (m_terrainData)
    {
        delete m_terrainData;
        m_terrainData = nullptr;
    }

    for (auto& row : worms)
    {
        for (auto& element : row)
        {
            if (element != nullptr)
            {
                delete element;
                element = nullptr;
            }
        }
    }
}

CreateHUD()

The create hud function was responsible for making/re-making the UI for the game.

void Game::CreateHUD()
{
    TurnTimer* m_turnTimer = new TurnTimer();
    m_GameObjectsHUD.push_back(m_turnTimer);

    WormHUD* m_wormHUD = new WormHUD();
    m_wormHUD->SetPos(Vector2(m_outputWidth - 20, m_outputHeight - 50));
    m_GameObjectsHUD.push_back(m_wormHUD);

    m_weaponHUD = new WeaponHUD("weapon_sprites", m_d3dDevice.Get(), m_outputWidth, m_outputHeight);
    m_weaponHUD->SetPos(Vector2(m_outputWidth - 100, m_outputHeight - 200));
    m_GameObjectsHUD.push_back(m_weaponHUD);

    cursor = new Cursor("cursor", m_d3dDevice.Get(), Vector2{ m_outputWidth * 1.0f, m_outputHeight * 1.0f });
    cursor->SetPos(500.0f * Vector2::One);
    cursor->SetMouseSensitivity(1.5f);
    m_GameObjectsHUD.push_back(cursor);

    TurnMessage* m_turnMessage = new TurnMessage();
    m_turnMessage->SetPos(Vector2(m_outputWidth * 0.5f, 100));
    m_GameObjectsHUD.push_back(m_turnMessage);

    DeathMessage* m_deathMessage = new DeathMessage();
    m_deathMessage->SetPos(Vector2(m_outputWidth * 0.5f, 100));
    m_GameObjectsHUD.push_back(m_deathMessage);


    MenuScreen* m_menuScreen = new MenuScreen("menu_screen", m_d3dDevice.Get(), { m_outputWidth * 1.0f, m_outputHeight * 1.0f });
    m_menuScreen->SetPos(Vector2(m_outputWidth * 0.5f, m_outputHeight * 0.5f));
    m_GameObjectsHUD.push_back(m_menuScreen);

    TurnArrow* arrow = new TurnArrow("turn_arrow", m_d3dDevice.Get());
    m_GameObjectsHUD.push_back(arrow);

    PowerBar* m_powerBar = new PowerBar("power_bar", m_d3dDevice.Get());
    m_GameObjectsHUD.push_back(m_powerBar);

    crosshair = new Crosshair("crosshair", m_d3dDevice.Get());
    m_GameObjectsHUD.push_back(crosshair);
}

CreateCamAndTerrain()

The create cam and terrain function was responsible for making/re-making (you guessed it) terrain, render target and camera.

void Game::CreateCamAndTerrain()
{
    m_GameObjects2D.push_back(camera2D);

    m_terrainData = new Terrain2D("testTerrain", m_d3dDevice.Get(), m_debugLogger, m_states);
    m_terrainData->addStampToTerrain("testStamp", m_d3dDevice.Get(), Vector2(500, 330)); // terrain stamp test!

    m_terrain = new RenderTarget(m_d3dDevice.Get(), m_terrainData->getTerrainWidth(), m_terrainData->getTerrainHeight());
    m_terrainData->getDeformationData()->updateBlendstate(m_terrain->GetDigBlend()); //Grabs blend state from Terrain into TerrainData 

    m_debugLogger->writeLine(DebugLogger::TERRAIN, DebugLogger::MESSAGE,
        ("Loaded RT with width of: " + to_string(m_terrainData->getTerrainWidth()) + " and height of: " + to_string(m_terrainData->getTerrainHeight()) + "..."));

    m_terrainData->addDeformationToTerrain("CircleBrush", m_d3dDevice.Get(), Vector2(100, 430), Vector2(-6, -7)); // terrain deformation tests!
    m_terrainData->addDeformationToTerrain("CircleBrush", m_d3dDevice.Get(), Vector2(570, 410), Vector2(-6, -7));
    m_terrainData->addDeformationToTerrain("CircleBrush", m_d3dDevice.Get(), Vector2(540, 410), Vector2(-6, -7));
    m_terrainData->addDeformationToTerrain("CircleBrushOdd", m_d3dDevice.Get(), Vector2(240, 480), Vector2(-6, -7));

    camera2D->SetBoundingSize(Vector2(m_terrainData->getTerrainWidth(), m_terrainData->getTerrainHeight()));

    BackgroundSprite* mountains = new BackgroundSprite("dark_mountains", m_d3dDevice.Get(), 0.5f);
    mountains->SetPos(200.0f * Vector2::One);
    mountains->SetScale(1);
    m_GameObjects2D.push_back(mountains);

    m_GameObjects2D.push_back(m_terrainData->getTerrainBackground()); //Background
}

CreateStartingWorms()

The create starting worms function created/re-created all the worms for the game along with physics and collision handlers.

void Game::CreateStartingWorms()
{
    collisionHandler = CollisionHandler::Instance(m_terrainData);
    physicsHandler = PhysicsHandler::Instance();

    collisionHandler->deleteAllComponents();
    physicsHandler->deleteAllComponents();

    handlers.push_back(collisionHandler);
    handlers.push_back(physicsHandler);

    for (int t = 0; t < 4; t++)
    {
        for (int w = 0; w < 4; w++)
        {
            worms[t][w] = new Worm("worm_sprites_pixelated", m_d3dDevice.Get());
            worms[t][w]->GetStats()->SetTeam(t);
            //worms[t][w]->SetPos({ 200 + (150.0f * t) + (600.0f * w), 0 });
            worms[t][w]->SetPos({ 300 + (75 * t) + (600.0f * w), 0 });
            worms[t][w]->setPhysicsComponent(physicsHandler, false, true, 1, 1, 0, 300);
            worms[t][w]->setCollisionComonent({ 48, 48 }, true, false, 10, 0.2);
            collisionHandler->addComponent(worms[t][w]->getCollisionComp());
            // m_GameObjects2D.push_back(worms[t][w]);

            DamageText* m_damageText = new DamageText(worms[t][w]);
            m_damageText->SetScale(0.75f);
            m_GameObjectsHUD.push_back(m_damageText);

            WormText* text = new WormText(worms[t][w]);
            text->SetScale(0.75f);
            m_GameObjectsHUD.push_back(text);

            m_GameObjects2D.push_back(worms[t][w]);
        }
    }

    worms[0][0]->GetStats()->SetName("Arnold");
    worms[0][1]->GetStats()->SetName("Barry");
    worms[0][2]->GetStats()->SetName("Colin");
    worms[1][0]->GetStats()->SetName("Dave");
    worms[1][1]->GetStats()->SetName("Eric");
    worms[1][2]->GetStats()->SetName("Fingers");
    worms[2][0]->GetStats()->SetName("Garry");
    worms[2][1]->GetStats()->SetName("Harry");
    worms[2][2]->GetStats()->SetName("Isaac");
    worms[3][0]->GetStats()->SetName("Jerry");
    worms[3][1]->GetStats()->SetName("Kevin");
    worms[3][2]->GetStats()->SetName("Lenny");

    worms[3][3]->KillWorm(true);
    worms[2][3]->KillWorm(true);
    worms[0][3]->KillWorm(true);
    worms[1][3]->KillWorm(true);
    worms[3][2]->KillWorm(true);
    worms[2][2]->KillWorm(true);
    worms[0][2]->KillWorm(true);
    worms[1][2]->KillWorm(true);
    worms[3][1]->KillWorm(true);
    worms[2][1]->KillWorm(true);
    worms[0][1]->KillWorm(true);
    worms[1][1]->KillWorm(true);
    worms[2][0]->KillWorm(true);
    worms[1][0]->KillWorm(true);

    collisionHandler->updateTerrain(m_terrainData);

    m_GD->m_currentTurn = m_turnHandler->NewTurn(worms);
    camera2D->setCameraTarget(worms[0][0]);
}

Side-note, The reason it sets so many worms to be dead in this code extract is because I needed to test if it worked when the game reset so I only wanted two worms to fight with.

Summery

Now that the level resets as it should we have a proper game loop! we call the reset level function during the init function as well as when resetting which makes the game nice and neat. I was a bit unhappy with this until I broke it up and neatened things up a bit but now i’m quite happy with how it turned out.