Estou tentando escrever o módulo gráfico do meu mecanismo. Ou seja, esta parte do código fornece apenas uma interface através da qual carregar imagens, fontes, etc. e desenhá-las na tela. Também é um invólucro para a biblioteca que estou usando (SDL neste caso).
Aqui estão as interfaces para os meus Image
, Font
e GraphicsRenderer
classes. Por favor, diga-me se estou seguindo o caminho certo.
Imagem
class Image
{
public:
Image();
Image(const Image& other);
Image(const char* file);
~Image();
bool load(const char* file);
void free();
bool isLoaded() const;
Image& operator=(const Image& other);
private:
friend class GraphicsRenderer;
void* data_;
};
Fonte
class Font
{
public:
Font();
Font(const Font& other);
Font(const char* file, int ptsize);
~Font();
void load(const char* file, int ptsize);
void free();
bool isLoaded() const;
Font& operator=(const Font& other);
private:
friend class GraphicsRenderer;
void* data_;
};
GrapphicsRenderer
class GraphicsRenderer
{
public:
static GraphicsRenderer* Instance();
void blitImage(const Image& img, int x, int y);
void blitText(const char* string, const Font& font, int x, int y);
void render();
protected:
GraphicsRenderer();
GraphicsRenderer(const GraphicsRenderer& other);
GraphicsRenderer& operator=(const GraphicsRenderer& other);
~GraphicsRenderer();
private:
void* screen_;
bool initialize();
void finalize();
};
Editar: algumas alterações no código e mais detalhes.
Por algumas das discussões aqui, decidi substituir meu uso void*
por algo assim:
class Image
{
private:
struct ImageData;
std::shared_ptr<ImageData> data_;
};
(Obviamente, farei o mesmo pela Font
turma.)
Também devo mencionar que essas não são minhas aulas finais e completas. Eu só mostro aqui a funcionalidade básica (carregamento e renderização). Em vez de me dizer que funcionalidade você acha que devo adicionar (rotação de imagens, inclinação, redimensionamento, etc.), concentre-se em revisar o que eu já tenho. Vou tentar defender minhas escolhas, ou mudar de abordagem, se não puder.
fonte
Respostas:
O que mais me coça é o
void *
'abuso'.Bem, você pode evitar a inclusão (que eu aprovo), declarando-a em algum lugar conveniente para você.
fonte
struct FontData
e terei umstd::shared_ptr<FontData>
membro como privado em vez dovoid*
. Isso significa que também posso evitar a declaração de encaminhamento, o que significa que não há menção da biblioteca no arquivo de cabeçalho. :)Sobre interfaces (em geral)
Então, você nos pediu para revisar seus projetos para interfaces.
Você não nos deu interfaces, nos deu declarações de classe completas. Se estas fossem interfaces , eu esperaria ver algo como:
Isso , em C ++, é uma interface. Eu posso substituí-lo em uma subclasse que implementa funcionalidade (de fato, devo!). Se você está escrevendo uma interface, está aplicando a política, e o que precede é como você faz isso.
Metade das reclamações sobre o uso de void * nas outras respostas seria evitada se você tivesse acabado de expor as funções da interface e mantivesse as variáveis membro ocultas (como deveriam ser, em uma classe de interface ).
Rawr.
Em interfaces (sua)
Imagem: Copiando
Você tem um construtor de cópias e um operador igual. O problema que vejo aqui é que não há uma boa maneira de impedir que o usuário faça cópias estranhas e tolas de imagens.
Para você, usando SDL_surfaces, esse é um grande problema . Sem querer ser ofensivo, estou disposto a apostar que você não considerou o que acontece quando libera uma imagem que é uma duplicata de outra imagem. Estou ainda disposto a apostar que você não planejou lidar com cópias profundas e profundas do SDL_surface; portanto, no caso mencionado, você provavelmente liberará uma imagem e suas outras cópias explodirão, matando todos os que você ama .
Solução: SEM CÓPIAS. Não faça isso, não permita. Use uma função de fábrica ou um carregador de estilo C para criar novas instâncias de uma imagem e use-as em vez de permitir cópias ou atribuições iguais. Como alternativa, descubra completamente como copiar uma imagem SDL em profundidade (não muito difícil, mas irritante).
Imagem: manipulação
Como altero suas imagens depois de carregá-las? De acordo com a sua interface, eu não. Ainda assim, você tem certeza de que é uma boa ideia? Como descubro a profundidade de bits de uma imagem? Sua altura? Largura? Espaço colorido?
Fonte
Como faço para desenhar com essa fonte? Como obtenho esse nome? Como evito os problemas de cópia dos quais reclamei acima? Como faço para definir sua cor? Kerning? E o suporte ao Unicode?
Renderer: Geral
Portanto, notei que você tem algumas funções blit * () e também uma função render (). Isso parece implicar que você deseja que os usuários possam enfileirar várias operações de blitting e, em seguida, liberá-las para a tela de uma só vez usando a chamada render ().
Isso é bom; de fato, é assim que a tecnologia de motores do meu grupo também lida com isso. :)
O uso de um singleton aqui é aceitável, principalmente porque você parece querer permitir que o renderizador tenha controle total sobre o desenho das coisas. Se houver apenas uma instância (como provavelmente deveria haver), isso não fará mal a nada. Não é o que fazemos, mas ei, é uma questão de gosto.
Existem alguns grandes problemas que vejo aqui.
Renderer: Transformações
Você parece estar trabalhando apenas em 2D. Isso é bom. MAS...
Como você lida com coisas como girar uma imagem quando a desenha? Escalando? Você precisa de suporte completo para algo chamado transformações afins . Isso permite que você gire, dimensione, traduza, incline e se mova facilmente de uma maneira bonita.
Isso precisa ser suportado (de alguma forma) para texto e imagens.
Renderer: Coloração e mistura
Quero poder misturar cores nas minhas imagens e definir as cores do meu texto. Você deve expor isso.
Também quero ser capaz de fazer coisas como misturar imagens ao cegar, para que eu possa fazer coisas como fantasmas translúcidos, fumaça ou fogo.
Como salvar o problema
Use SFML . Ele tem um design melhor para, bem, praticamente tudo sobre SDL. Eles já fizeram o que você está tentando fazer aqui. No mínimo, observe como eles especificaram suas interfaces e como eles projetaram sua hierarquia de classes.
Observe também que eles abordam a questão das transformações que apontei anteriormente na classe Drawable. E colorir. E misturando.
Eles têm bons tutoriais e documentação, portanto, pode valer a pena dedicar um pouco a isso e ter uma ideia do que seu código deve conseguir.
Boa sorte!
fonte
Estou um pouco incomodado com o método "load", que quebra o Princípio de Responsabilidade Única (a propósito, para o Pato Comunista, acho que é por isso que ele está usando um const char *, porque a função de carregamento de imagem SDL, codificada em C, não use std :: string). Não deve ser uma tarefa da classe Image carregar-se, por pelo menos dois motivos:
fonte
std::string
parachar*
, de modo que não é a razão. a razão é simplesmente que nãostd::string
traria vantagens.Singleton = BAD, remova imediatamente. Fontes e imagens não devem ter uma
free()
função, que deve ser o trabalho do destruidor. NãoGraphicsRenderer
deve oferecer essasBlit
funções, você deve oferecer classes orientadas a objetos que fornecerão uma posição para cada resultado final e a renderização real gerenciada automaticamente pelo GraphicsRenderer. Finalmente, para encapsulamento, use herança, não PIMPL, e definitivamente não use um vazio *, use um ponteiro opaco fortemente tipado.Aqui estão alguns trechos do meu próprio design, embora eu tenha usado a alternância em tempo de compilação e não a herança em tempo de execução.
Aqui, a memória é gerenciada para você - toda a propriedade é gerenciada por meio de apontador inteligente e a interface é completamente orientada a objetos.
fonte
free()
função está lá pelo mesmo motivo que aifstream::close()
função existe. | Blit: Estou tentando separar os dados do mecanismo que os usa. | Sprites: Este é apenas o módulo gráfico. Os sprites devem ser manipulados nos níveis mais altos do mecanismo, de maneira semelhante ao que você descreveu. [continuação]data_
é apenas umSDL_Surface*
elenco paravoid*
. Sempre que eu uso, eu o lanço de voltaSDL_Surface*
. | Blit [continuação]: Outra maneira de pensar sobre isso é que a imagem não se desenha & mdash; o renderizador desenha. A imagem simplesmente existe .std::shared_ptr
, no entanto. Obrigado definitivamente vai ser útil.free()
função; Além disso, pode-se usar um esquema de alocação personalizado que não chama o destruidor.Primeiras coisas que posso identificar:
Font
eImage
parece estar intimamente relacionado. Talvez você possa levar algumas das funcionalidades acima na hierarquia para aRenderable
.const char*
maisstd::string
?fonte
const char*
, eu não tinha notado isso.SDL.h
(void* data_
é apenas umaSDL_Surface*
conversão paravoid*
). | Strings: Tenho algum motivo para não usá-los? Não faço manipulações, só preciso de uma corda. Sou livre para usarstd::string
fora dessas aulas, se assim o desejar. | Singletons: Sim, mais de um GR quebrará meu código. O GR inicializa o SDL. Sendo um singleton, não preciso me preocupar quando o SDL for inicializado ou encerrado.SDL.h
é praticamente zero. De alguma forma, eu não teria um renderizador inicializando SDL. Eu resumiria isso ao mecanismo principal, ou qualquer outra coisa. E se você quisesse outras partes do SDL, exceto gráficos?