Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
How to make Flappy Bird with C++ (terminalroot.com)
110 points by marcoscpp on Aug 25, 2022 | hide | past | favorite | 47 comments


Interesting choice with SFML. Always curious to see what people choose for graphics when it comes to C++. I tried a simple game with that which worked alright, but then I wanted to port to Android and things didn't go very well. I switch to SDL2, where there's more official support for Android, but it's still not ... let's say ... enjoyable. Probably because I'm going against the Android grain in trying to avoid android-studio and gradle. I didn't need those for Windows64, Linux64, ARM, and I'd rather keep the same CMAKE, gui-free dev env for Android as well.


SFML can export to Android, but to me the biggest drawback is lack of Web export. In the current gamedev scene, web export is super popular for tiny games (specially for game jams, where it's just more convenience and also secure to share and try other games).

In today's world where so many tools offer web export (from libs to frameworks to engines), I can't justify learning a tool that doesn't support it.


Check out raylib


What a great library! Thanks! This should be a lot more popular.


Poor. Encouraging unnecessary use of 'shared_ptr' really is detrimental to a beginner's growth.


Beginner here. Could you please explain?


An example: `std::shared_ptr<sf::RenderWindow> window;` - your main window really doesn't need multiple owners - it can be singly owned by `main()` or whatever the top level construct is for your game with `std::unique_ptr`. If you need to pass the window to some other function, do it by raw pointer or ref, signaling that "here's this object for you to use, but not own". `shared_ptr` should only be used for pointers that actually have multiple owners. The textures here might be a good example for this - you could implement a simple resource system that drops unused resources with `shared_ptr` + `weak_ptr`.

As for why - it's mostly around signaling intent and protecting against leaks. It's much easier to reason about ownership if you don't have to worry about some object deep inside your engine hierarchy holding a `shared_ptr` to the object that owns it. This can still happen with unique_ptr, but is much less likely.


Great explanation, as someone who is new to C++ and actively learning, these perspectives are invaluable.

I have been using some shared pointers in a personal project because of its ease of use but I aim to review and refactor this code to follow C++ idioms more appropriately. Thank you!


In these cases I usually opt for gsl::not_null<T>


You don't even need 'unique_ptr'. The render window on its own is completely fine.


In this somewhat toy case - yes. :)


>std::shared_ptr<sf::RenderWindow> window; >std::shared_ptr<sf::Sprite> background, bird, pipeBottom, pipeTop;

None of that should be shared_ptr, in fact they shouldn't be pointers at all.

>auto flappy = std::make_shared<FlappyBird>(); >flappy->run();

That would work just fine as a global in main.cpp

FlappyBird flappy;


Or, better, as an anonymous expression in main():

  FlappyBird().run();


Disagree - it's very reasonable for resources to be shared.


This is C++.

You should not pay the performance hit for features you don't need in a particular program.

In fact: It is reasonable for C++ programs to NOT use all language features.


I'm not disagreeing with you.


In most cases, not. Where there is a legitimate reason to share, share. Without, it is just sloppiness.


In the context of a game engine, I mean. You may be sharing a model or texture between multiple entities, and want to know when it’s no longer in use.


Even there, something higher up the stack probably knows too, and is in a better position to choose a good time to dispose of it.


Possibly! It really depends on how you've architected things. I said it's "reasonable" not that it's the one true way of implementing it.


I think we really are all in agreement here.


Shared ownership is a rare case, so in general using shared_ptr should already raise some suspicion. And in this case there is no reason any of the shared_ptr'ed things in the code need to be on the heap so no reason to even have a pointer in the first place.

Code like this is effectively what you get when you have someone that doesn't know how to write C++ but writes it anyway and uses pointers everywhere because they either read that C++ is pointer heavy (it's not) or have learned C++ through some course that is actually a course on algorithms and only uses C"++" as example language and is hence at best showing shitty C with iostream and classes straight out the 1980s. Then those people find the (correct) advice of using smart pointers instead of (owning!!!) raw pointers and just put shared_ptr everywhere without thinking about if there actually is a shared ownership to be expressed and/or if the heap allocation is even needed.


A shared pointer is one where ownership can be shared across any part of the application. That sounds great initially, as it assures the thing you’re pointing at will still be alive while you need it. In reality it creates a vague notion of ownership, where no one is responsible for object lifetime, because everyone is. This creates a “functional global” in your application per instance of your shared pointer class. The net effect is a more modern looking architectural morass.


What does ownership mean? Specifically.


If object a owns object b, then it means that a is responsible for managing b's lifetime - that is to delete b and free the memory.

Edit: A may also fulfill its obligation by transferring b to a new owner.


Overuse of shared_ptr is called Java Disease in reviews.

Most programs don't need to use shared_ptr at all.

Side note, there is also no value in making those member functions "protected".


This program, in particular, has no need for any pointers of any kind. They are all better done as simple value objects.

I have verified this by deleting "#include memory>", all uses of shared_ptr, changing all "->" to ".", and deleting all unary "*".

The window member object is initialized, then, using (drumroll...) member initializer syntax. Most uses of "{}" are better deleted.


shared_ptr<> is most useful for multithreaded applications where a pointer can be safely shared amongst many threads, where the last one to go out of scope will call delete on the object.

To do this safely the shared_ptr<> class maintains a reference count which is protected by a mutex. This is pretty inefficient if you never share a pointer across threads.

Better to use unique_ptr<> where the Thing that created it is also the Thing that's responsible for deleting it - after making sure that anything it's shared the pointer with is already safely destroyed (another thread, a collection or some other object).


std::shared_ptr doesn't use mutexes in any implementation I know of. They use atomic instructions for incrementing and decrementing the reference count, which are very fast.


Plus their thread-safety is limited to the pointer's control block not the actual object it's pointing to.

e: nvm reading hard brain mutex too slow


You're right, an atomic inc/dec would also do just fine. I stand corrected.


I mean it's not bad but you start with a couple of files already packed with code and you don't explain all that. It is probably fine for someone who knows what these things are for but this kind of person can probably do away with the rest of the tutorial.


just want to point out, this unjustly uses std::shared_ptr

in main.cpp

  auto flappy = std::make_shared<FlappyBird>();
  flappy->run();
you can do this.

  FlappyBird flappy;
  flappy.run();

in flappy.hpp

  std::shared_ptr<sf::RenderWindow> window;
  std::shared_ptr<sf::Sprite> background, bird, pipeBottom, pipeTop;
just do this.

  sf::RenderWindow window;
  sf::Sprite background, bird, pipeBottom, pipeTop;
in flappy.cpp.

  auto e = std::make_shared<sf::Event>();
  while( window->pollEvent( *e ) ){
    if( e->type == sf::Event::Closed){
      window->close();
    }
  }
just do this

  sf::Event event;
  while (window.pollEvent(event)) {
    if (e.type == sf::Event::Closed) {
      window.close();
    }
  }

It's really that simple.


in https://github.com/terroo/flappybird/blob/main/flappy.cpp#L1...

  for (std::size_t i {}; i < pipes.size(); ++i) {
C++ has had range based loops for years now, can use that instead here.

  for (auto& pipe : pipes)
I don't remember if the '&' is needed after the auto though.

anyway you should just use Rust, Zig, or Go...


In this case, where it is erasing one of the container elements inside the loop, the range-based loop would not be right.

But the code is busted anyway. After it erases the pipe (which is always at index zero) it then skips the pipe that was at index 1, if there was one.

Correct would be to do the check and erase before entering the loop. Then the range-based loop would better.


Yeah, since C++11 (the 2011 standard).

'&' isn't necessary to use auto but you would use it here to avoid making an unnecessary copy of the pipe.


This feels like it was written by someone who started learning C++ the day before he wrote this tutorial


The sad thing is that there are tons of people doing this in a professional environment. auto and shared_ptr all the things. exceptions everywhere. RTTI. <cstdio> <cmath>.


The code looks fine to me. If you see the newer C++23 guides like Lospinoso's C++ book, auto, shared_ptr, braces default value initializations are all considered best practices now. The only major issue I see is that OP should have used references instead of shared_ptrs for some of the variables.


Not a single one of the things he has as a shared pointer needed to be a pointer at all.


So I spent the last couple of hours on this. Pulled out all the shared_ptr and make_shared bollocks, played a couple of tricks with ld to embed the PNG images into the binary (since I don't do Windows or macOS, I'm OK with that), add some external references to these embedded images, created a CMakeFile that will figure out if you have SFML installed on the system etc.

Looks better already!


Where did you get the graphics from? Are they scrapped from the original game, some free resources or you draw them yourself?


google flappy bird sprites

There are tons of sites with all type of sprites, opengameart.org, for example.


Step by step code and also video tutorial


Can you share your vim setup? What plugins do you use and what does your config look like?


Getting compiler errors with clang, any experience with building on Mac?


Well... The video is 35 minutes in length, but you can tell it's been heavily edited. Even if you knew what you're doing you probably wouldn't be able to do it in 35 mins




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: