官术网_书友最值得收藏!

Going the extra mile

A functional game is far from a fully finished product. Sure, we have everything we wanted in the beginning, but it still leaves things to be desired, such as keeping track of the score and showing how many lives we have. At first, your main instinct might be to just add a bit of text somewhere on the screen that simply prints the number of lives you have left. You may even be tempted to do as little as simply printing it out in the console window. If that's the case, the purpose of this part is to change your way of thinking by introducing something that we will be using and improving over the course of this book: the textbox.

If that name doesn't really mean anything to you, simply imagine a chat window on any given communication application, such as MSN Messenger or Skype. Whenever a new message is added, it's added to the bottom as the older messages are moved up. The window holds a certain number of messages that are visible at one time. That's not only useful for the purpose of the game printing a casual message, but can also be used for debugging. Let's start by writing our header, as usual:

using MessageContainer = std::vector<std::string>;

class Textbox{
public:
    Textbox();
    Textbox(int l_visible, int l_charSize, int l_width, sf::Vector2f l_screenPos);
    ~Textbox();

    void Setup(int l_visible, int l_charSize, int l_width, sf::Vector2f l_screenPos);
    void Add(std::string l_message);
    void Clear();

    void Render(sf::RenderWindow& l_wind);
private:
    MessageContainer m_messages;
    int m_numVisible;

    sf::RectangleShape m_backdrop;
    sf::Font m_font;
    sf::Text m_content;
};

We begin by defining the data type for the container of all the messages. In this case, we went with std::vector again, simply because that's the more familiar choice at this point. Just to make it look better and more readable, we've added a rectangle shape as one of the members of the class that will be used as a backdrop. On top of that, we have introduced a new data type: sf::Text. This is a drawable type that represents any typed characters or strings of characters, and can be adjusted in size, font, and color, as well as transformed, much like any other drawable in SFML.

Let's start implementing our fancy new feature:

Textbox::Textbox(){
    Setup(5,9,200,sf::Vector2f(0,0));
}

Textbox::Textbox(int l_visible, int l_charSize, 
int l_width, sf::Vector2f l_screenPos){
    Setup(l_visible, l_charSize, l_width, l_screenPos);
}

Textbox::~Textbox(){ Clear(); }

As you can see, it has two constructors, one of which can be used to initialize some default values and the other that allows customization by passing in some values as arguments. The first argument is the number of lines that are visible in the textbox. It is followed by the character size in pixels, the width of the entire textbox in pixels, and float vector that represents the position on the screen where it should be drawn at. All that these constructors do is invoke the Setup method and pass all these arguments to it, so let's take a look at it:

void Textbox::Setup(int l_visible, int l_charSize, int l_width, sf::Vector2f l_screenPos)
{
    m_numVisible = l_visible;

    sf::Vector2f l_offset(2.0f, 2.0f);

    m_font.loadFromFile("arial.ttf");
    m_content.setFont(m_font);
    m_content.setString("");
    m_content.setCharacterSize(l_charSize);
    m_content.setColor(sf::Color::White);
    m_content.setPosition(l_screenPos + l_offset);

    m_backdrop.setSize(sf::Vector2f(
        l_width, (l_visible * (l_charSize * 1.2f))));
    m_backdrop.setFillColor(sf::Color(90,90,90,90));
    m_backdrop.setPosition(l_screenPos);
}

Aside from initializing its member values, this method defines an offset float vector that will be used to space the text appropriately and provide some padding from the top-left corner. It also sets up our sf::Text member by first creating a font to which it's bound, setting the initial string to nothing, setting up the character size and color, and setting its position on the screen to the provided position argument with the proper offset factored in. Additionally, it sets up the size of the backdrop by using the width that was provided and multiplying the number of visible lines by the result of the multiplication of the character size and a constant floating point value of 1.2, in order to account for spacing between the lines.

Tip

From time to time, it does simply come down to playing with code to seeing what really works. Finding certain numeric constants that work in all cases is one of the situations where it's just a matter of testing in order to determine the correct value. Don't be afraid to try out new things and see what works.

Since we're utilizing a vector to store our messages, adding a new one or removing them all is as simple as using the push_back and clear methods:

void Textbox::Add(std::string l_message){
    m_messages.push_back(l_message);
    if(m_messages.size() < 6){ return; }
    m_messages.erase(m_messages.begin());
}

void Textbox::Clear(){ m_messages.clear(); }

In the case of adding a new message, checking whether we have more of them than we can see would be a good idea. Having something around that we're not going to see or need ever again is wasteful, so the very first message that is definitely out of sight at that time is removed from the message container.

We're very close to actually finishing this neat feature. The only thing left now is drawing it, which, as always, is taken care of by the Render method:

void Textbox::Render(sf::RenderWindow& l_wind){
  std::string l_content;

  for(auto &itr : m_messages){
    l_content.append(itr+"\n");
  }

  if(l_content != ""){
    m_content.setString(l_content);
    l_wind.draw(m_backdrop);
    l_wind.draw(m_content);
  }
}

The code begins with std::string being set up to hold all the visible messages on the screen. Afterwards, it's as simple as looping over the message vector and appending the text of each message to our local std::string variable with a new line symbol at the end. Lastly, after checking the local variable and making sure it isn't empty, we must set our m_content member of type sf::Text to hold the string we've been pushing our messages to and draw both the background and the text on the screen. That's all there is to the Textbox class.

After adding an instance of Textbox as a member to our game class, we can start setting it up:

Game::Game() ... {
...
    m_textbox.Setup(5,14,350,sf::Vector2f(225,0));
...
    m_textbox.Add("Seeded random number generator with: " + std::to_string(time(NULL)));
}

After passing some constant values to the Setup method of our m_textbox member, we immediately start using it right there in the constructor by actually outputting our first message. Let's finish integrating it fully by making one last adjustment to the Game::Render() method:

void Game::Render(){
    m_window.BeginDraw();
    // Render here.
    m_world.Render(*m_window.GetRenderWindow());
    m_snake.Render(*m_window.GetRenderWindow());
    m_textbox.Render(*m_window.GetRenderWindow());

    m_window.EndDraw();
}

It's the same as both the classes we've implemented before this, except that the text box is now the last thing we draw, which means it will be displayed over everything else. After adding more messages to the game to be printed and compiling our project, we should end up with something like this:

This text box, in its most basic form, is the last addition to our snake game that we will be covering in this book. Feel free to play around with it and see what else you can come up with to spice up the game!

主站蜘蛛池模板: 平阳县| 容城县| 盱眙县| 福鼎市| 成武县| 双柏县| 文昌市| 泽州县| 周至县| 达日县| 贵定县| 资阳市| 中宁县| 铜陵市| 东宁县| 龙山县| 北票市| 宁乡县| 杂多县| 四子王旗| 哈巴河县| 织金县| 正定县| 津市市| 苍溪县| 阿荣旗| 凉城县| 赤峰市| 永嘉县| 义乌市| 新乐市| 云南省| 崇仁县| 南陵县| 陵水| 微山县| 罗田县| 景洪市| 高青县| 浦江县| 渑池县|