Em algum momento, um mecanismo DEVE se especializar e saber coisas sobre o jogo. Vou sair pela tangente aqui.
Pegue recursos em um RTS. Um jogo pode ter Credits
e Crystal
outro Metal
ePotatoes
Você deve usar os conceitos de OO adequadamente e usar o máx. reutilização de código. É claro que Resource
existe um conceito aqui.
Portanto, decidimos que os recursos têm o seguinte:
- Um gancho no loop principal para incrementar / diminuir
- Uma maneira de obter o valor atual (retorna um
int
)
- Uma maneira de subtrair / adicionar arbitrariamente (jogadores transferindo recursos, compras ...)
Observe que essa noção de a Resource
pode representar mortes ou pontos em um jogo! Não é muito poderoso.
Agora vamos pensar em um jogo. Podemos ter moeda negociando moedas de um centavo e adicionando um ponto decimal à saída. O que não podemos fazer são recursos "instantâneos". Como dizer "geração de rede elétrica"
Digamos que você adicione um InstantResource
classe com métodos semelhantes. Agora você está (começando a) poluir seu mecanismo com recursos.
O problema
Vamos pegar o exemplo do RTS novamente. Suponha que o jogador doe algo Crystal
para outro jogador. Você quer fazer algo como:
if(transfer.target == engine.getPlayerId()) {
engine.hud.addIncoming("You got "+transfer.quantity+" of "+
engine.resourceDictionary.getNameOf(transfer.resourceId)+
" from "+engine.getPlayer(transfer.source).name);
}
engine.getPlayer(transfer.target).getResourceById(transfer.resourceId).add(transfer.quantity)
engine.getPlayer(transfer.source).getResourceById(transfer.resourceId).add(-transfer.quantity)
No entanto, isso é realmente muito confuso. É de uso geral, mas confuso. Já que impõe um resourceDictionary
que significa que agora seus recursos precisam ter nomes! E é por jogador, então você não pode mais ter recursos de equipe.
Esta é uma abstração "demais" (não é um exemplo brilhante, eu admito). Em vez disso, você deve chegar a um ponto em que aceita que seu jogo tem jogadores e cristais; então, você pode apenas ter (por exemplo)
engine.getPlayer(transfer.target).crystal().receiveDonation(transfer)
engine.getPlayer(transfer.source).crystal().sendDonation(transfer)
Com uma classe Player
e uma classe em CurrentPlayer
que CurrentPlayer
o crystal
objeto mostrará automaticamente o material no HUD para a transferência / envio de doações.
Isso polui o mecanismo com cristal, a doação de cristal, as mensagens no HUD para os jogadores atuais e tudo mais. É mais rápido e fácil ler / gravar / manter (o que é mais importante, pois não é significativamente mais rápido)
Considerações finais
O caso do recurso não é brilhante. Espero que você ainda possa ver o ponto. Se alguma coisa demonstrei que "os recursos não pertencem ao mecanismo", como o que um jogo específico precisa e o que é aplicável a todas as noções de recursos são MUITO diferentes. O que você normalmente encontrará são 3 (ou 4) "camadas"
- O "Núcleo" - esta é a definição de mecanismo do livro, é um gráfico de cena com ganchos de eventos, lida com shaders e pacotes de rede e uma noção abstrata de jogadores
- O "GameCore" - Isso é bastante genérico para o tipo de jogo, mas não para todos os jogos - por exemplo, recursos em RTS ou munição em FPSs. A lógica do jogo começa a se infiltrar aqui. É aqui que estaria nossa noção anterior de recursos. Adicionamos coisas que fazem sentido para a maioria dos recursos de RTS.
- "GameLogic" MUITO específico ao jogo que está sendo feito. Você encontrará variáveis com nomes como
creature
ou ship
ou squad
. Usando herança você vai ter aulas que abrangem todos os 3 camadas (por exemplo, Crystal
é um Resource
que é um GameLoopEventListener
exemplo)
- "Ativos" são inúteis para qualquer outro jogo. Tomemos, por exemplo, os scripts de combinação de IA na meia-vida 2, eles não serão usados em um RTS com o mesmo mecanismo.
Criando um novo jogo a partir de um mecanismo antigo
Isso é MUITO comum. A fase 1 é eliminar as camadas 3 e 4 (e 2 se o jogo for do tipo TOTALMENTE diferente) Suponha que estamos criando um RTS a partir de um RTS antigo. Ainda temos recursos, não apenas cristais e outras coisas - portanto, as classes base nas camadas 2 e 1 ainda fazem sentido, todo o cristal mencionado em 3 e 4 pode ser descartado. Então nós fazemos. No entanto, podemos verificá-lo como uma referência para o que queremos fazer.
Poluição na camada 1
Isso pode acontecer. Abstração e desempenho são inimigos. O UE4, por exemplo, fornece muitos casos otimizados de composição (portanto, se você deseja X e Y, alguém escreveu um código que faz X e Y muito rápido - sabe que está fazendo as duas coisas) e, como resultado, é MUITO grande. Isso não é ruim, mas é demorado. A Camada 1 decidirá coisas como "como você passa dados para shaders" e como você anima as coisas. Fazer da melhor maneira para o seu projeto é SEMPRE bom. Apenas tente planejar o futuro, reutilizar o código é seu amigo, herdar para onde faz sentido.
Classificando camadas
Ultimamente (prometo) não tenha muito medo de camadas. Motor é um termo arcaico dos velhos tempos de dutos de função fixa, nos quais os motores funcionavam da mesma maneira graficamente (e, como resultado, tinha muito em comum), o duto programável virou isso de cabeça para baixo e, assim, a "camada 1" ficou poluída com quaisquer efeitos que os desenvolvedores desejassem alcançar. A IA era a característica distintiva (por causa da infinidade de abordagens) dos motores, agora é AI e gráficos.
Seu código não deve ser arquivado nessas camadas. Até o famoso motor Unreal tem MUITAS versões diferentes, cada uma específica para um jogo diferente. Existem poucos arquivos (exceto estruturas de dados semelhantes, talvez) que não teriam sido alterados. Isto é bom! Se você quiser fazer um novo jogo com outro, levará mais de 30 minutos. A chave é planejar, saber quais bits copiar e colar e o que deixar para trás.