A animação ainda pode ser perfeitamente dividida entre lógica e renderização. O estado de dados abstratos da animação seria a informação necessária para a API de gráficos renderizar a animação.
Em jogos 2D, por exemplo, pode ser uma área retangular que marca a área que exibe a parte atual da sua planilha de sprite que precisa ser desenhada (quando você tem uma planilha composta por, digamos, 30 desenhos 80x80 contendo as várias etapas do seu personagem pulando, sentando, movendo etc.). Também pode ser qualquer tipo de dados que você não precise renderizar, mas talvez para gerenciar os estados da animação, como o tempo restante até a etapa atual da animação expirar ou o nome da animação ("walking", "standing" etc.) Tudo isso pode ser representado da maneira que você desejar. Essa é a parte da lógica.
Na parte de renderização, você faz como de costume, obtém esse retângulo do seu modelo e usa seu renderizador para realmente fazer as chamadas para a API de gráficos.
No código (usando a sintaxe C ++ aqui):
class Sprite //Model
{
private:
Rectangle subrect;
Vector2f position;
//etc.
public:
Rectangle GetSubrect()
{
return subrect;
}
//etc.
};
class AnimatedSprite : public Sprite, public Updatable //arbitrary interface for classes that need to change their state on a regular basis
{
AnimationController animation_controller;
//etc.
public:
void Update()
{
animation_controller.Update(); //Good OOP design ;) It will take control of changing animations in time etc. for you
this.SetSubrect(animation_controller.GetCurrentAnimation().GetRect());
}
//etc.
};
Esses são os dados. Seu renderizador pegará esses dados e os desenhará. Como os Sprites normais e os animados são desenhados da mesma maneira, você pode usar a polimorfia aqui!
class Renderer
{
//etc.
public:
void Draw(const Sprite &spr)
{
graphics_api_pointer->Draw(spr.GetAllTheDataThatINeed());
}
};
TMV:
Eu vim com outro exemplo. Digamos que você tenha um RPG. Seu modelo que representa o mapa do mundo, por exemplo, provavelmente precisaria armazenar a posição do personagem no mundo como coordenadas de bloco no mapa. No entanto, quando você move o personagem, eles caminham alguns pixels por vez para o próximo quadrado. Você armazena essa posição "entre blocos" em um objeto de animação? Como você atualiza o modelo quando o personagem finalmente "chega" à próxima coordenada do bloco no mapa?
O mapa-múndi não sabe diretamente a posição dos jogadores (não possui um Vector2f ou algo parecido que armazena diretamente a posição dos jogadores =; em vez disso, ele tem uma referência direta ao próprio objeto do jogador, que por sua vez deriva do AnimatedSprite para que você possa transmiti-lo facilmente ao renderizador e obter todos os dados necessários.
No entanto, em geral, seu mapa de tile não deve ser capaz de fazer tudo - eu teria uma classe "TileMap" que cuida do gerenciamento de todos os tiles, e talvez ele também detecte colisão entre os objetos que eu entrego a ele e as peças no mapa. Então, eu teria outra classe "RPGMap", ou como você gostaria de chamá-la, que tem uma referência ao seu mapa de blocos e a referência ao jogador e faz as chamadas Update () reais para o seu jogador e para o seu tilemap.
Como você deseja atualizar o modelo quando o player se move depende do que você deseja fazer.
É permitido ao seu jogador mover-se entre blocos de forma independente (estilo Zelda)? Basta manipular a entrada e mover o reprodutor de acordo com cada quadro. Ou você quer que o jogador pressione "para a direita" e seu personagem move automaticamente uma peça para a direita? Deixe sua classe RPGMap interpolar a posição dos jogadores até que ele chegue ao seu destino e, enquanto isso, bloqueie todo o manuseio das teclas de movimento.
De qualquer forma, se você quiser facilitar as coisas, todos os seus modelos terão os métodos Update () se eles realmente precisarem de alguma lógica para se atualizar (em vez de apenas alterar os valores das variáveis) - Você não abre mão do controlador no padrão MVC dessa maneira, basta mover o código de "um passo acima" (o controlador) para o modelo, e tudo o que o controlador faz é chamar esse método Update () do modelo (o controlador no nosso caso seria o RPGMap). Ainda é possível trocar facilmente o código de lógica - você pode alterar diretamente o código da classe ou, se precisar de um comportamento completamente diferente, pode derivar da classe de modelo e substituir apenas o método Update ().
Essa abordagem reduz muito as chamadas de método e coisas assim - que costumava ser uma das principais desvantagens do padrão MVC puro (você acaba chamando GetThis () GetThat () com muita frequência) - torna o código mais longo e mais um pouco mais difícil de ler e também mais lento - embora isso possa ser resolvido pelo seu compilador, que otimiza muitas coisas assim.