Welcome, Guest. Please login or register.

Login with username, password and session length

 
Advanced search

1411421 Posts in 69363 Topics- by 58416 Members - Latest Member: timothy feriandy

April 18, 2024, 01:07:00 AM

Need hosting? Check out Digital Ocean
(more details in this thread)
TIGSource ForumsCommunityTownhallForum IssuesArchived subforums (read only)TutorialsA game from scratch in C++ using SFML
Pages: [1] 2 3
Print
Author Topic: A game from scratch in C++ using SFML  (Read 32407 times)
Serapth
Level 2
**


View Profile
« on: September 08, 2011, 02:57:28 PM »

I have/am put together this tutorial series on creating a game using SFML 1.6.

One of the general trends I see is that new C++ developers are following outdated or awful tutorials, so they learn a horrific programming style.  This series of tutorials looks to address that.  It is as much an OO programming treatise, as an SFML or Game Programming primer.  Hopefully it will be useful to new developers, especially if it can help you with properly structuring your code.

It is (going to be) written over many parts, so some code you write in part 2 may be completely rewritten in part 8.  I personally believe this is one of the greatest ways to teach, and I hope others agree.

I have used C++ for years, although for the last 7 or 8 I have been mostly a C# developer, so I appreciate feedback from other C++ developers if you disagree with some of my instruction.  I know ( and sorta regret ) making my Game class a static object was probably a mistake as a beginner lesson, but I think in the long run it will make sense.  Many times I will write code that is going to be refactored later.  I intend this to be more about the process than the actual code generated.

This tutorial is very much an ongoing project, and is a live document, so your feedback will shape how it develops.  I already have three more chapters worth of code written and many more to come.  It may not be the most concise thing you have ever read, but I hope it fills the gap between code samples and full games, a gap I think hurts many new developers.


So, check it out and let me know what you think.

http://www.gamefromscratch.com/page/Game-From-Scratch-CPP-Edition.aspx
Logged
Mikademus
Level 10
*****


The Magical Owl


View Profile
« Reply #1 on: September 09, 2011, 12:48:42 AM »

Nice introduction to C++ and SFML game programming! Well though-through and gentle. Your writing is easy to read and you get the concepts through well. You've put some thought into the series, and it shows! I especially like your infoboxes! The extra information about C++ syntax and portability aspects placed where relevant is nice and helpful. I think this series will be appreciated! CoffeeHand Thumbs Up Right


Specific feedback:


Portability:
  • You discuss _tmain and _TCHAR as MS-specific, but fail to mention "#pragma once". Even though you assume VStudio as the IDE it might be beneficial to mention it being non-portable and introduce header guards.
  • You use backslashes for paths. This will fail on non-Windows systems. Since Windows/VStudio accepts forward slashes they should be used. And perhaps an infobox about that?

Project paths: You assume that the SFML headers are in the project's local path? ("#include "SFML\Window.hpp"") That seems strange to me. Might it be better to use #include <> for the global path?

Coding style
  • SFML uses capitals for member symbols, which is unfortunate since lower-case members have become the industrial standard and the SFML style is increasingly rare. Since you're setting out to teach SFML, though, it is OK because it creates code consistency, but it might be worth an infobox.
  • You use a mixture of GNU and Allman indentation in the source (and for the MenuItem class declaration you seem to use Whitesmith style). Consistency is important! Since you want to teach beginners it might be better to use a standard style throughout regardless how much one might like one's own. Since GNU style (not to mention Withesmith) is considered unconventional, even idiosyncratic, today, it is better to stick with Allman throughout, since it separates block more clearly and is easier to read and follow.
  • 2-space indentation is difficult to unravel, especially since you use deeply nested scopes; increase indentation to the standard 4-spaces for the benefit of readers.
  • You prefix your private members with underscore. As you say, leading underscore are principally reserved for compiler implementation. The Scott Meyers recommendation is to use a trailing underscore instead to avoid possible collisions (unlikely as they might be), or simply skip the underscores altogether.
  • "While (42 != 43)" might be confusing to beginners; "while (true)" is the standard idiom.


As a quick food for thought, you think it might be meaningful to discuss at the end the difference between SFML 1.6 and 2.0, and whether it is worthwhile for beginners using the newer or older version?
« Last Edit: September 09, 2011, 12:54:15 AM by Mikademus » Logged

\\\"There\\\'s a tendency among the press to attribute the creation of a game to a single person,\\\" says Warren Spector, creator of Thief and Deus Ex. --IGN<br />My compilation of game engines for indies
Serapth
Level 2
**


View Profile
« Reply #2 on: September 09, 2011, 05:12:41 AM »

Very good feedback, thanks!

As to the #pragma once, this is kinda freaking me out as I am positive I wrote something about it in the Microsoftisms box, about how the standards was gaurds, that pragma's by nature arent portable etc...    Obviously it didn't make the cut!  I have found writing a blog post from two different laptops is not a very good idea!  You are 100% correct on this and I will add that bit back in.

Good point re slashes, I wonder if that deserves it's own infobox, or if I should just change it.  Frankly it was just an error on my behalf and there is little reason to teach both ways, is there?


I went with the project pathing the way I did because I present a complete project file at the end of each post, so I have no dependencies are global settings being configured properly.  I believe that include/linker setup are two of the most common errors among new developers, so I took it out of the equation. 

The indentation is actually in Allman style at the code level, with two exceptions.  Enums, so they fit within the size constraints of my sites formatting, and case statements, which for some reason Visual C++ express goes stupid on and I stopped fighting it long ago.  However, my code is then using a plugin for Live Writer and it..............  does interesting things.  To say nothing of the fact I switched plugins part way through as the first simply stopped working and would crash on every use ( which is a shame, as it was vastly superior ).  The two space indentation is another casualty of the plugin used.  Again, the actual code itself is 4 space tabs.

You are 100% correct about the 42!=43 comment.  That is an old inside joke that I have used for so long, I just use it without thinking now.  You are right, it is not new user friendly and shouldn't have made it through!


I almost wonder if a completely separate post on code formatting/style for beginners would be merited.  You are right, consistency is extremely important, I wonder if there is a good beginner friendly document I could link too.  I would write something myself, but frankly I am pretty guilty about using my own horrible hybrid naming style I've developed over the years.
Logged
Mikademus
Level 10
*****


The Magical Owl


View Profile
« Reply #3 on: September 09, 2011, 08:47:44 AM »

You're welcome to look at my coding standard if you wish: who knows, you might even like it!  Big Laff
Logged

\\\"There\\\'s a tendency among the press to attribute the creation of a game to a single person,\\\" says Warren Spector, creator of Thief and Deus Ex. --IGN<br />My compilation of game engines for indies
Endurion
Level 2
**



View Profile WWW
« Reply #4 on: September 10, 2011, 10:03:17 AM »

Forget everything about coding style, use one and use that consistently. Everything else varies wildly and 90% are always using the other style.

Looks very nice, explains everything thoroughly and beside two spelling errors nothing glares at me Wink

In part 3 this
Since MenuItem contains entirely public members, choosing struct saves a bit of typing “private: “.
should probably mean "public:" ?

In part 4 the info on constructor should probably set _isLoaded to false in both cases to not irritate beginners.

You may want to have a slightly tinted background for the info boxes so they appear separated.
Logged
Serapth
Level 2
**


View Profile
« Reply #5 on: September 10, 2011, 03:11:17 PM »

Thanks for the feedback Endurion, good catches to both, I will include them in my edits.

As to the slight tinting, that is a good idea, now I really wish I used a CSS template, instead of copy/pasting HTML code for each of those boxes... Smiley


Also, Mikademus, I like your coding standards and follow a similar one, but will probably follow Endurion's ( and your ) advice and just try to stay consistent.  Coding standards are one of those potential holy war issues.  I am really tempted at some point in the future to put together a post specifically about coding standards.

BTW, the font you use for source on your wiki is rather difficult to read, at least on Chrome at 1080p.  Probably the wiki software and beyond your control though, like my issue with spacing in my blogging software.
Logged
megamjau
Level 0
*


View Profile
« Reply #6 on: September 10, 2011, 03:13:09 PM »

Great tutorial, I just did step 1-4 using SFML 2.0 and GCC.

A few typos and quirks I ran into:

* In MainMenu.cpp when handling sf::Event:Closed you type "Exit", should be "return Exit" if I'm not mistaken.
* In VisibleGameObject.h there's a uppercase X but a lowercase y in the SetPosition function definition.
* In the VisibleGameObject::Load definition in VisibleGameObject.cpp  you have filename == "", should be filename = "" ?
* In PlayerPaddle.h you type #include  "visiblegameobject.h" (in all lowercase) which does not work on linux/unix systems (I think).
* No handy download link for the paddle.png img.

The program works fine with SFML 2.0 with a few small changes, using sf::Texture instead of sf::Image, sf::Rect has been changed and the Event system has been changed slightly.

Looking forward to part 5.
Logged
Serapth
Level 2
**


View Profile
« Reply #7 on: September 10, 2011, 06:00:18 PM »

Exceedingly good catch on the return Exit;  That's the quirky C++'ism that can be such a PITA to catch.  That "Exit;" was valid code let that one slip through on compile, but you are completely correct, it should be return Exit;

I created a world of hurt for myself by authoring 5 different projects ( Pang1-5 ), then writing the blog post to go with them, as propagating changes between them has been an absolute nightmare and allowed far too many quirks in.  I am cleaning the code up based on feedback, then going forward ( after Pang6 project which has already been written ), I will keep the code to blog post in sync.

Thanks for the finds, and glad to hear it works relatively unchanged on 2.0!

What I may do after I "finish", is go back through and edit based on 2.0 compatibility in their own text boxes like suggested earlier.  I am pleased to hear things didn't vary that much.
Logged
Mikademus
Level 10
*****


The Magical Owl


View Profile
« Reply #8 on: September 11, 2011, 03:00:27 AM »

Also, Mikademus, I like your coding standards and follow a similar one, but will probably follow Endurion's ( and your ) advice and just try to stay consistent.  Coding standards are one of those potential holy war issues.  I am really tempted at some point in the future to put together a post specifically about coding standards. BTW, the font you use for source on your wiki is rather difficult to read, at least on Chrome at 1080p.  Probably the wiki software and beyond your control though, like my issue with spacing in my blogging software.

Absolutely, consistency is Queen. Not King, because sometimes we need to make exceptions to emphasise code intent and improve code readability. The thing about my coding standard is actually not the indentation style (which IS a common source of Unholy Wars) but rather my concept of "code pedagogics": every one of my rules in my document is about making C++ code as self-explanatory, readable and solid as possible, and they can be used with any indentation style.

Thanks for the feedback on the font size! Most of that is, as you guessed, beyond my control, but perhaps I can adjust the source font size! Coffee
Logged

\\\"There\\\'s a tendency among the press to attribute the creation of a game to a single person,\\\" says Warren Spector, creator of Thief and Deus Ex. --IGN<br />My compilation of game engines for indies
Serapth
Level 2
**


View Profile
« Reply #9 on: September 13, 2011, 06:32:59 PM »

It was somewhat slower than I hoped but Part 5 is now up.

In this section we create a game object manager, aptly named GameObjectManager.

This thus far is the most code heavy portion and introduces the most C++ concepts in a single part, so I hope I did an adequate job explaining things.  If you have been following along till this point and found the complexity or difficulty curve rose too steeply, please let me know!

We didn't actually add anything new to the game, but this was probably the single most important change we are going to make to the game.  Next part is going to add functionality dont worry! Smiley

Of course, any and all comments appreciated from experts in the forums as well!  I do not pretend to be a C++ expert, so if there are any flaws, errors or short comings in some of my instructions, let me know.  Trust me, I will not take it personally in the least!  As is clear from earlier in this thread, errors slip through without effort.  But when dealing with instruction for new developers, the cost of those errors is magnified ten fold, so of course I want to catch every one of them.

Anyways, here is Game From Scratch: C++ Edition  Part 5
Logged
Mikademus
Level 10
*****


The Magical Owl


View Profile
« Reply #10 on: September 13, 2011, 11:32:54 PM »

Feedback on part 5:

All your GameObjectManager methods that use string arguments should use references (preferably const references) to avoid implicit copy overhead.

I would strongly recommend you to teach beginners to write the pointer asterisk together with the variable rather than the data type to avoid the typical beginners mistake:
Code:
int* a, b; // BAD! What data type is b?

// Better practice:
int *a, b;  // Now the data type of b is clear and it is
int *c, *d; // obvious that pointers always must have asterisks.

// I am consistent with this and use it in signatures and for references, too
void Function( char *source, char *target );
void Gunction( string &source, string *target );
This will also make the reference in your GameObjectDeallocator's operator() look less like an operation, which currently might be  confusing for beginners.

Since you are targeting beginners I would recommend you to remove the comment about disagreeing with Bjarne and C++ being difficult to work with. It is subjective, not something that beginners wants to read and may be disheartening. The task you've taken on is to smooth their way into games making, not introduce them to meaningless language wars.
Logged

\\\"There\\\'s a tendency among the press to attribute the creation of a game to a single person,\\\" says Warren Spector, creator of Thief and Deus Ex. --IGN<br />My compilation of game engines for indies
Average Software
Level 10
*****

Fleeing all W'rkncacnter


View Profile WWW
« Reply #11 on: September 14, 2011, 03:45:09 AM »

Some comments, since I'm a C++ pedant, and some of this stuff may interest you:

Code:
class Game
{

public:
  static void Start();

private:
  static bool IsExiting();
  static void GameLoop();

  enum GameState { Uninitialized, ShowingSplash, Paused,
          ShowingMenu, Playing, Exiting };   
  static GameState _gameState;
  static sf::RenderWindow _mainWindow;
};

Classes with nothing but statics are a Java-ism and they really have no place in C++.  This should be rewritten like so:

Code:
// game.h
#ifndef GAME_H
#define GAME_H

namespace Game
{
    void Start();
}

#endif

// game.cpp

#include "game.h"

namespace  // Anonymous namespace for file locals.
{
    enum GameState { Uninitialized, ShowingSplash, Paused,
                     ShowingMenu, Playing, Exiting };   
 
    GameState _gameState;
    sf::RenderWindow _mainWindow;

    bool IsExiting()
    {
        // whatever
    }

    void GameLoop()
    {
        // whatever
    }
}

void Game::Start()
{
    // whatever
}

What you really want is a namespace module.  This is a much cleaner design that keeps implementation details out of the header, and provides identical functionality.

Also, the importantance of const-correctness can't be overstated, and it's important to get in the habit early.

Code:
class GameObjectManager
{
public: 
  GameObjectManager();
  ~GameObjectManager();

  void Add(std::string name, VisibleGameObject* gameObject);
  void Remove(std::string name);
  int GetObjectCount() const;
  VisibleGameObject* Get(std::string name) const;

  void DrawAll(sf::RenderWindow& renderWindow) const;

private:
  std::map<std::string, VisibleGameObject*> _gameObjects;
 
  struct GameObjectDeallocator
  {
    void operator()(const std::pair<std::string,
                    VisibleGameObject*> & p) const
    {
      delete p.second;
    }
  };
};

At a glance, I'd reckon all the methods I've tagged 'const' in that class should be const, I didn't look too closely at the source code, so I may be wrong on at least one.

And as mentioned earlier, leading anything with an underscore is generally a bad idea.  You run the risk of clashing with the compiler's reserved namespace.

Quote from: Mikademus
All your GameObjectManager methods that use string arguments should use references (preferably const references) to avoid implicit copy overhead.

This can be a double-edged sword.  I've had many functions in the past that actually showed worse performance when doing this, due to the loss of data locality and the fact that the aliasing introduced by references tends to hamstring the optimizer.  Furthermore, with the new rvalue references and pass by move semantics in C++11, pass by value should start to deliver superior performance when temporaries are involved, since you will get copy elision, data locality, and lack of aliasing all at the same time.  I actually expect the whole 'always pass by const reference rule' to become obsolete with C++11.

Personally, I choose passing mode based on the inteded semantics.  If the intent is pass by value, then I pass by value.  If profiling show a performance impact, then I change it and test again.  If the change helps (it doesn't always!) then I pass by reference.

Of course, if you need virtual dispatch, then you really have no choice.
Logged



What would John Carmack do?
Mikademus
Level 10
*****


The Magical Owl


View Profile
« Reply #12 on: September 14, 2011, 04:10:26 AM »

Some comments, since I'm a C++ pedant, and some of this stuff may interest you:
...
Quote from: Mikademus
All your GameObjectManager methods that use string arguments should use references (preferably const references) to avoid implicit copy overhead.

This can be a double-edged sword.  I've had many functions in the past that actually showed worse performance when doing this, due to the loss of data locality and the fact that the aliasing introduced by references tends to hamstring the optimizer.  Furthermore, with the new rvalue references and pass by move semantics in C++11, pass by value should start to deliver superior performance when temporaries are involved, since you will get copy elision, data locality, and lack of aliasing all at the same time.  I actually expect the whole 'always pass by const reference rule' to become obsolete with C++11.

This was a very interesting discussion. Thanks for the heads-up!
Logged

\\\"There\\\'s a tendency among the press to attribute the creation of a game to a single person,\\\" says Warren Spector, creator of Thief and Deus Ex. --IGN<br />My compilation of game engines for indies
Average Software
Level 10
*****

Fleeing all W'rkncacnter


View Profile WWW
« Reply #13 on: September 14, 2011, 04:37:04 AM »

Some comments, since I'm a C++ pedant, and some of this stuff may interest you:
...
Quote from: Mikademus
All your GameObjectManager methods that use string arguments should use references (preferably const references) to avoid implicit copy overhead.

This can be a double-edged sword.  I've had many functions in the past that actually showed worse performance when doing this, due to the loss of data locality and the fact that the aliasing introduced by references tends to hamstring the optimizer.  Furthermore, with the new rvalue references and pass by move semantics in C++11, pass by value should start to deliver superior performance when temporaries are involved, since you will get copy elision, data locality, and lack of aliasing all at the same time.  I actually expect the whole 'always pass by const reference rule' to become obsolete with C++11.

This was a very interesting discussion. Thanks for the heads-up!

Here is an example I used once to demonstrate this, another benefit of pass by value is the ability to inline virtuals:

Code:
struct Doodad
{
    virtual void do_it() const // Inline virtual.
    {
    }
};

void do_it_by_ref(const Doodad &doodad)
{
    for (unsigned x = 0; x < 500; x++)
    {
        doodad.do_it();
    }
}

void do_it_by_val(Doodad doodad)
{
    for (unsigned x = 0; x < 500; x++)
    {
        doodad.do_it();
    }
}

GCC is able to optimize away the entire body of do_it_by_val(), since it is able to determine at compile time that the method do_it() will be empty, whereas do_it_by_ref() is forced to make 500 virtual function calls.

Now this is of course a constructed and completely artificial example, but it should demonstrate the kind of optimizations you can miss out on when passing by reference.
Logged



What would John Carmack do?
Mikademus
Level 10
*****


The Magical Owl


View Profile
« Reply #14 on: September 14, 2011, 07:45:47 AM »

Right, so does this go for objects of trivial construction cost only, or for all objects with move ctors?
Logged

\\\"There\\\'s a tendency among the press to attribute the creation of a game to a single person,\\\" says Warren Spector, creator of Thief and Deus Ex. --IGN<br />My compilation of game engines for indies
Average Software
Level 10
*****

Fleeing all W'rkncacnter


View Profile WWW
« Reply #15 on: September 14, 2011, 08:00:37 AM »

Right, so does this go for objects of trivial construction cost only, or for all objects with move ctors?

It applies to any object.  It's usually an advantage for the optimizer to know exactly what kind of object it's working with.  Pass by reference doesn't get you that, at least not in C++, since the object could be of some derived type (Ada, for example, allows you to pass strict types by reference.  That is, you can have a function that accepts a reference to a Doodad, and only a Doodad.  No types derived from Doodad are allowed.  I'd like to see this feature in more languages).

Of course, it's very situational.  You really have to analyze performance and see what works best.  Even after move construction becomes common, there will probably still be cases where pass by reference gets you better performance, there will just be fewer of them.

I guess the point is that there is no magic passing method that guarantees better performance, it's entirely situational.  You have to take the properties of the object, and what the function is actually doing into account as well.  There's no substitute for performance testing.
Logged



What would John Carmack do?
Serapth
Level 2
**


View Profile
« Reply #16 on: September 14, 2011, 08:47:57 AM »

Thank you two for the feedback, I have made some updates accordingly.  I agree with what you said Mikademus regarding the Bjarne comment, it has been removed. 

Excellent point regarding constness, I really need to pay more attention and priority to doing that correctly. 

I do have a question and the answer may simply be "it's just the way things are done", but what is with the C++ trend of putting const at the end of the statement, instead of at the beginning like commonly seen in C# or Java ( or frankly, in C back in the day )?
Logged
Average Software
Level 10
*****

Fleeing all W'rkncacnter


View Profile WWW
« Reply #17 on: September 14, 2011, 10:13:56 AM »

I do have a question and the answer may simply be "it's just the way things are done", but what is with the C++ trend of putting const at the end of the statement, instead of at the beginning like commonly seen in C# or Java ( or frankly, in C back in the day )?

Are you referring to things like this:

Code:
class Something
{
public:
    void some_method();
    void some_other_method() const; // <--- here
};

I think you might be confusing concepts.  C++ allows constant instances of objects, C# and Java do not (to the best of my knowledge).  Tagging member functions with 'const' means that the operation is allowed to be used by a constant object. (Or an object referred to by a pointer or reference to const, which might actually be constant)

That is, C++ lets you do this:

Code:
const Something my_thing; // <--- This object is constant.

my_thing.some_other_method(); // OK!  This is a const method.
my_thing.some_method(); // ERROR!  This method can change the object, so it can't be used on a constant one.

This idea is very important in allowing the compiler to catch mistakes, since const methods are not allowed to alter their objects (forget about mutable for now).

If you have an object that draws itself, for example, you probably don't want the object to change during the draw function.  By tagging it as const, the compiler will ensure that this is the case, and if you accidentally try to change the object while drawing, it will be an error.
Logged



What would John Carmack do?
Serapth
Level 2
**


View Profile
« Reply #18 on: September 14, 2011, 10:33:08 AM »

You are correct, and I am confusing concepts.

So when you tag a member function as const you are essentially creating a contract(exception) with the compiler that const instances of this class can safely call this function as you are promising not to make any Changes?


Off topic: wow there is something with the site, perhaps the million emoticons but that post just too me about 10 my transformer tablet. I've experienced lag before, but but nothing like this.
Logged
Average Software
Level 10
*****

Fleeing all W'rkncacnter


View Profile WWW
« Reply #19 on: September 14, 2011, 10:45:26 AM »

So when you tag a member function as const you are essentially creating a contract(exception) with the compiler that const instances of this class can safely call this function as you are promising not to make any Changes?

Yes, that's exactly it.

It's a very important thing to get right, especially as programs get larger.  For example, when I do C++ games I generally have a class like this:

Code:
class Drawable
{
public:
    virtual void draw() const = 0;
};

Everything that can be drawn inherits this, and by making it const I ensure that a draw operation will never alter an object.  If I screw this up anywhere and try to change something the compiler says, "hey, you said you wouldn't do that!" and I can fix the problem.
Logged



What would John Carmack do?
Pages: [1] 2 3
Print
Jump to:  

Theme orange-lt created by panic