SFML community forums

Help => Graphics => Topic started by: ineed.help on May 07, 2014, 07:55:56 pm

Title: Making "game text", et cetera
Post by: ineed.help on May 07, 2014, 07:55:56 pm
SOLVED
Title: Re: Making "game text", et cetera
Post by: ChronicRat on May 07, 2014, 08:18:49 pm
I think will be better, while sf::Text does not support mark up, to create class which parses source string and holds array of sf::Text objects. Create this class derived from sf::Drawable and sf::Transformable, implement draw and parse (init) function. You'll need just to apply own matrix  to RenderStates in draw function. In your update(float dt) function you can implement any additional effects for show.
Title: Re: Making "game text", et cetera
Post by: Ixrec on May 07, 2014, 09:59:07 pm
I actually had very similar needs for my program, and I basically made my own textbox class using sf::Font and sf::Glyph.  Using large numbers of sf::Text objects feels like an ugly hack to me considering how easy sf::Font is to use.  The tutorial had almost everything I needed.  By far the biggest headache was computing where to insert line breaks for word wrapping.

My class parses sequences like {red} and {/} when a string is assigned to it, and internally it stores the string of "real" characters along with a bunch of metadata about when colors and fonts and other things change.

I also gave my class an internal sf::Clock and a draw() method which will figure out how many characters it needs to draw this frame based on the elapsed time so far.  In your case it may be sufficient to have a drawNLetters() method instead of having it keep time internally.
Title: Re: Making "game text", et cetera
Post by: Ixrec on May 08, 2014, 07:57:27 am
No, but my current code is quite terrible and non-generic, and I don't have the time to refactor it into something acceptable. =(  Besides, you probably don't have exactly the same needs I do, so even the parts of my design that are good may not apply.  The parts that probably do apply to you I already mentioned in my previous post.  And, the tutorials/documentation on sf::Font have literally everything you need for this, so there's no need to describe implementation details until you get stuck on something specific.  I'm fine with posting certain parts of my code if you really need help with a particular detail.
Title: Re: Making "game text", et cetera
Post by: ChronicRat on May 08, 2014, 08:33:35 am
... What?
Sorry, i'm using google translate often. =)
Title: Re: Making "game text", et cetera
Post by: Ixrec on May 08, 2014, 10:02:54 pm
Well, what part don't you understand?  Finding where to insert line breaks?  Pre-processing the string into a "real" string plus metadata?  How to draw a variable number of characters each frame?
Title: Re: Making "game text", et cetera
Post by: Ixrec on May 09, 2014, 09:54:13 pm
Okay, here's some pseudocode to demonstrate what my function does:

class Textbox {
        std::vector<sf::Sprite> drawable_characters;
        sf::Font font;
public:
        setString(const std::string& input);
        draw(sf::RenderTarget& target); // you figure out this part
}

void Textbox::setString(const std::string& input) {
        std::size_t n = 0;
        unsigned int fontSize = 12;
        bool bold = false;
        sf::Vector2i position = /* some initial position */;
        sf::Color color = sf::Color(255, 255, 255);
        sf::Glyph glyph;
        char last_literal = -1;
       
        drawable_characters.clear();

        while(n <= input.length()) {
       
                // process escape sequences
                while(input[n] == '\\') {
                        n++;
                        switch(input[n]) {
                                case 'n':
                                        // insert a linebreak
                                        position.y += font.getLineSpacing(fontSize);
                                        position.x = /* x of initial position */;
                                        last_literal = -1;
                                        n++;
                                        break;
                                case '{':
                                        if( /* the following characters are "red}" */ ) {
                                                color = sf::Color(255, 0, 0);
                                                n += 4;
                                        } else if( ... ) {
                                                // more colors!
                                        } else if( /* the next character is '}' */ ) {
                                                color = sf::Color(255, 255, 255);
                                                n++;
                                        }
                                        break;
                                default:
                                        // throw an error?
                                        break;
                        }
                }
               
                // done processing any escape sequences, so now we're on a literal character
               
                char current_literal = input[n];
               
                glyph = font.getGlyph(current_literal, fontSize, bold);
               
                drawable_characters.push_back( sf::Sprite(font, glyph.getTextureRect()) );
               
                drawable_characters.back().setColor(color);

                // skip kerning if we're at the start of the current line
                if(last_literal != -1)
                        position.x += font.getKerning(last_literal, current_literal, fontSize);
               
                drawable_characters.back().setPosition(position.x, position.y + glyph.bounds.top);
               
                position.x += glyph.advance;
               
                last_literal = current_literal;
                n++;
        }
}
 

I should stress that this is something I scribbled down just now based on my real code, so while it should include all the important steps, there's almost certainly some bugs and missing pieces (beyond the obvious ones).  And of course I left out word wrapping.  But still, this should be enough for you to get your head around what's required and figure out the rest on your own.

In particular, keep an eye out for premature destruction errors.  Remember things like the "white square problem" from the Texture tutorial?  Similar things can happen when working with fonts, except for some reason they give you invisible squares instead of white squares so it's much less obvious that it's your fault.


P.S. If you're feeling particularly masochistic, here's what this function looks like in my real program (http://pastebin.com/hkcMLNNX).
Title: Re: Making "game text", et cetera
Post by: Ixrec on May 10, 2014, 12:40:02 pm
(which had some errors, by the way - <sf::Sprite> doesn't take <sf::Font> as a parameter, but never mind)

I think I should've written "font.getTexture()" instead of just "font" on that line.  My current code is so bad I got fonts and textures confused =(

Quote
What I'm wondering is how much of the PC's resources this will take up in comparison to your method.

Of course a real answer can only come from lots of benchmarking experiments, but my gut feeling would be it's probably fine because sf::Text probably does internally more or less the same thing my class was doing.

After looking at the sf::Text source code (which you should look at too, since we're both just making more complex versions of it (https://github.com/SFML/SFML/blob/master/src/SFML/Graphics/Text.cpp)), it does appear to do something similar, except it uses a vertex array instead of a vector of sprites.  So, if my understanding of sprites and textures and whatnot is correct, your use of a vector of Texts will probably end up doing about the same amount of work as my method, since each one uses a separate draw() call for each character.  However my code can be more easily adapted to use a vertex array and one draw() call at some later date, since I'm already working with the glyphs directly (in fact I feel kind of stupid for not doing that in the first place...but this was literally the first thing I ever wrote in SFML).  Your needs are clearly far simpler than mine were so you may not ever care about this optimization.
Title: Re: Making "game text", et cetera
Post by: Ixrec on May 10, 2014, 02:03:54 pm
Admittedly all of these versions are probably good enough.  It's unlikely you'll be using so much text or updating so frequently that any real problems emerge.

You should definitely make an effort to learn all of C++ before getting too much farther into SFML.  The language and most software written in it kind of assume you know what you're doing, and most of its advantages quickly turn into disadvantages if you don't understand it.  The SFML source code is exceptionally easy to read for a C++ library, so you should definitely aim to learn C++ well enough to read that at least.

Admittedly, in modern C++, you should almost never use owning raw pointers anywhere, and ideally not many owning pointers period, so not understanding pointers isn't that big of a deal.  Learning C or assembly language instead will quickly make it obvious why pointers have to exist on some level.
Title: Re: Making "game text", et cetera
Post by: Ixrec on May 10, 2014, 02:21:58 pm
What do you mean by "all of these versions"?

I meant your textbox class, my textbox class, and sf::Text.

Quote
Learning ALL of C++? There are A LOT of things to learn about C++. A LOT.

I don't mean memorizing every word of the standard, but you do need to learn all the major features and most popular idioms.

At a bare minimum, you should know everything discussed in these tutorials: http://www.cplusplus.com/doc/tutorial/

And knowing what the standard library classes already provide is very important.  For example, you could use std::string::substr instead of checking each character separately.

Quote
So, I need some help ...

This is the part where you need to learn how to use a debugger.  A subscript error is exactly the sort of thing you should be able to find easily on your own with the right tools.

My guess would be that you aren't always changing the value of index correctly when processing escape sequences.



I should also mention there are some major design issues with processing the text that way.  In particular, you'll never be able to support multiple escape sequences intermingling with that design.  It's better to have a stateful loop with some flags like "current_color" that you change when you encounter an escape sequence, then have exactly one block of code to handle a literal character, regardless of what escape sequences are currently in effect.  At the moment you're duplicating that code in two places.
Title: Re: Making "game text", et cetera
Post by: Ixrec on May 11, 2014, 11:25:44 am
In general, "stateful" methods are ones that depend on the current "state" of an object, typically represented by its member variables.  This is as opposed to "stateless" methods that depend on nothing but their arguments, and will always give the same answer every time.

For example, sf::Window::getSize() is stateful, because the current window size is part of the class' state.  A function like sin() or cos() is stateless, because their results depend on nothing but the arguments you give them.

What I meant by a "stateful loop" was something like this:
// here's the "state"
sf::Color current_color = // white;

for( ... ) {
    if( /* the next chars are "{red}" */ ) {
        current_color = // red;
    } else if ( /* the next chars are "{}" */ ) {
        current_color = // white;
    }

    // make sprite using the literal char
    sprite.setColor(current_color);
}
 

For the purpose of pre-processing escape sequences in text, this design is better, because it is almost trivial to add more escape sequences later and allow escape sequences within other escape sequences, like this:

// here's the "state"
sf::Color current_color = // white;
unsigned int current_size = 12;
bool current_bold = false;

for( ... ) {
    if( /* the next chars are "{red}" */ ) {
        current_color = // red;
    } else if( /* the next chars are "{bold}" */ ) {
        current_bold = true;
    } else if( /* the next chars are "{size:n}" */ ) {
        current_size = n;
    } else if ...

    // make sprite using the literal char
    ... = font.getGlyph(font, current_size, current_bold);
    // other stuff to make sprite
    sprite.setColor(current_color);
}
 
Title: Re: Making "game text", et cetera
Post by: Ixrec on May 11, 2014, 04:09:37 pm
Yeah that's more like it.

Though you should still use substr() for your substring matching; it's much easier than checking individual characters.

For that error, just use a debugger.  You'll have to learn how to use one at some point, and subscript errors are exactly the sort of thing debuggers are perfect for pinning down.

int yPos = (int)textBox.top; /* RIGHT? */
1) Avoid C-style casts.  Learn the C++-style ones (static_cast, dynamic_cast, reinterpret_cast, const_cast).
2) textBox is a very bad name for a member of a class called TextBox.  Call it bounds or boundingBox or something more descriptive.
3) The actual logic of this statement seems fine.  If the text appears where you want it to, then it is fine.
Title: Re: Making "game text", et cetera
Post by: zsbzsb on May 11, 2014, 04:12:07 pm
Quote
The error should be in "TextBox.cpp", I would assume...

Don't ever assume, and don't come here asking us to find errors in your code when you can easily learn to use a debugger. I'm not saying this to be rude, but if you can't solve a simple problem like you won't ever make it very far as a programmer.

characterMargin = text[0].lineSpacing;
Title: Re: Making "game text", et cetera
Post by: Ixrec on May 11, 2014, 05:58:12 pm
Then the priority right now is learning how to use the debugger properly.  Set some breakpoints, step through lines of code, check the values of various variables to make sure they are what you think they should be at each step (directly with the debugger, not using cout), and when you finally hit the error it should be obvious why it happened.
Title: Re: Making "game text", et cetera
Post by: Ixrec on May 11, 2014, 07:00:37 pm
A vector can grow indefinitely (given infinite memory), but that's only when you explicitly ask it to grow using methods like push_back() or insert().  The vector's size at any given moment is always finite.  If the vector's current size is 5 and you ask for element 10, that's blatantly an error in your code, and the vector isn't going to try and hide that from you by returning a default value or some other useless thing.

Anyway, after you go relearn how vectors work, you still need to use your debugger properly.  The way you asked this question implies you haven't even found out whether you're asking for element 10 in a size 5 vector or element 90 in a size 3 vector when this error happens.  You need to start checking values like this if you ever want to narrow down the real problem.

Please at least go google some tutorials on debugging.  This is far more important than simply fixing your textbox class; as zsbzsb said you cannot get very far as a programmer if you don't learn to debug properly.


P.S. For the record, I think I see what the error is, but I'm going to make you find it yourself.  Gaining basic debugging skills is far more important than fixing this one bug.
Title: Re: Making "game text", et cetera
Post by: zsbzsb on May 11, 2014, 07:22:37 pm
And to zsbzsb: if you don't want to be rude, saying "you won't ever make it very far as a programmer"......

Thanks for taking what I said out of context, but Ixrec got it correct.

Quote
.....you cannot get very far as a programmer if you don't learn to debug properly.

Also on that note, Ixrec is onto it about what your problem is (I even posted a line at the end of my previous post). Vectors and most stl containers will not automatically insert elements if you ask for one that does not exist.
Title: Re: Making "game text", et cetera
Post by: Ixrec on May 11, 2014, 08:21:10 pm
Could you show us what the actual string is that it's supposedly wrong about?  Checking what the value of text is at the time that loop starts should be trivial.

Just a blind guess, but it almost sounds like you might be getting confused by zero-indexing.  Namely, if the string is 39 characters long then size() returns 39 and the valid indices are 0 through 38, not 1 through 39.