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.
Posso desenvolver isso, se desejar, mas tenho um renderizador central que é instruído a desenhar no loop. Ao invés de
Eu tenho um sistema mais parecido
A classe renderer simplesmente contém uma lista de referências a componentes desenháveis de objetos. Eles são atribuídos nos construtores por simplicidade.
Para o seu exemplo, eu teria uma classe GameBoard com vários Tiles. Obviamente, cada ladrilho conhece sua posição e presumo algum tipo de animação. Fatore isso em algum tipo de classe de Animação que o bloco possua e faça com que ele passe uma referência para uma classe Renderer. Lá, todos separados. Quando você atualiza o Tile, ele chama Atualização na animação ... ou atualiza ele próprio. Quando
Renderer.Draw()
é chamado, desenha a animação.A animação independente de quadro não deve ter muito a ver com o loop de desenho.
fonte
Ultimamente, tenho aprendido os paradigmas, por isso, se esta resposta estiver incompleta, tenho certeza que alguém irá adicionar a ela.
A metodologia que parece fazer mais sentido para o design de jogos é separar a lógica da saída da tela.
Na maioria dos casos, você gostaria de usar uma abordagem multithread, se você não estiver familiarizado com esse tópico, é uma pergunta própria, aqui está a cartilha do wiki . Essencialmente, você deseja que sua lógica de jogo seja executada em um thread, bloqueando variáveis que ele precisa acessar para garantir a integridade dos dados. Se o seu loop lógico é incrivelmente rápido (super mega pong 3d animado?), Você pode tentar fixar a frequência que o loop executa, adormecendo o thread por pequenas durações (120 hz foi sugerido neste fórum para loops de física de jogos). Simultaneamente, o outro encadeamento está redesenhando a tela (60 hz foi sugerido em outros tópicos) com as variáveis atualizadas, novamente solicitando um bloqueio nas variáveis antes de acessá-las.
Nesse caso, animações ou transições, etc., entram no segmento de desenho, mas você deve sinalizar através de uma bandeira de algum tipo (talvez uma variável de estado global) que o segmento de lógica do jogo não deve fazer nada (ou fazer algo diferente ... configure o novos parâmetros do mapa, talvez).
Depois de entender a concorrência, o resto é bastante compreensível. Se você não tem experiência com concorrência, sugiro fortemente que você escreva alguns programas de teste simples para entender como o fluxo acontece.
Espero que isto ajude :)
[edit] Em sistemas que não suportam multithreading, a animação ainda pode entrar no loop de desenho, mas você deseja definir o estado de forma a sinalizar para a lógica que algo diferente está ocorrendo e não continuar processando o nível atual / mapa / etc ...
fonte