Welcome to Code Forum!

Join a community that supports you and your coding journey from day one. We strive to be a friendly, supportive community that empowers everyone to be better developers. By registering with us, you'll be able to discuss, share and private message with other members of our community.

SignUp Now!
  • Tutorials listed in this section have not been reviewed nor are affliated by Code Forum. It is the sole responsbility of the authour to properly maintain and ensure information is accurate.

Unmaintained Introduction to coding - 9. Making the character attack

Making the character attack​

View attachment 615

Keeping track of whether a monster is alive or not​

A variable of type 'bool' can either equal 'true' or 'false'. Here, we define an array of 'bool' values indicating if a monster is dead or not:
C++:
bool monstersDead[4];
In the loop where we assign the texture to the sprites of the monsters, we also fill the array 'monstersDead' with the value 'false':
C++:
for(int c = 0; c < 4; c++)
{
    monsters[c].setTexture(monsterTexs[0]);
    monsters[c].setScale(3, 3);
    monstersDead[c] = false;
}
Now, we draw the monsters only when they are not dead:
C++:
for(int c = 0; c < 4; c++)
{
    if(monstersDead[c] == false)
        win.draw(monsters[c]);
}
When a monster is killed, we wait a few seconds before spawning it again. We define an array of objects of type sf::Clock to keep track of the number of seconds a monster has been dead:
C++:
sf::Clock monsterRespawnClocks[4];

Control structures with many conditions​

There may be more than one condition inside a control structure (Example: The structure 'if', 'while', 'for'...).

To test if two conditions are true, we must separate them with two ampersands &&.

The following code tests if 'var1' equals 7 AND 'var2' is bigger than 13. The instructions inside the braces are executed only if both conditions are true:
C++:
if(var1 == 7 && var2 > 13)
{
}
To test if at least one of two conditions are true, we must separate them with two vertical bars ||.

The following code tests if 'var1' is smaller than 66 AND/OR 'var2' equals 73. The instructions inside the braces are executed as long as at least one of the conditions is true:
C++:
if(var1 < 66 || var2 == 73)
{
}
We may assemble many conditions together:
C++:
if((var1 > 10 || var2 > 20) && (var1 < 100 || var2 < 160))
{
}
The following tests if 'var1' is equal to or bigger than 10:
C++:
if(var <= 10)
{
}

The attack animation​

When the character attacks, we temporarily change its texture. Let us load that texture:
C++:
sf::Texture playerAttackingTex;
playerAttackingTex.loadFromFile("resources/playerAttacking.png");
The player needs to wait 1 second between each attack. Let us define a clock to keep track of the time elapsed since the last hit:
C++:
sf::Clock playerAttackClock;
The function 'sf:🐭:isButtonPressed' returns true if the mouse button it receives in argument is pressed and false otherwise.
C++:
// We check if the left button of the mouse is currently pressed.
if(sf::Mouse::isButtonPressed(sf::Mouse::Left))
{
}
In the game loop, we check if either the 'space' key or the left button of the mouse is pressed. If so, we look if the time elapsed since the last attack is higher than 1 second. If both conditions are true, we make the character attack.
C++:
if((sf::Keyboard::isKeyPressed(sf::Keyboard::Space) || sf::Mouse::isButtonPressed(sf::Mouse::Left)) && playerAttackClock.getElapsedTime().asMilliseconds() > 1000)
{
}
Inside the braces of the 'if' structure above, we restart the attack clock of the player:
C++:
playerAttackClock.restart();
Then, we change the texture of the character for the one where he attack:
C++:
player.setTexture(playerAttackingTex);
We define a sf::FloatRect, located right in front of the player, with one-fourth of its width and one-third of its height.
C++:
sf::FloatRect attackRect(player.getPosition()+sf::Vector2f((player.getGlobalBounds().width/4), -player.getGlobalBounds().height/3), sf::Vector2f(player.getGlobalBounds().width/4, player.getGlobalBounds().height/3));
If a monster is in collision with that sf::FloatRect, we consider that the attack touched it:
C++:
for(int c=0; c < 4; c++)
{
    // If the monster is not dead and the attack touched it.
    if(monstersDead[c] == false && attackRect.intersects(monsters[c].getGlobalBounds()))
    {
        // The monster is hit by the attack, so he dies.
        monstersDead[c] = true;
        // To know when to respawn it, we keep track of the time elapsed since the monster died.
        monsterRespawnClocks[c].restart();
    }
}
Outside the 'if' structure from above, we check if the time elapsed since the last attack is of at least 1 second. If so, we set back the normal texture to the character.
C++:
if(playerAttackClock.getElapsedTime().asMilliseconds() > 1000)
{
    player.setTexture(playerTex);
}

Spawning back the monsters that died​

In the game loop, for each monster, we check if the time elapsed since its death is of at least 8 seconds. If so, set it back to life.
C++:
for(int c=0; c < 4; c++)
{
    if(monsterRespawnClocks[c].getElapsedTime().asSeconds() >= 8)
        monstersDead[c] = false;
}

The code of the game so far​

C++:
#include <SFML/Graphics.hpp>

int main()
{
    sf::RenderWindow win(sf::VideoMode(800, 600), "Introduction to coding", sf::Style::Close);

    // Grass

    sf::Texture grassTex;
    grassTex.loadFromFile("resources/grass.png");
    grassTex.setRepeated(true);

    sf::Sprite grass;
    grass.setTexture(grassTex);
    grass.setScale(5, 5);
    grass.setTextureRect(sf::IntRect(0,0,160,120));

    // Dirt tiles

    sf::Texture dirtTex;
    dirtTex.loadFromFile("resources/dirt.png");

    sf::Sprite dirtTiles[10];
    dirtTiles[0].setPosition(300, 60);
    dirtTiles[1].setPosition(660, 300);
    dirtTiles[2].setPosition(390, 480);
    dirtTiles[3].setPosition(660, 60);
    dirtTiles[4].setPosition(720, 420);
    dirtTiles[5].setPosition(180, 300);
    dirtTiles[6].setPosition(60, 180);
    dirtTiles[7].setPosition(60, 420);
    dirtTiles[8].setPosition(240, 420);
    dirtTiles[9].setPosition(480, 120);

    for(int c = 0; c < 10; c++)
    {
        dirtTiles[c].setTexture(dirtTex);
        dirtTiles[c].setScale(5, 5);
    }

    // Trees

    sf::Texture treeTex;
    treeTex.loadFromFile("resources/tree.png");

    sf::Sprite trees[8];
    trees[0].setPosition(50, 60);
    trees[1].setPosition(600, 150);
    trees[2].setPosition(630, 456);
    trees[3].setPosition(300, 600);
    trees[4].setPosition(720, 40);
    trees[5].setPosition(460, 500);
    trees[6].setPosition(20, 370);
    trees[7].setPosition(340, 240);

    for(int c = 0; c < 8; c++)
    {
        trees[c].setTexture(treeTex);
        trees[c].setScale(3, 3);
    }

    // Stones

    sf::Texture stoneTex;
    stoneTex.loadFromFile("resources/stone.png");

    sf::Sprite stones[3];
    stones[0].setPosition(304, 64);
    stones[1].setPosition(664, 304);
    stones[2].setPosition(180, 300);

    for(int c = 0; c < 3; c++)
    {
        stones[c].setTexture(stoneTex);
        stones[c].setScale(3, 3);
    }

    // Player

    sf::Texture playerTex;
    playerTex.loadFromFile("resources/player.png");
    sf::Texture playerAttackingTex;
    playerAttackingTex.loadFromFile("resources/playerAttacking.png");

    sf::Sprite player;
    player.setTexture(playerTex);
    player.setPosition(400, 300);
    player.setScale(3, 3);

    float playerMovementSpeed = 200.0;
    sf::Clock playerMovementClock;
    sf::Clock playerAttackClock;

    // Monsters

    sf::Texture monsterTexs[3];
    monsterTexs[0].loadFromFile("resources/monster1.png");
    monsterTexs[1].loadFromFile("resources/monster2.png");
    monsterTexs[2].loadFromFile("resources/monster3.png");

    sf::Sprite monsters[4];
    monsters[0].setPosition(500, 40);
    monsters[1].setPosition(100, 170);
    monsters[2].setPosition(400, 450);
    monsters[3].setPosition(690, 160);

    bool monstersDead[4];

    for(int c = 0; c < 4; c++)
    {
        monsters[c].setTexture(monsterTexs[0]);
        monsters[c].setScale(3, 3);
        monstersDead[c] = false;
    }

    sf::Clock monstersAnimationClock;
    sf::Clock monsterRespawnClocks[4];

    // Game loop

    sf::Event event;

    while(win.isOpen())
    {
        while(win.pollEvent(event))
        {
            if(event.type == sf::Event::Closed)
                win.close();
        }

        // Player movements

        sf::Vector2f trans;
        float mouv = playerMovementClock.getElapsedTime().asSeconds() * playerMovementSpeed;

        if(sf:⌨️:isKeyPressed(sf:⌨️:W))
            trans.y -= mouv;
        if(sf:⌨️:isKeyPressed(sf:⌨️:S))
            trans.y += mouv;
        if(sf:⌨️:isKeyPressed(sf:⌨️:A))
            trans.x -= mouv;
        if(sf:⌨️:isKeyPressed(sf:⌨️:D))
            trans.x += mouv;

        player.setPosition(player.getPosition().x+trans.x, player.getPosition().y+trans.y);
        playerMovementClock.restart();

        // Collision testing

        for(int c=0; c < 8; c++)
        {
            if(player.getGlobalBounds().intersects(trees[c].getGlobalBounds()))
                player.setPosition(player.getPosition().x-trans.x, player.getPosition().y-trans.y);
        }
        for(int c=0; c < 3; c++)
        {
            if(player.getGlobalBounds().intersects(stones[c].getGlobalBounds()))
                player.setPosition(player.getPosition().x-trans.x, player.getPosition().y-trans.y);
        }

        // Animation of the monsters

        int index = monstersAnimationClock.getElapsedTime().asMilliseconds() / 333;

        if(index > 2)
        {
            monstersAnimationClock.restart();
            index = 0;
        }

        for(int c=0; c < 4; c++)
        {
            monsters[c].setTexture(monsterTexs[index]);
        }

        // Player attacking

        if((sf:⌨️:isKeyPressed(sf:⌨️:Space) || sf:🐭:isButtonPressed(sf:🐭:Left)) && playerAttackClock.getElapsedTime().asMilliseconds() > 1000)
        {
            playerAttackClock.restart();
            player.setTexture(playerAttackingTex);

            // Testing if the attack reaches a monster

            sf::FloatRect attackRect(player.getPosition()+sf::Vector2f((player.getGlobalBounds().width/4), -player.getGlobalBounds().height/3), sf::Vector2f(player.getGlobalBounds().width/4, player.getGlobalBounds().height/3));

            for(int c=0; c < 4; c++)
            {
                if(monstersDead[c] == false && attackRect.intersects(monsters[c].getGlobalBounds()))
                {
                    monstersDead[c] = true;
                    monsterRespawnClocks[c].restart();
                }
            }
        }

        if(playerAttackClock.getElapsedTime().asMilliseconds() > 1000)
        {
            player.setTexture(playerTex);
        }

        // Respawn of the monsters

        for(int c=0; c < 4; c++)
        {
            if(monsterRespawnClocks[c].getElapsedTime().asSeconds() >= 8)
                monstersDead[c] = false;
        }

        // Rendering

        win.clear();

        win.draw(grass);

        for(int c = 0; c < 10; c++)
            win.draw(dirtTiles[c]);

        for(int c = 0; c < 8; c++)
            win.draw(trees[c]);

        for(int c = 0; c < 3; c++)
            win.draw(stones[c]);

        win.draw(player);

        for(int c = 0; c < 4; c++)
        {
            if(monstersDead[c] == false)
                win.draw(monsters[c]);
        }

        win.display();
    }

    return 0;
}
 
Last edited by a moderator:
Back
Top Bottom