Olá mundo!
Vim aqui trazer alguns exemplos em C++ como funcionam classes, classes abstratas e interfaces.
Para que o texto fique mais claro para todos e possa ser adaptado para outras linguagens, a keyword "virtual" e a implementação de um método puramente virtual de C++ serão substituídas por macros.
PS. eu não sou um sênior, então tudo escrito aqui é do ponto de vista de um júnior que aprendeu POO recentemente.
pps. diante de feedbacks ficou claro que você precisa ter um mínimo de background para entender esse texto por completo, então eu recomendo pesquisar um pouco sobre como criar classes e o que é overload de função, pode ser qualquer linguagem, o importante é que você entenda o básico.
Index 🔗
Começando 🔰
Bom, para começar tenho que falar um pouco sobre Street Fighter para aqueles que não conhecem.
Street Fighter se trata de uma franquia de jogos de luta 2D, normalmente no padrão 1 VS 1 com melhor de 3.
Street Fighter tem mecânicas bem básicas, cada personagem tem uma barra de vida e uma barra de super, podem executar golpes fracos, golpes fortes, derrubadas, defesas, saltos, golpes no ar, golpes especiais e super golpes, mas no nosso caso só vamos nos preocupar com a barra de vida, a barra de super, golpes especiais e super golpes.
Classe 🤵♂️
Uma classe se trata de um molde de algo, normalmente chamamos uma instância desse molde de objeto, é daí que vem o termo "Programação Orientada a Objeto" (POO).
No caso desse texto iremos fazer moldes de personagens de Street Fighter, para não ficar muito longo vamos só fazer 2 deles, Ryu e Chun-Li.
#include <iostream>
#define CONSTRUTOR
#define DESTRUTOR
class Ryu {
public:
int vida;
int super;
CONSTRUTOR Ryu( void ): vida( 100 ), super( 0 ) { };
DESTRUTOR ~Ryu( void ) { };
// ... implementacao das outras acoes
void especialHadoken( void ) { };
void especialTatsumaki( void ) { };
void especialShoryuken( void ) { };
void superShinkuHadoken( void ) { };
void superShinShoryuken( void ) { };
};
class ChunLi {
public:
int vida;
int super;
CONSTRUTOR ChunLi( void ): vida( 100 ), super( 0 ) { };
DESTRUTOR ~ChunLi( void ) { };
// ... implementacao das outras acoes
void especialSpinningBirdKick( void ) { };
void especialHyakuretsukyaku( void ) { };
void especialKikoken( void ) { };
void superKikosho( void ) { };
void superHoyokusen( void ) { };
};
Ok, por enquanto isso parece bom, mas eu honestamente cortei parte dos especiais da Chun-Li e do Ryu, leve em consideração que também não implementei o que cada especial e super faz, quanto de dano causam e quanto da barra de super usam. Outro ponto negativo de fazer a classe diretamente é que o código se tornaria menos genérico, você teria que configurar um caminho para cada luta possível (ou fazer infinitos overload de função) e Street Fighter tem mais de 32 personagens (o número de combinações é X^2 considerando que você pode colocar um personagem para lutar consigo mesmo, com 44 você tem 1,936 combinações possíveis) e a cada novo jogo (ou DLC ou nova versão para a Capcom coletar uma grana) temos mais personagens.
Classe Abstrata 👨👔
Se uma classe é um molde, uma classe abstrata é o molde do molde. A forma mais simples de eu explicar uma classe abstrata é com fruta, imagine que de um lado você tem uma classe maçã e do outro uma classe banana, a classe abstrata de ambas é uma classe chamada fruta, pois o molde de uma maçã e uma banana é uma fruta, as duas tem nutrientes diferentes, formatos diferentes, sabores diferentes, mas ambas são classificadas como frutas (em um jogo você poderia classificar elas numa classe abstrata como item de cura ou comida).
Agora vamos abstrair Ryu e ChunLi por suas semelhanças, se você prestou atenção percebeu que eles têm em comum a barra de vida e de super.
#include <iostream>
#define CONSTRUTOR
#define DESTRUTOR
#define IDENTIFICA virtual
#define HERDA_DE : public
class ALutador {
public:
int vida;
int super;
CONSTRUTOR ALutador( void ): vida( 100 ), super( 0 ) { };
IDENTIFICA DESTRUTOR ~ALutador( void ) { };
};
class Ryu HERDA_DE ALutador {
public:
CONSTRUTOR Ryu( void ): ALutador( ) { };
DESTRUTOR ~Ryu( void ) { };
// ... implementacao das outras acoes
void especialHadoken( void ) { };
void especialTatsumaki( void ) { };
void especialShoryuken( void ) { };
void superShinkuHadoken( void ) { };
void superShinShoryuken( void ) { };
};
class ChunLi HERDA_DE ALutador {
public:
CONSTRUTOR ChunLi( void ): ALutador( ) { };
DESTRUTOR ~ChunLi( void ) { };
// ... implementacao das outras acoes
void especialSpinningBirdKick( void ) { };
void especialHyakuretsukyaku( void ) { };
void especialKikoken( void ) { };
void superKikosho( void ) { };
void superHoyokusen( void ) { };
};
Além de termos diminuído o código de cada classe, agora não precisamos codar um caminho para cada luta possível, visto que um objeto da classe pai consegue guardar uma classe filho (eu sei que muitas pessoas vão falar que uma classe abstrata não pode ser instanciada, porém, em C++ isso só acontece quando você tem um método puramente virtual), então agora só temos 1 caminho, uma função que recebe dois ALutador podem ser tanto 2 Ryu, 2 ChunLi ou 1 Ryu e 1 ChunLi (esse é o famoso conceito de polimorfismo), porém ainda temos um problema, se passarmos um Ryu como ALutador para lutar, ele não vai conseguir executar nenhum tipo de ataque, pois cada personagem tem um nome diferente de ataque.
Interface 🔦
Se classe abstrata é um molde de molde, podemos comparar interfaces como uma lanterna que vem sem pilhas, isso, pois a principal diferença entre classes abstratas e interfaces é que interfaces não possuem variáveis, apenas funções.
Vamos deixar Ryu e ChunLi mais genéricos para que não tenhamos que descubrir quem são.
#include <iostream>
#define CONSTRUTOR
#define DESTRUTOR
#define IDENTIFICA virtual
#define HERDA_DE : public
#define SEM virtual
#define IMPLEMENTACAO = 0
class IGolpes {
public:
DESTRUTOR virtual ~IGolpes( void );
SEM void especial1( void ) IMPLEMENTACAO;
SEM void especial2( void ) IMPLEMENTACAO;
SEM void especial3( void ) IMPLEMENTACAO;
SEM void super1( void ) IMPLEMENTACAO;
SEM void super2( void ) IMPLEMENTACAO;
};
class ALutador HERDA_DE IGolpes {
public:
int vida;
int super;
CONSTRUTOR ALutador( void ): vida( 100 ), super( 0 ) { };
IDENTIFICA DESTRUTOR ~ALutador( void ) { };
};
class Ryu HERDA_DE ALutador {
public:
CONSTRUTOR Ryu( void ): ALutador( ) { };
DESTRUTOR ~Ryu( void ) { };
// ... implementacao das outras acoes
void especial1( void ) { };
void especial2( void ) { };
void especial3( void ) { };
void super1( void ) { };
void super2( void ) { };
};
class ChunLi HERDA_DE ALutador {
public:
CONSTRUTOR ChunLi( void ): ALutador( ) { };
DESTRUTOR ~ChunLi( void ) { };
// ... implementacao das outras acoes
void especial1( void ) { };
void especial2( void ) { };
void especial3( void ) { };
void super1( void ) { };
void super2( void ) { };
};
Ok não reduzimos o código de Ryu e ChunLi, isso, pois fizemos com que Ryu e ChunLi tivessem métodos genéricos de especiais e supers, e vocês podem até pensar que seria melhor só implementar com o nome genérico, porém implementar por uma interface permite facilitar a expansão, afinal cada personagem pode ter 3 especiais, mas alguns podem ter 4, 6, 9, a interface permite que tenhamos 9 especiais e que apenas 3 deles sejam implementados para o Ryu e 4 deles para a ChunLi.
Conclusão 🏁
Espero que tenham gostado e entendido um pouco, vou deixar uma main aqui no final com um exemplo simples com o Ryu, e vou propor um desafio para quem quer mais.
#include <iostream>
#define CONSTRUTOR
#define DESTRUTOR
#define IDENTIFICA virtual
#define HERDA_DE : public
#define SEM virtual
#define IMPLEMENTACAO = 0
class IGolpes {
public:
virtual ~IGolpes( void ) {
std::cout << "CONTINUE?" << std::endl;
};
SEM void especial1( void ) IMPLEMENTACAO;
SEM void especial2( void ) IMPLEMENTACAO;
SEM void especial3( void ) IMPLEMENTACAO;
SEM void super1( void ) IMPLEMENTACAO;
SEM void super2( void ) IMPLEMENTACAO;
};
class ALutador HERDA_DE IGolpes {
public:
int vida;
int super;
CONSTRUTOR ALutador( void ): vida( 100 ), super( 0 ) { };
IDENTIFICA DESTRUTOR ~ALutador( void ) {
std::cout << "YOU LOSE!" << std::endl;
};
};
class Ryu HERDA_DE ALutador {
public:
CONSTRUTOR Ryu( void ): ALutador( ) { };
DESTRUTOR ~Ryu( void ) {
std::cout << "K.O!" << std::endl;
};
// ... implementacao das outras acoes
void especial1( void ) {
std::cout << "HADOKEN!" << std::endl;
super += 25;
};
void especial2( void ) { };
void especial3( void ) { };
void super1( void ) {
if (super >= 50) {
std::cout << "SHINKU HADOKEN!" << std::endl;
super -= 50;
}
};
void super2( void ) { };
};
class ChunLi HERDA_DE ALutador {
public:
CONSTRUTOR ChunLi( void ): ALutador( ) { };
DESTRUTOR ~ChunLi( void ) { };
// ... implementacao das outras acoes
void especial1( void ) { };
void especial2( void ) { };
void especial3( void ) { };
void super1( void ) { };
void super2( void ) { };
};
int main()
{
ALutador* ryu = new Ryu();
ryu->especial1();
ryu->especial1();
ryu->especial1();
ryu->super1();
delete ryu;
return 0;
}
O desafio é: adaptar isso para outros jogos, como as classes de armas de um Call of Duty ou Battlefield, ou então criar um bestiário de inimigos igual o de Castlevania Symphony of the Night, ou os diferentes carros de um Forza Horizon.
Bom isso é tudo, obrigado por ler até o final, como recompensa fique com esse momento épico no mundo dos jogos onde o jogador Daigo (jogando com o Ken) vira uma partida que ele tinha praticamente perdido.
edit 2024/03/23: adicionado pps.
Top comments (0)