Welcome, Guest. Please login or register. Did you miss your activation email?

Author Topic: [Updated Code] Looking for Technical Critique (Partial Face Example SFML)  (Read 10021 times)

0 Members and 1 Guest are viewing this topic.

io

  • Jr. Member
  • **
  • Posts: 52
  • z/OS by day, SFML by night
    • View Profile
[updated code, scroll down or click] http://en.sfml-dev.org/forums/index.php?topic=10363.msg72710#msg72710


Hi everyone.  I just started with SFML -- this is really my first project that involves graphics at all, mainframe coder here :P (nice green COBOL & Assembler).   

Right now I have moving eyes and blinking eyes.  Eventually, I want to move this to a more OO approach (face object contains eye objects, nose object etc) and an OO management of images.

Other than the lack of OO/image management class is there anything glaringly obvious that I'm doing wrong w/ SFML (like loading images into memory a million times or just doing something funny).  Tear it apart please, I'd like to find the areas I need to read up on more/study more =). 

Just a quick video so you have an idea of what it does so far... sorry for low frame rate -- cheap recording software.  "Action" starts at ~4sec



I've attached the project source code -- not too many lines ~450 (which includes counting whitespace).  I figure would be easier to grab that then have  wall of sourcecode on the forum ;].


I think the area I'm most uncomfortable with is the way I handled the blink animation.

main.cpp fragment
    sf::RenderWindow window(sf::VideoMode(1024, 768), "SFML works!");
    window.setFramerateLimit(30);
 

main.cpp fragment
    /**************************************************************************/
    /*                         LOAD EYEBALL PART IMAGES                       */
    /**************************************************************************/
    if( !LeftEye.loadEyeLid("img/eyes/left/eyeball_lid.png") )
        return -1;
    if( !LeftEye.loadEyeLid("img/eyes/left/eyeball_lid_1.png") )
        return -1;
    if( !LeftEye.loadEyeLid("img/eyes/left/eyeball_lid_2.png") )
        return -1;
    if( !LeftEye.loadEyeLid("img/eyes/left/eyeball_lid_3.png") )
        return -1;
    LeftEye.finalizeLid();


    if( !LeftEye.loadEyeColor("img/eyes/left/eyeball_color.png") )
        return -1;
    if( !LeftEye.loadEyeWhite("img/eyes/left/eyeball_white.png") )
        return -1;
 


eye.cpp fragment
void Eye::drawEye()
{
    eye_bound.clear();
    eye_bound.draw(sprite_eye_white);
    eye_bound.draw(sprite_eye_color);

    if( !blinking ) // grab top eye lid
    {
        frame_counter = 0;
        sprite_eye_lid.setTexture(texture_eye_lid.front());
        eye_bound.draw(sprite_eye_lid);
    }
    else
    {
        frame_counter++;

        if( frame_counter > 15 )
            frame_counter = 0;

        if( frame_counter == 3 )
            sprite_eye_lid.setTexture(texture_eye_lid.at(1));
        else if( frame_counter == 6 )
            sprite_eye_lid.setTexture(texture_eye_lid.at(2));
        else if( frame_counter == 9 )
            sprite_eye_lid.setTexture(texture_eye_lid.at(3));
        else if( frame_counter == 12 )
            sprite_eye_lid.setTexture(texture_eye_lid.at(2));
        else if( frame_counter == 15 )
            sprite_eye_lid.setTexture(texture_eye_lid.at(1));
        else if( frame_counter == 0 )
        {
            sprite_eye_lid.setTexture(texture_eye_lid.at(0));
            blinking = false;
        }

        eye_bound.draw(sprite_eye_lid);
    }

    eye_bound.display();

    rendered_eye.setTexture(eye_bound.getTexture());
}
 

[attachment deleted by admin]
« Last Edit: February 08, 2013, 06:35:42 am by io »

eXpl0it3r

  • SFML Team
  • Hero Member
  • *****
  • Posts: 11030
    • View Profile
    • development blog
    • Email
Re: Looking for Technical Critique (Partial Face Example SFML)
« Reply #1 on: January 22, 2013, 06:18:51 pm »
So over all your code looks quite fine for a first project. :)

Turning on some more warnings in the compiler, immediately reveled two problems in your code:
eye.h||In constructor 'Eye::Eye(Eye::Eyeball)':|
eye.h|84|warning: 'Eye::eyeball_lid_width' will be initialized after [-Wreorder]|
eye.h|70|warning:   'float Eye::scaleX' [-Wreorder]|
eye.h|25|warning:   when initialized here [-Wreorder]|
eye.h|71|warning: 'Eye::scaleY' will be initialized after [-Wreorder]|
eye.h|68|warning:   'float Eye::MOVE_SPEED' [-Wreorder]|
eye.h|25|warning:   when initialized here [-Wreorder]|
eye.h|68|warning: 'Eye::MOVE_SPEED' will be initialized after [-Wreorder]|
eye.h|67|warning:   'bool Eye::blinking' [-Wreorder]|
eye.h|25|warning:   when initialized here [-Wreorder]|
eye.h|67|warning: 'Eye::blinking' will be initialized after [-Wreorder]|
eye.h|66|warning:   'int Eye::frame_counter' [-Wreorder]|
eye.h|25|warning:   when initialized here [-Wreorder]|
eye.h|25|warning: 'Eye::texture_eye_lid' should be initialized in the member initialization list [-Weffc++]|
eye.h|25|warning: 'Eye::texture_eye_white' should be initialized in the member initialization list [-Weffc++]|
eye.h|25|warning: 'Eye::texture_eye_color' should be initialized in the member initialization list [-Weffc++]|
eye.h|25|warning: 'Eye::sprite_eye_lid' should be initialized in the member initialization list [-Weffc++]|
eye.h|25|warning: 'Eye::sprite_eye_white' should be initialized in the member initialization list [-Weffc++]|
eye.h|25|warning: 'Eye::sprite_eye_color' should be initialized in the member initialization list [-Weffc++]|
eye.h|25|warning: 'Eye::rendered_eye' should be initialized in the member initialization list [-Weffc++]|
eye.h|25|warning: 'Eye::eye_bound' should be initialized in the member initialization list [-Weffc++]|

nose.h||In constructor 'Nose::Nose()':|
nose.h|8|warning: 'Nose::texture_nose' should be initialized in the member initialization list [-Weffc++]|
nose.h|8|warning: 'Nose::sprite_nose' should be initialized in the member initialization list [-Weffc++]|
nose.h|8|warning: 'Nose::nose_width' should be initialized in the member initialization list [-Weffc++]|
nose.h|8|warning: 'Nose::nose_height' should be initialized in the member initialization list [-Weffc++]|

When initializing class members in the initialization list, you have to initialize them in the order you've declared them.
And for the second problem, you should always initialize all the variables (that don't have default constructor) like native types (int, float, etc) in the initialization list, otherwise, if you forget to init them later and then try to access them, the behavior is undefined.

One a bit more serious thing you should not do and you could eventually run into problems, is using std::vector<sf::Texture>, because every time you push_back a new element, all elements could get moved to a different address in memory, which will then break all the references of any sf::Sprite. It's probably better to use either a different container like std::deque or std::map, or use a smart pointer in the vector: std::vector<std::unique_ptr<sf::Texture>>.
Then again when thinking about those multiple textures, you should rather use one single texture with all the images on it and simply use sf::Sprite::setTextureRect() to actually choose which part you want.

In connection with the textures, you're using a container that can hold an arbitrary amount of elements, while you actually expect to have exactly 4 textures in there, with the exact order specification. This is very problematic, since someone who will use your class, could forget about loading lid1 before lid2 or only load 3 textures, which will eventually lead to a crash, when trying to access texture 4. This correlates with the often stated fact, that one should try to avoid 'magic numbers' as often as possible. And if one uses fixed values, one should also make sure that the user of class isn't able to actually break the code.

So if I were you, I'd create one texture with everything on it and then either use an external file, that defines the different rectangles for the different images, or implement them fixed within the class itself. Then instead of letting the class load the texture themselves, I'd only pass a std::shared_ptr to the class. Now if the image isn't the correct one or none is given at all, you'll only get a bad looking result instead of a crash. ;)

Your movement code is depending directly on the framerate, thus if it's higher as expected then the eyes will move way to quite and the other way around.

Now to a few code design decisions:
You're using quite a heavy interface for the Eye class, which doesn't really seem to be necessary, except maybe for the debug output, which on the other hand could also happen within the class. Do you really need all the get...Width/get...Height? If you really need them, then I suggest you use a sf::Vector2, so you can at least remove every other function.

It's quite common in C++ programming to prefix member variables, e.g. I usually use m_, so it's clear at one glance where the variables come from. If you see a none prefixed variable, it's a var local to the current scope, if it has a m_ prefix, it's a member of the class.

Then you should probably use a bit a more consistent naming convention. At the moment you for instance have bool eyeball_position, float MOVE_SPEED and scaleX. As said before you should use a sf::Vector2 for scale, so you can drop the X and Y. For the uppercase variable, I usually use uppercase for constants. Mixing different styles can lead to wrong assumptions when reading the code, which then can often lead to long bug hunts...

Instead of requesting the sprites every frame iteration, you could derive your class from sf::Drawable, implement the draw function and get a bit a nicer syntax: window.draw(eye);

If you're working with booleans or boolean expressions, one can often do a bit better than always checking with if. It's not really wrong and can help for the beginner, but it's also not necessary and in some cases can be very usful. For example:
void Eye::blink()
{
    blinking = !blinking;
}
or
bool Eye::loadEyeLid( std::string file )
{
    sf::Texture tmp;
    texture_eye_lid.push_back( tmp );
    return texture_eye_lid.back().loadFromFile( file);
}
or
bool Eye::loadEyeColor( std::string file )
{
    if( !texture_eye_color.loadFromFile(file) )
        return false;

    eyeball_color_width = texture_eye_color.getSize().x;
    eyeball_color_height = texture_eye_color.getSize().y;

    sprite_eye_color.setTexture(texture_eye_color);

    return true;
}

Although it makes sense to include things at the point where you actually need it, it's better to include everything you need for the file in the beginning (i.e. put #include<iostream> in eye.cpp at after the eye.h include). So people will immediately see what your using in that file and don't have to searching for all the includes between the functions.

Last but not least, I'd suggest to use matching .hpp/.cpp file endings. Some if one could eventually determine on the header file ending, if it's a C file or a C++ file.

I hope, I could point you in some directions. :)
Official FAQ: https://www.sfml-dev.org/faq.php
Official Discord Server: https://discord.gg/nr4X7Fh
——————————————————————
Dev Blog: https://duerrenberger.dev/blog/

io

  • Jr. Member
  • **
  • Posts: 52
  • z/OS by day, SFML by night
    • View Profile
Re: Looking for Technical Critique (Partial Face Example SFML)
« Reply #2 on: January 23, 2013, 01:07:38 am »
Thank you so much for the time taken for your post exploiter!  It was very helpful and insightful, will definitely be taking your advice and including it in the subsequent rewrites of the code.

Appreciate the direction and guidance =) -- a lot for me to look over and study, but great direction =D.

I do have one thing I'd like to ask a little clarification on:
I can see now how the movement code will be an issue with variable frame rate. 

Was I heading in the correct direction in my Eye::drawEye() function for blinking (incorporating a frame counter) in order to combat variable fps?

Looking at some other examples, it seems a lot are based off the clock itself -- I'm guessing this is the best and most solid approach?

Code: [Select]
player.pos.x += player.hspeed * timediff;
Quote
Where timediff is the time difference between the current frame and the last frame that way the player will move the same speed no matter what the frame-rate is.

Skimming one of Laurent's tutorials (very quickly), https://github.com/SFML/SFML/wiki/Source%3A-Sprite-Sheets, seems that clock is used there too -- so this will be a great reference.  I'll look up more if clock's are the best route to go on this.


Thanks again (=
« Last Edit: January 23, 2013, 02:36:54 am by io »

eXpl0it3r

  • SFML Team
  • Hero Member
  • *****
  • Posts: 11030
    • View Profile
    • development blog
    • Email
Re: Looking for Technical Critique (Partial Face Example SFML)
« Reply #3 on: January 23, 2013, 03:01:57 am »
Thank you so much for the time taken for your post
You're welcome! :)

Was I heading in the correct direction in my Eye::drawEye() function for blinking (incorporating a frame counter) in order to combat variable fps?
Yes, it's the right 'direction'. ;)
As said before it incorporates too many 'magic numbers' at the moment. Sure you have to define an animation somewhere, but a cleaner solution would be to have some sort of a animation description file, that you can parse in the beginning. It will make your code much more independed and generic.
Then again if the face is the only thing your application has to do, one could also think about directly integrating the animation description into the code.

Skimming one of Laurent's tutorials (very quickly)
Those are not Laurent's tutorials, that's the tutorial section of the community. ;)

Code: [Select]
player.pos.x += player.hspeed * timediff;
Where timediff is the time difference between the current frame and the last frame that way the player will move the same speed no matter what the frame-rate is.
Keep in mind though, that for more 'time critical' applications/games the Euler 'integration' isn't really that reliable and one should probably use something bit better, like described here.
Official FAQ: https://www.sfml-dev.org/faq.php
Official Discord Server: https://discord.gg/nr4X7Fh
——————————————————————
Dev Blog: https://duerrenberger.dev/blog/

io

  • Jr. Member
  • **
  • Posts: 52
  • z/OS by day, SFML by night
    • View Profile
Re: Looking for Technical Critique (Partial Face Example SFML)
« Reply #4 on: January 23, 2013, 03:10:25 am »
Great, thanks for the clarifications.  This gives me enough to work with =).   Time for some reading and then binge coding =P.

 Ah community tutorial -- I guess the last edited by exploiter on the article would have been a give away if I saw that :D.

eXpl0it3r

  • SFML Team
  • Hero Member
  • *****
  • Posts: 11030
    • View Profile
    • development blog
    • Email
Re: Looking for Technical Critique (Partial Face Example SFML)
« Reply #5 on: January 23, 2013, 03:29:31 am »
Time for some reading and then binge coding =P.
Good luck! :)
Also don't fear to ask stuff on IRC. Sometimes it's a bit crowded, but you'll get quickly help and guidance.

Ah community tutorial -- I guess the last edited by exploiter on the article would have been a give away if I saw that.
Yeah, I've changed all the wiki entries to reflect the new title naming scheme (since GitHub changed the title handling) and broke every wiki link. ;D
« Last Edit: January 23, 2013, 03:33:00 am by eXpl0it3r »
Official FAQ: https://www.sfml-dev.org/faq.php
Official Discord Server: https://discord.gg/nr4X7Fh
——————————————————————
Dev Blog: https://duerrenberger.dev/blog/

io

  • Jr. Member
  • **
  • Posts: 52
  • z/OS by day, SFML by night
    • View Profile
Re: Looking for Technical Critique (Partial Face Example SFML)
« Reply #6 on: February 07, 2013, 10:13:03 am »
If anyone is interested, I've updated the majority of the code to include exploiter's suggestions.  Thank's again for the help, and I'm always open to more suggestions/critique =).



1] Didn't get any warnings when I rebuild my project, but I need to double check initialization order as you suggested
2] shared_ptr is now used for texture.  Design choice was one "sprite sheet" for each face component
3] movement code / blinking based on Euler 'integration' as this is not time critical
4] naming conventions I think is a lot more fluid now (used m_, capital for constants, and m_UpperCase for personal class objects variables, incoming_ for function parameters)
5] updated to make use of vectors
6] put the eye class on a little bit of a diet so interface is smaller
7] no rogue iostream
8] booleans cleaned up
9] matching hpp/cpp endings


~~Trouble Spot I still need to figure out~~

In the eye class's updateState function, the handling of blinking is still using "magic numbers".  I need to change this to be dynamic where it can changed based on blink speed and frames in a blink animation.  Too tired to fix this right now, but I'll be able to fix it after sitting down with it and throwing some math magic at it :P.


Here's the sprite sheet for the eye:
http://i.imgur.com/bzrNY6b.png

[attachment deleted by admin]

eXpl0it3r

  • SFML Team
  • Hero Member
  • *****
  • Posts: 11030
    • View Profile
    • development blog
    • Email
Re: [Updated Code] Looking for Technical Critique (Partial Face Example SFML)
« Reply #7 on: February 09, 2013, 08:23:36 pm »
I agree the code looks way more cleaned up and I think you've already learned quite a bit while applying the suggested changes. :)

In the eye class's updateState function, the handling of blinking is still using "magic numbers".  I need to change this to be dynamic where it can changed based on blink speed and frames in a blink animation.  Too tired to fix this right now, but I'll be able to fix it after sitting down with it and throwing some math magic at it.
As I pointed out before, magic numbers are not all evil, at some point one needs to define somewhere how the animation steps are defined. For such a small example as yours, it would probably be an overkill, if you'd go and export all the animation definition to an external file, which you then load and parse at runtime, thus having fixed values in the source is imho acceptable.

Here are a few more suggestions, some are again more "artistic" ones:
  • I personally like to name my files with an beginning uppercase letter, so it will match the class name and look a bit more pronounced.
  • In the Face class constructor you don't need to initialize the color in the constructor, but you can also simply do it in the initialization list.
  • You're using int in all your for loops, although none of them will need negative values, thus it's more logical to use unsigned int.
  • In theory the prefix-increment/decrement should be preferred over the postfix, because with the postfix the compiler (if not optimized) would create a temporary variable, while with the prefix you directly in-/decrease the value. For for loops ++i should be used over i++.
  • With the use of C++11 you could also think about using range-based for loop, which will remove the need for specific types, etc.[/tt]
  • At the moment you're using a shared_ptr for the skin color, which isn't the nicest design. Since the color gets set once in the constructor and should reference the one color in the face class, you could simply pass in a reference and only save the reference in the constructor. Although this should be fine, the question still remains, if the eye should have a reference to the face's skin color. Personally I'd rather chose a design, where the eye knows to what part it's attached to and can grab the color directly from that class. Then again if you look at what you're doing, you could also just draw the background with a transparent color and only draw on top of it, what should get displayed. With that you won't need to know the background color and it will automatically shine through.
  • When you implement the interface of sf::Drawable you should always apply the render states, so if you'd decide to do something else than what SFML provides you could easily switch back.

Keep in mind that all of this points are mostly my opinion and that I also make mistakes. :D
Official FAQ: https://www.sfml-dev.org/faq.php
Official Discord Server: https://discord.gg/nr4X7Fh
——————————————————————
Dev Blog: https://duerrenberger.dev/blog/

io

  • Jr. Member
  • **
  • Posts: 52
  • z/OS by day, SFML by night
    • View Profile
Re: [Updated Code] Looking for Technical Critique (Partial Face Example SFML)
« Reply #8 on: February 09, 2013, 11:47:15 pm »
Thanks for taking another look over this!  Helpful suggestions as always :-).


Could I get some clarification on these two points:

Quote
  • (1) At the moment you're using a shared_ptr for the skin color, which isn't the nicest design. Since the color gets set once in the constructor and should reference the one color in the face class, you could simply pass in a reference and only save the reference in the constructor. Although this should be fine, the question still remains, if the eye should have a reference to the face's skin color. Personally I'd rather chose a design, where the eye knows to what part it's attached to and can grab the color directly from that class. Then again if you look at what you're doing, you could also just draw the background with a transparent color and only draw on top of it, what should get displayed. With that you won't need to know the background color and it will automatically shine through.
  • (2) When you implement the interface of sf::Drawable you should always apply the render states, so if you'd decide to do something else than what SFML provides you could easily switch back.


For (2) do you mean in my face class where I have:

    target.draw( *m_LeftEye );
    target.draw( *m_RightEye );
 

change to

    target.draw( *m_LeftEye, states );
    target.draw( *m_RightEye, states );
 

In case I want to use RenderStates later?

I'll need to reed up on RenderStates as I'm not quite sure what they are yet =).


And for #1

The problem I had with transparent background on the eyes was once the eye color portion (the green eyeball) would move past the lids, the green eye would be showing instead of being hidden.  I haven't been able to find a way around this :(.  (This will make more sense if you replace the zip file's sprite sheet with this sprite sheet:   http://i.imgur.com/ncai40x.png)

For your suggestion -- do you mean something like this?

in Face.hpp change the color shared pointer to just a color object

when I create the Eye in the Face constructor simply pass &m_color_object

Eye would have a color* that takes the passed object.



Thanks again!  Very helpful info.
« Last Edit: February 09, 2013, 11:51:27 pm by io »

eXpl0it3r

  • SFML Team
  • Hero Member
  • *****
  • Posts: 11030
    • View Profile
    • development blog
    • Email
Re: [Updated Code] Looking for Technical Critique (Partial Face Example SFML)
« Reply #9 on: February 10, 2013, 12:02:45 am »
change to

    target.draw( *m_LeftEye, states );
    target.draw( *m_RightEye, states );
 
Yes exactly.

In case I want to use RenderStates later?
Well you most probably won't really need it, but instead of ignoring it, you can also just apply it, there shouldn't be a difference in performance.

But if you for instance would want to apply a shader to your object, you could do this by calling
window.draw(eye, &shader)
And since sf::RenderStates has a constructor for sf::Shader, it will implicitly call that constructor with the shader as argument and then pass that render states object to the draw function, which will internally call your eye's draw function, which will call the draw function on the target with the applied shader. (Not sure if you got that...)


The problem I had with transparent background on the eyes was once the eye color portion (the green eyeball) would move past the lids, and the green eye would be showing instead of being hidden.  I haven't been able to find a way around this :(.
How do you prevent that at the moment?
Note that you can "replace" the current color with a transparent one, if you set the render state for the blend mode to sf::BlendNone.


For your suggestion -- do you mean something like this?
in Face.hpp change the color shared pointer to just a color object
when I create the Eye in the Face constructor simply pass &m_color_object
Eye would have a color* that takes the passed object.
Not exactly, more like:

class Bar
{
public:
    Bar(sf::Color& color) :
        m_color(color)
    {
        // ...
    }

private:
    sf::Color& m_color;
};

class Foo
{
public:
    Foo()
    {
        Bar b(m_color);
        // ...
    }

private:
    sf::Color m_color;
};
Official FAQ: https://www.sfml-dev.org/faq.php
Official Discord Server: https://discord.gg/nr4X7Fh
——————————————————————
Dev Blog: https://duerrenberger.dev/blog/

io

  • Jr. Member
  • **
  • Posts: 52
  • z/OS by day, SFML by night
    • View Profile
Re: [Updated Code] Looking for Technical Critique (Partial Face Example SFML)
« Reply #10 on: February 10, 2013, 12:28:34 am »
Quote
But if you for instance would want to apply a shader to your object, you could do this by calling
window.draw(eye, &shader)
And since sf::RenderStates has a constructor for sf::Shader, it will implicitly call that constructor with the shader as argument and then pass that render states object to the draw function, which will internally call your eye's draw function, which will call the draw function on the target with the applied shader. (Not sure if you got that...)
actually made sense for me :)

Thanks for the foobar example, made it clearer to me now.

The way I prevent the eye issue now is through the combo of rendertexture and putting the base color on the eyelids on the original spritesheet.  That way the eyelid frame is on top of the green eyeball picture with only the middle part being transparent.  The white portion of the spritesheet is below the green eyeball. I was thinking if I had to change skintone I would have to update the texture somehow.  (hope this paragraph made sense XD) 

    m_eye_canvas.clear( *m_skintone );
    m_eye_canvas.draw( m_eye_sprite[EYE_WHITE] );
    m_eye_canvas.draw( m_eye_sprite[EYE_COLOR] );
    m_eye_canvas.draw( m_eye_sprite[m_eye_frame] );
    m_eye_canvas.display();

    m_rendered_eye.setTexture(m_eye_canvas.getTexture());
 
so we draw white portion first, draw the green eye on top of that, and the m_eye_frame on top of all those (the portion on the sprite_sheet that has the beige skintone. 

Hmm .. maybe I could use that transparent sprite sheet I provided you that has the "error"... and then do something like this with the render texture to get the same results as having the actual original color in the spritesheet?

http://mobiledevelopertips.com/cocoa/how-to-mask-an-image.html

I see a post w/ blendnone as well:
http://en.sfml-dev.org/forums/index.php?topic=7427.msg49991#msg49991

I'll have to look more at your blendnone suggestion tonight because right now I think I'm just rambling ;].
« Last Edit: February 10, 2013, 12:40:23 am by io »