Alternativas flexíveis a muitas pequenas classes polimórficas (para uso como propriedades, mensagens ou eventos) C ++

9

Existem duas classes no meu jogo que são realmente úteis, mas lentamente se tornando uma dor. Mensagem e propriedade (a propriedade é essencialmente um componente).

Ambos derivam de uma classe base e contêm um ID estático, de modo que os sistemas podem prestar atenção apenas àqueles que desejam. Está funcionando muito bem ... exceto ...

Estou constantemente criando novos tipos de mensagens e propriedades à medida que estendo meu jogo. Cada vez que eu preciso escrever 2 arquivos (hpp e cpp) e uma tonelada de clichê para obter essencialmente um classID e um ou dois tipos de dados padrão ou um ponteiro.

Está começando a fazer com que brincar e testar novas idéias seja uma tarefa real. Desejo que, quando desejar criar uma nova mensagem ou tipo de propriedade, queira poder digitar algo como

ShootableProperty:  int gunType, float shotspeed;

ItemCollectedMessage:  int itemType;

em vez de criar um arquivo de cabeçalho e cpp, escrever um construtor, incluindo a classe pai, etc.

São cerca de 20 a 40 linhas (incluindo guardas e tudo) apenas para fazer o que logicamente são 1 ou 2 linhas em minha mente.

Existe algum padrão de programação para contornar isso?

E quanto aos scripts (dos quais eu não sei nada) ... existe uma maneira de definir um monte de classes quase iguais?


Aqui está exatamente como uma classe se parece:

// Velocity.h

#ifndef VELOCITY_H_
#define VELOCITY_H_

#include "Properties.h"

#include <SFML/System/Vector2.hpp>

namespace LaB
{
namespace P
{

class Velocity: public LaB::Property
{
public:
    static const PropertyID id;

    Velocity(float vx = 0.0, float vy = 0.0)
    : velocity(vx,vy) {}
    Velocity(sf::Vector2f v) : velocity(v) {};

    sf::Vector2f velocity;
};

} /* namespace P */
} /* namespace LaB */
#endif /* LaB::P_VELOCITY_H_ */



// Velocity.cpp

#include "Properties/Velocity.h"

namespace LaB
{
namespace P
{

const PropertyID Velocity::id = Property::registerID();

} /* namespace P */
} /* namespace LaB */

Tudo isso apenas para um vetor 2D e um ID que espera um vetor 2D. (Concedido, algumas propriedades têm elementos mais complicados, mas é a mesma ideia)

Bryan
fonte
3
Dê uma olhada nisso, isso pode ajudá-lo. gameprogrammingpatterns.com/type-object.html
11
Tudo isso parece que os modelos podem ajudar, mas não com a inicialização estática. Certamente pode ser muito mais fácil nesse caso, mas é difícil dizer sem mais informações sobre o que você deseja fazer com esses IDs e por que você está usando a herança. Usar herança pública, mas nenhuma função virtual é definitivamente um cheiro de código ... Isso não é polimorfismo!
ltjax
Você encontrou a biblioteca de terceiros que a implementa?
Boris

Respostas:

1

C ++ é poderoso, mas é detalhado . Se o que você deseja são muitas classes polimórficas pequenas, todas diferentes, então sim, será necessário muito código-fonte para declarar e definir. Não há realmente nada a fazer sobre isso.

Agora, como ltjax disse, o que você está fazendo aqui não é exatamente polimorfismo , pelo menos para o código que você forneceu. Não vejo uma interface comum que oculte a implementação específica de subclasses. Exceto, talvez, o ID da classe, mas isso é realmente redundante com o ID da classe real: é o nome. Parece ser apenas um monte de classes contendo alguns dados, nada realmente complicado.

Mas isso não resolve o seu problema: você deseja criar muitas mensagens e propriedades com a menor quantidade de código. Menos código significa menos erros, portanto, é uma decisão sábia. Infelizmente para você não existe uma solução única. É difícil dizer o que melhor atenderia às suas necessidades sem saber exatamente o que você pretende fazer com essas mensagens e propriedades . Então deixe-me expor suas opções:

  1. Use modelos C ++ . Os modelos são uma ótima maneira de deixar o compilador "escrever código" para você. Está se tornando cada vez mais proeminente no mundo C ++, à medida que a linguagem evolui para suportá-los melhor (por exemplo, agora você pode usar um número variável de parâmetros de modelo ). Existe toda uma disciplina dedicada à geração automatizada de código por meio de modelos: metaprogramação de modelos . O problema é: é difícil. Não espere que os não programadores possam adicionar novas propriedades se planejarem usar essa técnica.

  2. Use macros C simples . É da velha escola, fácil de abusar demais e propenso a erros. Eles também são muito simples de criar. As macros são apenas pastas de cópias glorificadas executadas pelo pré-processador; portanto, elas são realmente adequadas para criar toneladas de coisas quase iguais, com pequenas variações. Mas não espere que outros programadores o amem por usá-los, pois as macros costumam ser usadas para ocultar falhas no design geral do programa. Às vezes, porém, são úteis.

  3. Outra opção é usar uma ferramenta externa para gerar o código para você. Já foi mencionado em uma resposta anterior, então não vou expandir isso.

  4. Existem outros idiomas que são menos detalhados e permitem criar classes com mais facilidade. Portanto, se você já possui ligações de script (ou planeja tê-las), definir essas classes no script pode ser uma opção. Acessá-los a partir do C ++ será bastante complicado, e você perderá a maior parte dos benefícios da criação simplificada de classes. Portanto, isso provavelmente é viável apenas se você planeja executar a maior parte da lógica do jogo em outro idioma.

  5. Finalmente, por último, mas não menos importante, você deve considerar o uso de um design orientado a dados . Você pode definir essas "classes" em arquivos de texto simples usando um layout semelhante ao que você se propôs. Você precisará criar um formato e um analisador personalizados para ele ou usar uma das várias opções já disponíveis (.ini, XML, JSON e outros enfeites). Então, no lado do C ++, você precisará criar um sistema que suporte uma coleção desses diferentes tipos de objetos. Isso é quase equivalente à abordagem de script (e provavelmente requer ainda mais trabalho), exceto que você poderá adaptá-lo com mais precisão às suas necessidades. E se você simplificar o suficiente, os não programadores poderão criar coisas novas sozinhos.

Laurent Couvidou
fonte
0

Que tal uma ferramenta de geração de código para ajudá-lo?

por exemplo, você pode definir seu tipo de mensagem e membros em um pequeno arquivo de texto e fazer com que a ferramenta de geração de código o analise e grave todos os arquivos C ++ padrão como uma etapa de pré-construção.

Existem soluções existentes, como ANTLR ou LEX / YACC, e não seria difícil criar as suas próprias, dependendo da complexidade de suas propriedades e mensagens.

Programador Crônico de Jogos
fonte
Apenas uma alternativa à abordagem LEX / YACC, quando tenho que gerar arquivos muito semelhantes com apenas pequenas modificações, uso um script python simples e pequeno e um arquivo de modelo de código C ++ contendo tags. O script python procura essas tags no modelo, substituindo-as pelos tokens representativos do elemento que está sendo criado.
victor
0

Aconselho você a pesquisar sobre os buffers de protocolo do Google ( http://code.google.com/p/protobuf/ ). Eles são uma maneira muito inteligente de lidar com mensagens genéricas. Você apenas precisa especificar as propriedades em um padrão de estrutura e um gerador de código faz todo o trabalho de gerar as classes para você (Java, C ++ ou C #). Todas as classes geradas possuem analisadores de texto e binários, o que as torna boas para inicialização e serialização de mensagens baseadas em texto.

dsilva.vinicius
fonte
Minha central de mensagens é o núcleo do meu programa - você sabe se os buffers de protocolo possuem uma grande sobrecarga?
Bryan
Eu não acho que eles tenham uma sobrecarga grande, pois a API é apenas uma classe Builder para cada mensagem e a classe para a própria mensagem. O Google os utiliza para o núcleo de sua infraestrutura. Por exemplo, as entidades do Google App Engine são todas convertidas em buffers de protocolo antes de persistirem. Em caso de dúvida, aconselho que você implemente um teste de comparação entre sua implementação atual e os buffers de protocolo. Se você acha que a sobrecarga é aceitável, use-as.
Dsilva.vinicius 26/09