- SFML Game Development By Example
- Raimondas Pupius
- 1145字
- 2021-07-23 14:55:09
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!
- Java系統分析與架構設計
- 數據結構(Java語言描述)
- C語言程序設計學習指導與習題解答
- ASP.NET程序設計教程
- Learning Hunk
- Java網絡編程核心技術詳解(視頻微課版)
- Swift 4 Protocol-Oriented Programming(Third Edition)
- Kivy Cookbook
- Android技術內幕(系統卷)
- Java程序設計
- Dart:Scalable Application Development
- TypeScript High Performance
- CISSP in 21 Days(Second Edition)
- MATLAB從入門到精通
- INSTANT Apache Maven Starter