Skip to content

Tutorial SFML

igorosberg edited this page May 24, 2019 · 81 revisions

Desenhando formas geométricas

O projeto codeblocks possui o arquivo sfml.cpp que implementa várias funções para desenho de formas, tais como ellipse(), rect(), line(), point(), quad(), triangle(). Essas funções são implementadas usando os recursos da biblioteca SFML e visam facilitar a implementação do jogo. Esse tutorial é baseado no tutorial sobre desenvolvimento de jogos com p5.js, feito pelo Professor Orivaldo Santana.

Para começar, digite o código abaixo no seu arquivo main.cpp do projeto que você baixou aqui. (Se ainda não havia baixado o projeto nem configurado o codeblocks, siga o tutorial de configuração e após terminar retorne).

#include "sfml.h"

int main() {

    //cria a janela com tamanho 640x480 pixels
    SFML sfml(640,480,"Meu jogo!");

    //define a cor de fundo como preto
    sfml.background(0,0,0);

    //início do laço principal do jogo
    while (sfml.windowIsOpen()) {

        //apaga o conteúdo da janela
        sfml.clear();

        //Desenha uma ellipse no centro da tela
        sfml.ellipse(320-50, 240-50, 100, 100);

        //mostra o conteúdo na janela
        sfml.display();

    }

    return 0;
}

No caso do código acima, é desenhado um círculo de tamanho 100x100 no centro da tela (320,240). Para isso, usamos a função ellipse([coordenada_centro x],[coordenada_centro y], [largura], [altura]). O resultado da execução desse código aparece na imagem abaixo.

Podemos também brincar com a aparência dessas formas. No seu main.cpp, digite o código seguinte:

#include "sfml.h"

int main() {

    //cria a janela com tamanho 640x480 pixels
    SFML sfml(640,480,"Meu jogo!");

    //define a cor de fundo como preto
    sfml.background(0,0,0);

    //início do laço principal do jogo
    while (sfml.windowIsOpen()) {

        //apaga o conteúdo da janela
        sfml.clear();

        //desativa o preenchimento
        sfml.noFill();
        //ativa a borda com cor azul
        sfml.stroke(0, 0, 255);
        //desenha uma ellipse sem preenchimento com borda
        //azul, com centro no ponto (200,100)
        sfml.ellipse(200-60, 100-30, 120, 60);
        //desativa a borda
        sfml.noStroke();
        //ativa o preenchimento
        sfml.fill(255, 0, 0);
        //desenha um retângulo com preenchimento vermelho
        //e sem borda, com centro no ponto(200,200)
        sfml.rect(200-40, 200-40, 80, 80);
        //ativa a borda
        sfml.stroke(60, 150, 60);
        //define a largura da borda como 3 pixels
        sfml.strokeWeight(3);
        //desenha uma linha com largura de 3 pixels
        sfml.line(100, 300, 400, 400);

        //mostra o conteúdo na janela
        sfml.display();

    }

    return 0;
}

O resultado da compilação e execução do código acima é mostrado na imagem abaixo

A linha RenderWindow window() cria uma janela com tamanho 640x480, e a linha background() define sua cor de fundo. As funções fill() e stroke(), definem as cores de preenchimento e de contorno, respectivamente. Uma vez utilizadas essas funções, seus efeitos valerão para todas as formas declaradas abaixo. noFill() e noStroke() retiram o preenchimento e o contorno, respectivamente, e strokeWeight() define uma espessura para o contorno da forma. Essas funções são implementadas no arquivo draw.cpp usando os recursos da biblioteca SFML e visam facilitar a implementação do jogo.

Reconhecendo eventos do mouse

Agora vejamos algo mais interessante...

O laço while(window.isOpen()){} ficará se repetindo no decorrer da execução do programa. Nele poderemos fazer algo mudar seu valor ao longo do tempo. Um exemplo é a posição da ellipse, que pode ser alterada de acordo com a posição do mouse! Escreva o código abaixo no arquivo main.cpp para implementar esse comportamento.

#include "sfml.h"

int main() {

    SFML sfml(640,480,"Meu jogo!");
    sfml.background(255,255,255);
    sfml.clear();
    sfml.noFill();
    sfml.stroke(0,0,0);

    while (sfml.windowIsOpen()) {

        //verifica se o usuário está pressionando o botão esquerdo do mouse
        if (sfml.mouseLeftButtonIsPressed()) {
            //desenha uma ellipse na posição do ponteiro do mouse
            sfml.ellipse(sfml.mouseX()-50, sfml.mouseY()-50, 100, 100);
        }
        sfml.display();

    }

    return 0;
}

O teste if (Mouse::isButtonPressed(Mouse::Button::Left)) verifica se o usuário está pressionando o botão esquerdo do mouse. Caso esteja, é desenhado um círculo sem preenchimento e com borda preta, na posição corrente do mouse. Veja imagem abaixo.

Gerando números aleatórios

A função random(min, max) também pode ser muito útil para diversas aplicações. O código abaixo desenha linhas com posições de início e fim aleatórias, enquanto o jogador mantém o botão esquerdo do mouse pressionado.

#include "sfml.h"
#include "util.h"

using namespace util;

int main() {

    SFML sfml(640,480,"Meu jogo!");
    sfml.background(255,255,255);
    sfml.stroke(0,0,0);
    sfml.clear();

    while (sfml.windowIsOpen()) {
        //verifica se o usuário está pressionando o botão esquerdo do mouse
        if(sfml.mouseLeftButtonIsPressed()) {
            //desenha uma linha com posição duas extremidades em posições aleatórias da tela
            sfml.line(random(0,640),random(0,480),random(0,640),random(0,480));
        }

        sfml.display();
    }

    return 0;
}

O resultado do código acima é mostrado na imagem abaixo.

Reconhecendo um evento de clique por vez

O código anterior desenha várias linhas loucamente enquanto o usuário mantém o botão esquerdo do mouse pressionado. Mas e se quisermos desenhar uma única linha a cada clique do mouse? Experimente executar o código abaixo e veja o que acontece ao clicar com o botão esquerdo do mouse.

#include "sfml.h"
#include "util.h"

using namespace util;

int main() {

    SFML sfml(640,480,"Meu jogo!");
    sfml.background(255,255,255);
    sfml.stroke(0,0,0);
    sfml.clear();

    bool drawline = true;

    while (sfml.windowIsOpen()) {

        if(sfml.mouseLeftButtonIsPressed()) {
            if(drawline) {
                sfml.line(random(0,640),random(0,480),random(0,640),random(0,480));
                //faz com que a linha seja desenhada novamente somente quando o usuário
                //deixar de pressionar o botão esquerdo do mouse e pressionar novamente
                drawline = false;
            }
        } else {
            drawline = true;
        }

        sfml.display();
    }

    return 0;
}

Movendo formas geométricas de maneira autônoma

Podemos usar variáveis para desenhar formas geométricas em posições diferentes. Basta usar variáveis para controlar as posições desses figuras. Veja o código abaixo.

#include "sfml.h"

int main() {

    SFML sfml(640,480,"Meu jogo!");
    sfml.background(0,0,0);

    float x = 200;

    while (sfml.windowIsOpen()) {

        sfml.clear();

        //Desenha uma ellipse com coordenada x definida pela variável x
        sfml.ellipse(x, 240-50, 100, 100);

        x += 2;
        if(x > 690) {
            x = -50;
        }

        sfml.display();

    }

    return 0;
}

A execução do código acima faz com que um círculo seja desenhado em diferentes posições, dando a impressão de movimento. No laço principal do jogo, verificamos se a posição x do círculo está dentro da nossa janela. Se sim, a variável x é incrementada fazendo com que o círculo ande pela tela na horizontal. Se o círculo sai da janela, a posição x volta para zero (0) e o ciclo recomeça. A janela está sendo limpada (sfml.clear()) dentro do laço principal para evitar que o círculo deixe um rastro na tela; primeiro é pintado um fundo, depois o círculo é pintado em uma posição x, depois o fundo é pintado novamente e só então um novo círculo é pintado, em uma nova posição x. Experimente retirar a linha sfml.clear() e veja o que acontece.

Usando o teclado para mover formas geométricas

No código abaixo utilizamos entradas do teclado para alterar os valores das variáveis criadas. Isso é feito através da função keyIsDown(), que retorna true sempre que uma tecla com o mesmo código que foi passado como parâmetro na função estiver sendo pressionada, e false caso contrário. Exemplos de possíveis valores são: Backspace, Delete, Enter, Tab, Escape, LShift, LControl, Up, Down, Left, Right. Para conhecer mais valores de teclas, consulte o arquivo sfml.h.

#include "sfml.h"

int main() {

    SFML sfml(640,480,"Meu jogo!");
    sfml.background(0,0,0);

    float x = 100;
    float y = 100;

    float speed = 5;

    while (sfml.windowIsOpen()) {

        sfml.clear();

        if (sfml.keyIsDown(SFML::Key::Left)) {
            x-=speed;
        } else if (sfml.keyIsDown(SFML::Key::Right)) {
            x+=speed;
        } else if (sfml.keyIsDown(SFML::Key::Up)) {
            y-=speed;
        } else if (sfml.keyIsDown(SFML::Key::Down)) {
            y+=speed;
        }

        sfml.ellipse(x, y, 50, 50);

        sfml.display();

    }

    return 0;
}

Desenhando formas geométricas em posições aleatórias

A aleatoriedade é muito útil no desenvolvimento de jogos. Por exemplo, um inimigo pode aparecer em uma posição aleatória do mapa cada vez que você começa o jogo. Na nossa biblioteca, podemos sortear números aleatórios através da função util::random, da biblioteca util.h. No código abaixo, a função random foi utilizada para definir uma posição aleatória para um retângulo, dentro da nossa tela. Observe que passamos dois valores como parâmetro para random. Tais valores são interpretados como o valor mínimo e máximo e o número escolhido estará na faixa [mínimo, máximo]. Note que após o retângulo sair da área da nossa tela, devemos definir as posições novamente, para que ele volte a aparecer no começo da tela.

Nesse exemplo também fazemos uso do parâmetro ângulo que define um ângulo de rotação para o retângulo. Como o ângulo varia, o retângulo gira enquanto desliza pela tela.

#include "sfml.h"
#include "util.h"

using namespace util;

int main() {

    SFML sfml(640,480,"Meu jogo!");
    sfml.background(0,0,0);

    int xo = -random(0,640);
    int yo = random(0,480);

    int angle = 0;

    while (sfml.windowIsOpen()) {

        sfml.clear();

        xo += 3;

        if(xo > 640) {
            xo = -random(0,640);
            yo = random(0,480);
        }

        sfml.rect(xo,yo,40,40, angle);

        angle = (angle+2)%360;

        sfml.display();
    }

    return 0;
}

Variáveis de estado

Introduziremos agora o conceito de variáveis de estado. Utilizar variáveis de estado é muito útil para quando precisamos que algo aconteça no programa somente quando alguma outra ação estiver sendo realizada. Por exemplo, você só poderá fazer provas de C2 enquanto a variável "cursando C2" estiver ativada, uma vez que essa variável seja desligada você não poderá mais fazer provas de C2 a não ser que "cursando C2" seja ligada novamente ('-'). Para criar uma variável de estado utilizamos as variáveis booleanas, pois estas possuem dois estados, true e false, que são usados para representar ligado e desligado.

Vejamos um exemplo prático dessas variáveis de estado.

#include "sfml.h"
#include "util.h"

using namespace util;

int main() {

    SFML sfml(640,480,"Meu jogo!");
    sfml.background(0,0,0);

    int yo = random(0,480);
    int xo = 70;
    bool podeMudar = true;

    while (sfml.windowIsOpen()) {

        sfml.clear();

        if(!podeMudar){
            xo += 5;
        }

        if(xo > 660){
            podeMudar = true;
        }

        if(sfml.mouseLeftButtonIsPressed() && podeMudar) {
            yo = random(0,480);
            xo = 15;
            podeMudar = false;
        }

        sfml.rect(xo,yo,40,40);

        sfml.display();
    }

    return 0;
}

Aqui a variável de estado é podeMudar. O programa funciona da seguinte forma: quando o mouse é clicado, uma posição é definida para o retângulo que logo então começará a andar para a direita. Uma vez em movimento, o clique do mouse não conseguirá mais mudar sua posição até que ele saia da área da tela. Assim, quando o retângulo estiver fora de vista, podeMudar é se torna verdadeira e a posição poderá ser alterada com um clique do mouse. O retângulo então entra mais uma vez em movimento e podeMudar se torna falsa. Com isso o clique do mouse não funcionará até que o retângulo saia de vista novamente.

Desenhando texto na tela

Em alguns casos, precisaremos escrever algo na nossa tela. Podemos fazer isso usando a função text(), desse modo:

#include "sfml.h"
#include <cstring>

int main() {

    SFML sfml(640,480,"Meu jogo!");
    sfml.background(0,0,0);

    //Define o tamanho da fonte
    sfml.textSize(16);

    char frase[200];
    int contagem = 0;
    int posX = 0;
    int posY = 200;

    while (sfml.windowIsOpen()) {

        sfml.clear();

        //cria a frase que será desenhada no vetor de caracteres frase.
        sprintf(frase,"Já passaram %d bolas",contagem);

        //escreve a frase na tela
        sfml.text(frase, 250, 15);

        if (posX < 640){
            posX = posX + 5;
        }else{
            contagem++;
            posX = 0;
        }

        sfml.ellipse(posX, posY, 50, 50);

        sfml.display();
    }

    return 0;
}

O resultado do código acima é mostrado na figura abaixo.

Calculando a distância entre dois pontos.

Outra função bastante utilizada é a dist(x1, y1, x2, y2) da biblioteca util.h, que retorna a distância entre duas posições informadas. Podemos utilizá-la de diversas formas, veremos um exemplo aqui.

#include "sfml.h"
#include "util.h"
#include <cstring>

using namespace util;

int main() {

    SFML sfml(640,480,"Meu jogo!");
    sfml.background(200,200,200);
    sfml.fill(0,0,0);

    sfml.textSize(18);

    int x1 = 10;
    int y1 = 90;
    char frase[100];

    while (sfml.windowIsOpen()) {

        sfml.clear();

        int x2 = sfml.mouseX();
        int y2 = sfml.mouseY();

        sfml.line(x1, y1, x2, y2);
        sfml.ellipse(x1-4, y1-4, 8, 8);
        sfml.ellipse(x2-4, y2-4, 8, 8);

        //calcula a distância entre os pontos (x1,y1) e (x2,y2)
        int d = dist(x1+4, y1+4, x2+4, y2+4);

        sprintf(frase,"A distância entre os pontos é %d pixels.",d);

        sfml.text(frase, 80, 400);

        sfml.display();
    }

    return 0;
}

Saída:

Detectando colisão entre figuras desenhadas

A detecção de colisão está presente em boa parte dos jogos. Através dela objetos ou personagens podem interagir. Podemos usar a função dist(x1,y1,x2,y2) para detectar colisão entre objetos. Veja o exemplo abaixo.

#include "sfml.h"
#include "util.h"

using namespace util;

int main() {

    SFML sfml(640,480,"Meu jogo!");
    sfml.background(0,0,0);

    float xplayer = 100;
    float yplayer = 100;

    int xenemy = -random(0,640);
    int yenemy = random(0,480);

    float speed = 5;

    while (sfml.windowIsOpen()) {

        sfml.clear();

        bool isColliding = (dist(xplayer+25,yplayer+25,xenemy+25,yenemy+25) < 25+25) ? true : false;

        if(!isColliding) {
            xenemy += 1;
        } else {
            sfml.text("Colidiu!",10,10);
        }

        if(xenemy > 640) {
            xenemy = -random(0,640);
            yenemy = random(0,480);
        }

        if (sfml.keyIsDown(SFML::Key::Left)) {
            xplayer-=speed;
        } else if (sfml.keyIsDown(SFML::Key::Right)) {
            xplayer+=speed;
        } else if (sfml.keyIsDown(SFML::Key::Up)) {
            yplayer-=speed;
        } else if (sfml.keyIsDown(SFML::Key::Down)) {
            yplayer+=speed;
        }

        sfml.ellipse(xplayer, yplayer, 50, 50);
        sfml.rect(xenemy, yenemy, 50, 50);

        sfml.display();

    }

    return 0;
}

No código acima, o jogador movimenta o círculo usando as setas direcionais do teclado. Caso o círculo se aproxime do retângulo, de modo que a distância seja menor do que a soma do raio do círculo mais metade da largura do retângulo, o retângulo para de se mover e é mostrada a mensagem "Colidiu!" no top esquerdo da tela, conforme mostra imagem abaixo.

Desenhando imagens

A arte de um jogo é um fator diferencial para seu sucesso. Usando imagens podemos criar jogos mais bonitos e apelativos. Vamos aprender como desenhar imagens agora? Veja o código abaixo e note como é simples.

#include "sfml.h"

int main() {

    SFML sfml(640,480,"Meu jogo!");
    sfml.background(0,0,0);

    Image image("zelda.png");

    while (sfml.windowIsOpen()) {

        sfml.clear();

        sfml.image(image,320-125,240-125);

        sfml.display();

    }

    return 0;
}

Nesse código, a linha Image image("zelda.png") carrega uma imagem que está no mesmo diretório o projeto. No laço principal do jogo, desenhamos a imagem no centro da tela.

Criando animações com imagens

Além de imagens estáticas como a do exemplo anterior, é comum em jogos o uso de animações. Uma animação consiste no desenho de várias imagens em sequência, dando a impressão de ação/movimento. Também podemos implementar animação imprimindo partes de uma imagem maior. O código abaixo usa essa técnica. Veja.

#include "sfml.h"

int main() {

    SFML sfml(640,480,"Meu jogo!");
    sfml.background(0,0,0);

    Image image("background.png");

    int x = 0;

    while (sfml.windowIsOpen()) {

        sfml.clear();

        sfml.image(image,0,0,x,0,640,480);
        x = (x+1)%1280;

        sfml.display();

    }

    return 0;
}

O programa acima carrega a imagem "background.png", cujas dimensões são 1920x480. Perceba que a altura é igual a altura da nossa tela e a largura é o triplo da largura da tela. Veja na imagem abaixo que o último terço da imagem backgroung.png (destacado e azul) é exatamente igual ao primeiro terço (destacado em vermelho). Assim, na linha sfml.image(image,0,0,x,0,640,480) o programa vai fazendo recortes de tamanho 640x480 variando o início do recorte em x=0 até x=1280. Quando x atinge 1281, estamos no caso em que o recorte será exatamente igual ao primeiro terço da imagem. Assim podemos fazer x=0 novamente e começar o processo novamente. Chamamos esse efeito de scrolling.

Animações com sprites

Outra técnica de animação utiliza sprites: uma imagem divida em partes iguais, onde cada parte é uma fotografia diferente do mesmo objeto. Abaixo temos um exemplo de sprite que representa uma animação de uma explosão.

Essa imagem tem tamanho 900x900. Cada subimagem tem tamanho 100x100. Quando o usuário clica em alguma posição da tela, o código abaixo faz recortes dos sprites, da esquerda para a direta, de cima para baixo e os desenha na tela.

#include "sfml.h"

int main() {

    SFML sfml(640,480,"Meu jogo!");
    sfml.background(0,0,0);

    Image image("explosion-sprite.png");

    bool animating = false;
    int i = 0;
    int x, y;
    int w = 100; //largura do objeto no sprite
    int h = 100; //altura do objeto no sprite


    while (sfml.windowIsOpen()) {

        sfml.clear();

        if(sfml.mouseLeftButtonIsPressed() && !animating) {
            animating = true;
            x = sfml.mouseX();
            y = sfml.mouseY();
        }

        if(animating) {
            sfml.image(image,x-w/2,y-h/2,(i%9)*w,(i/9)*h,w,h);
            i++;
            if(i == 81) {
                animating = false;
                i = 0;
            }
        }

        sfml.display();

    }

    return 0;
}

Adicionando sons

Os recursos de áudio trazem mais emoção aos jogos. Tocar um áudio com a nossa biblioteca é fácil. Vamos adaptar o código anterior para tocar um áudio sempre que a animação da explosão iniciar. Veja o código adaptado abaio.

#include "sfml.h"

int main() {

    SFML sfml(640,480,"Meu jogo!");
    sfml.background(0,0,0);

    Image image("explosion-sprite.png");

    Sound sound("explosion.wav");

    bool animating = false;
    int i = 0;
    int x, y;
    int w = 100; //largura do objeto no sprite
    int h = 100; //altura do objeto no sprite

    while (sfml.windowIsOpen()) {

        sfml.clear();

        if(sfml.mouseLeftButtonIsPressed() && !animating) {
            sound.play();
            animating = true;
            x = sfml.mouseX();
            y = sfml.mouseY();
        }

        if(animating) {
            sfml.image(image,x-w/2,y-h/2,(i%9)*w,(i/9)*h,w,h);
            i++;
            if(i == 81) {
                animating = false;
                i = 0;
            }
        }

        sfml.display();

    }

    return 0;
}

Efeito de salto

Em jogos de plataforma é comum que o personagem salte. O código abaixo simula um salto quando o usuário aperta a seta para cima do teclado. Para tal usa-se a equação do espaço do lançamento vertical.

#include "sfml.h"

int main() {

    SFML sfml(640,480,"Meu jogo!");
    sfml.background(0,0,0);

    float x = 100;
    float y = 400;

    bool saltando = false;

    float speed = 5;

    float t = 0;  //tempo de duração do lançamento
    int vy0 = 55; //velocidade inicial no lançamento vertical
    int y0 = 400; //posição inicial no lançamento vertical. corresponde ao chão
    int g = 9.8;  //força da gravidade no lançamento vertical

    while (sfml.windowIsOpen()) {

        sfml.clear();

        if (sfml.keyIsDown(SFML::Key::Left)) {
            x-=speed;
        } else if (sfml.keyIsDown(SFML::Key::Right)) {
            x+=speed;
        } else if (sfml.keyIsDown(SFML::Key::Up) && !saltando) {
            saltando = true;
            t = 0;
        }

        if(saltando) {
            y = y0 - vy0*t + (g*t*t)/2.0; //atualiza y segundo a equação do lançamento vertical
            if(y > y0) { // verifica se y já atingiu o chão. se atingiu o chão, o salto acabou
                y = y0;
                saltando = false;
            }
            t += 0.1;
        }

        sfml.line(0,450,640,450);

        sfml.ellipse(x, y, 50, 50);

        sfml.display();

    }

    return 0;
}

Efeito de tiro

O lançamento de objetos ou projéteis é importante na maioria dos jogos. O código abaixo apresenta uma das formas de implementar o efeito de tiro ao apertar a tecla barra de espaço. A função sfml.convexPolygon(x,y,pontos,qtdPontos) desenha um polígolo convexo definidos pelo vetor pontos, centrado em (x,y).

#include "sfml.h"
#include<cmath>

struct Coordenada {
    int x, y;
};

struct Nave {
    Coordenada c;
    bool atirando;
};

struct Tiro {
    Coordenada c;
};

void desenhaNave(SFML& sfml, Nave nave) {
    int points[][2] = {
        {20,40},{0,-20},{-20,40},{0,20}
    };
    sfml.convexPolygon(nave.c.x,nave.c.y,points,4);
}

int main() {

    //cria a janela com tamanho 640x480 pixels
    SFML sfml(640,480,"Meu jogo!");

    //define a cor de fundo como preto
    sfml.background(0,0,0);

    Nave nave;
    nave.c.x = 320;
    nave.c.y = 430;
    nave.atirando = false;

    Tiro tiro;

    //início do laço principal do jogo
    while (sfml.windowIsOpen()) {

        //apaga o conteúdo da janela
        sfml.clear();

        //Desenha uma ellipse no centro da tela

        if(sfml.keyIsDown(SFML::Key::Left)) {
            nave.c.x -= 2;
        }
        else if(sfml.keyIsDown(SFML::Key::Right)) {
            nave.c.x += 2;
        }
        else if(sfml.keyIsDown(SFML::Key::Space)) {
            if(!nave.atirando) {
                nave.atirando = true;
                tiro.c.x = nave.c.x-2;
                tiro.c.y = nave.c.y-2;
            }
        }

        desenhaNave(sfml,nave);

        if(nave.atirando) {
            sfml.ellipse(tiro.c.x, tiro.c.y,4,4);
            tiro.c.y -= 2;
            if(tiro.c.y < 0-10) {
                nave.atirando = false;
            }
        }

        sfml.display();

    }

    return 0;
}

Persistindo dados em arquivos

A maioria dos jogos usa algum tipo de persistência de dados para salvar o progresso ou pontuação de um jogo. O exemplo abaixo usa o biblioteca fstream da linguagem c++ para salvar o número de defesas feitas ao decorrer do jogo. Ao fechar e reabrir o jogo, a quantidade de defesas é recuperada do arquivo e mostrada no topo da tela.

#include "sfml.h"
#include "util.h"

#include <fstream>

using namespace util;
using namespace std;

struct coord {
    int x, y;
};

int main() {

    SFML sfml(640,480,"Meu jogo!");
    sfml.background(0,0,0);

    coord goleiro = {40,480/2};
    coord bola = {800,480/2};

    int velocidadeGoleiro = 5;
    int velocidadeBola = 3;

    int score;
    char str[200];

    ifstream in("defesas.txt",std::ifstream::in);
    if(in.is_open()) {
        in >> score;
    } else {
        score = 0;
    }

    while (sfml.windowIsOpen()) {

        sfml.clear();

        if (sfml.keyIsDown(SFML::Key::Up)) {
            goleiro.y-=velocidadeGoleiro;
            if(goleiro.y < 60) {
                goleiro.y = 60;
            }
        } else if (sfml.keyIsDown(SFML::Key::Down)) {
            goleiro.y+=velocidadeGoleiro;
            if(goleiro.y > 410) {
                goleiro.y = 410;
            }
        }

        bola.x -= velocidadeBola;

        bool isColliding = (dist(goleiro.x+25,goleiro.y+25,bola.x+10,bola.y+10) < 25+10) ? true : false;
        if(isColliding) {
            score++;
            bola.x = random(640,1.5*640);
            bola.y = random(60,380);
        } else if(bola.x < 0) {
            bola.x = random(640,1.5*640);
            bola.y = random(60,380);
        }

        sprintf(str,"Defesas: %d",score);

        sfml.line(0,40,640,40);

        sfml.ellipse(goleiro.x, goleiro.y, 50, 50);

        sfml.ellipse(bola.x, bola.y, 20, 20);

        sfml.rect(20,60,10,400);

        sfml.text(str,10,10);

        sfml.display();

    }

    ofstream out("defesas.txt",std::ofstream::out);
    out << score;
    out.close();

    return 0;
}

Conclusão

Nesse tutorial aprendemos a usar uma biblioteca de desenvolvimento de jogos simples que abstrai alguns recursos da biblioteca SFML. Agora você já possui habilidades básicas necessárias para desenvolver seu próprio jogo. Bons estudos.