Flappy Bird is a mobile game developed by Vietnamese video game artist and programmer Dong Nguyen (Vietnamese: Nguyễn Hà Đông), under his game development company .Gears.
The game is a side-scroller where the player controls a bird, attempting to fly between columns of green pipes without hitting them. Nguyen created the game over the period of several days, using a bird protagonist that he had designed for a cancelled game in 2012.
In this article we will see how to recreate with C++ and SFML, at the end of the article there is also a video that demonstrates in video all the steps done here.
Declare and initialize core members and member functions.
Create the files:
Makefile
flappy.cpp
flappy.hpp
main.cpp
./resources/ # img/font
Click to expand
flappy.hpp
#pragma once
#include <SFML/Graphics.hpp>
#include <memory>
class FlappyBird {
std::shared_ptr<sf::RenderWindow> window;
float gravity, frame, space;
int count;
sf::Texture bg, flappy, pipe;
std::shared_ptr<sf::Sprite> background, bird, pipeBottom, pipeTop;
std::vector<sf::Sprite> pipes;
protected:
void events();
void draw();
public:
FlappyBird();
void run();
};
flappy.cpp
#include "flappy.hpp"
FlappyBird::FlappyBird(){
window = std::make_shared<sf::RenderWindow>(
sf::VideoMode(1000,600),
"FlappyBird (remix)",
sf::Style::Titlebar | sf::Style::Close
);
window->setFramerateLimit(60);
window->setPosition(sf::Vector2i(0, 0));
gravity = frame = {0};
space = {160.f};
count = {0};
bg.loadFromFile("./resources/img/background.png");
flappy.loadFromFile("./resources/img/flappy.png");
pipe.loadFromFile("./resources/img/pipe.png");
background = std::make_shared<sf::Sprite>();
bird = std::make_shared<sf::Sprite>();
pipeBottom = std::make_shared<sf::Sprite>();
pipeTop = std::make_shared<sf::Sprite>();
background->setTexture(bg);
bird->setTexture(flappy);
pipeBottom->setTexture(pipe);
pipeTop->setTexture(pipe);
bird->setPosition(
500.f - flappy.getSize().x / 2.f,
300.f - flappy.getSize().y / 2.f
);
bird->setScale(2.f, 2.f);
bird->setTextureRect(sf::IntRect(0, 0, 34, 24));
pipeBottom->setScale(1.5f, 1.5f);
pipeTop->setScale(1.5f, -1.5f);
// TEMP
pipeBottom->setPosition(100, 200);
pipeTop->setPosition(100, 100);
}
void FlappyBird::events(){
auto e = std::make_shared<sf::Event>();
while(window->pollEvent(*e)){
if( e->type == sf::Event::Closed){
window->close();
}
}
}
void FlappyBird::draw(){
window->clear(sf::Color::Black);
window->draw(*background);
window->draw(*bird);
window->draw(*pipeBottom);
window->draw(*pipeTop);
window->display();
}
void FlappyBird::run(){
while(window->isOpen() ){
events();
draw();
}
}
main.cpp
#include "flappy.hpp"
int main(){
auto flappy = std::make_shared<FlappyBird>();
flappy->run();
return 0;
}
Makefile
TARGET=a.out
CXX=g++
DEBUG=-g
OPT=-O2
WARN=-Wall
SFML=-lsfml-graphics -lsfml-window -lsfml-system
CXXFLAGS=$(DEBUG) $(OPT) $(WARN) $(SFML)
LD=g++
OBJS=main.o flappy.o
all: $(OBJS)
$(LD) -o $(TARGET) $(OBJS) $(CXXFLAGS)
@rm *.o
@./$(TARGET)
main.o: main.cpp
$(CXX) -c $(CXXFLAGS) main.cpp -o main.o
flappy.o: flappy.cpp
$(CXX) -c $(CXXFLAGS) flappy.cpp -o flappy.o
// TEMP
pipeBottom->setPosition(100, 200);
pipeTop->setPosition(100, 100);
flappy.hpp
void game();
void movePipes();
flappy.cpp
...
window->display();
++count;
}
void FlappyBird::movePipes(){
void FlappyBird::game(){
Add to main.cpp
std::srand(time(0));
movePipes()
:void FlappyBird::movePipes(){
if( count % 150 == 0 ){
int pos = std::rand() % 275 + 175;
pipeBottom->setPosition(1000, pos + space);
pipeTop->setPosition(1000, pos);
pipes.push_back(*pipeBottom);
pipes.push_back(*pipeTop);
}
for (std::size_t i {}; i < pipes.size(); ++i) {
if( pipes[i].getPosition().x < -100){
pipes.erase(pipes.begin() + i);
}
pipes[i].move(-3, 0);
}
}
Add game()
to run()
Replace window->draw(*pipeBottom); window->draw(*pipeTop);
with:
for(auto &p : pipes){
window->draw(p);
}
So he can move his wings
flappy.hpp
:void setAnimeBird();
flappy.cpp
:void FlappyBird::setAnimeBird(){
frame += 0.15f;
if( frame > 3 ){
frame -= 3;
}
bird->setTextureRect(sf::IntRect(34 * (int)frame, 0, 34, 24));
}
game()
:void FlappyBird::game(){
setAnimeBird();
movePipes();
}
flappy.hpp
:void moveBird();
flappy.cpp
void FlappyBird::moveBird(){
bird->move(0, gravity);
gravity += 0.5f;
}
game()
:setAnimeBird();
moveBird();
movePipes();
movePipes()
before if..
:if( sf::Mouse::isButtonPressed(sf::Mouse::Left)){
gravity = -8.f;
}
if( sf::Mouse::isButtonPressed(sf::Mouse::Left)){
gravity = -8.f;
bird->setRotation(-frame - 10.f);
}else{
bird->setRotation(frame - 10.f);
}
flappy.hpp
bool gameover;
flappy.cpp
(constructor):gameover = {false};
movePipes()
:...
for (std::size_t i {}; i < pipes.size(); ++i) {
if(pipes[i].getGlobalBounds().intersects(bird->getGlobalBounds())){
gameover = true;
}
...
game()
:void FlappyBird::game(){
if(!gameover){
setAnimeBird();
moveBird();
movePipes();
}
}
This will freeze the screen
window->draw(*bird)
to after pipes:for(auto &p : pipes){
window->draw(p);
}
window->draw(*bird);
flappy.hpp
sf::Font font;
sf::Text txt_score, txt_gameover;
flappy.cpp
(construtor)font.loadFromFile("./resources/font/flappy-font.ttf");
txt_gameover.setFont(font);
txt_gameover.setString("Press SPACE to restart");
txt_gameover.setPosition(200, 300);
txt_gameover.setCharacterSize(50);
txt_gameover.setOutlineThickness(3);
draw()
:window->draw(*bird);
if(gameover){
window->draw(txt_gameover);
}
Add score
and equal to zero in the constructor.
To restart the game. Add to events()
:
if( gameover && sf::Keyboard::isKeyPressed(sf::Keyboard::Space) ){
score = 0;
pipes.clear();
bird->setPosition(
500.f - flappy.getSize().x / 2.f,
300.f - flappy.getSize().y / 2.f
);
gameover = false;
}
flappy.hpp
: int count, score;
txt_score.setFont(font);
txt_score.setString(std::to_string(score));
txt_score.setPosition(10, 10);
txt_score.setCharacterSize(50);
txt_score.setOutlineThickness(3);
movePipes()
after bird->move(..
...
pipes[i].move(-3, 0);
if( pipes[i].getPosition().x == 298 ){
txt_score.setString(std::to_string(++score));
}
...
draw()
:window->draw(txt_score);
flappy.hpp
→ bool gameover, add;
flappy.cpp
→ add = {false};
if( pipes[i].getPosition().x == 298 && !add ){
txt_score.setString(std::to_string(score));
add = true;
}else{
add = false;
}
score
after gameover
in events()
after score = 0;
:txt_score.setString(std::to_string(++score));
Take advantage and reset
gravity = 0.f;
too.
The video is in Portuguese, but you can follow the steps even if you don’t understand the language. You can also enable subtitles and use Youtube’s automatic translation.
Enjoy!