Codificação baseada em dados
Tudo o que você menciona é algo que pode ser especificado nos dados. Por que você está carregando aspecificmap
? Como a configuração do jogo indica que é o primeiro nível quando um jogador inicia um novo jogo, ou porque esse é o nome do ponto de salvamento atual no arquivo de salvamento que ele acabou de carregar, etc.
Como você encontra aspecificmap
? Porque está em um arquivo de dados que lista os IDs do mapa e seus recursos em disco.
É necessário apenas um conjunto particularmente pequeno de recursos "essenciais" que sejam legitimamente rígidos ou impossíveis de evitar a codificação. Com um pouco de trabalho, isso pode ser limitado a um único nome de ativo padrão codificado como main.wad
ou semelhante. Este arquivo pode ser potencialmente alterado em tempo de execução, passando um argumento de linha de comando para o jogo, também conhecido como game.exe -wad mymain.wad
.
A escrita de código orientado a dados se baseia em alguns outros princípios. Por exemplo, é possível evitar que sistemas ou módulos solicitem um recurso específico e inverter essas dependências. Ou seja, não faça DebugDrawer
carregamento debug.font
no seu código de inicialização; em vez disso, DebugDrawer
use um identificador de recurso em seu código de inicialização. Esse identificador pode ser carregado a partir do arquivo de configuração principal do jogo.
Como exemplos concretos de nossa base de código, temos um objeto "dados globais" carregado do banco de dados de recursos (que por si só é a ./resources
pasta, mas pode ser sobrecarregado com um argumento de linha de comando). O ID do banco de dados de recurso desses dados globais é o único nome de recurso codificado necessário na base de código (temos outros porque às vezes os programadores ficam preguiçosos, mas geralmente acabamos corrigindo / removendo esses eventualmente). Esse objeto de dados global está cheio de componentes cujo único objetivo é fornecer dados de configuração. Um dos componentes é o componente Dados globais da interface do usuário, que contém identificadores de recursos para todos os principais recursos da interface do usuário (fontes, arquivos Flash, ícones, dados de localização etc.), entre vários outros itens de configuração. Quando um desenvolvedor de interface do usuário decide renomear o principal recurso da interface do usuário de /ui/mainmenu.swf
para/ui/lobby.swf
eles apenas atualizam essa referência de dados globais; nenhum código do mecanismo precisa mudar.
Usamos esses dados globais para tudo. Todos os personagens jogáveis, todos os níveis, interface do usuário, áudio, ativos principais, configuração de rede, tudo. (bem, não tudo , mas essas outras coisas são bugs a serem corrigidos.)
Essa abordagem tem muitas outras vantagens. Por um lado, torna a compactação e agregação de recursos parte integrante de todo o processo. Os caminhos de codificação embutida no mecanismo também tendem a significar que esses mesmos caminhos devem ser codificados em quaisquer scripts ou ferramentas que agrupam os ativos do jogo, e esses caminhos podem ficar fora de sincronia. Contando com um único núcleo de ativos e cadeias de referência a partir daí, podemos criar um pacote de ativos com um único comando bundle.exe -root config.data -out main.wad
e saber que ele incluirá todos os ativos necessários. Além disso, como o empacotador seguiria apenas as referências de recursos, sabemos que incluirá apenas os ativos necessários e pularemos toda a penugem restante que inevitavelmente se acumula durante a vida de um projeto (além disso, podemos gerar automaticamente listas dessa cotão para poda).
Um caso difícil dessa coisa toda está nos scripts. Tornar o mecanismo controlado por dados é fácil conceitualmente, mas já vi muitos projetos (hobby da AAA) em que os scripts são considerados dados e, portanto, "é permitido" usar apenas caminhos de recursos indiscriminadamente. Não faça isso. Se um arquivo Lua precisar de um recurso e chamar apenas uma função textures.lua("/path/to/texture.png")
, o pipeline de ativos terá muitos problemas ao saber que o script precisa /path/to/texture.png
operar corretamente e pode considerar que a textura não é usada e desnecessária. Os scripts devem ser tratados como qualquer outro código: todos os dados necessários, incluindo recursos ou tabelas, devem ser especificados em uma entrada de configuração que o mecanismo e o pipeline de recursos possam inspecionar quanto a dependências. Os dados que dizem "carregar script foo.lua
" devem dizer "foo.lua
e forneça esses parâmetros ", onde os parâmetros incluem todos os recursos necessários. Se um script gerar aleatoriamente inimigos, por exemplo, passe a lista de possíveis inimigos para o script a partir desse arquivo de configuração. O mecanismo poderá pré-carregar os inimigos com o nível ( pois ele conhece a lista completa de possíveis spawns) e o pipeline de recursos sabe agrupar todos os inimigos no jogo (já que eles são definitivamente referenciados pelos dados de configuração) .Se os scripts geram sequências de nomes de caminhos e apenas chama uma load
função, o mecanismo nem o pipeline de recursos têm como saber especificamente quais ativos o script pode tentar carregar.
Da mesma maneira que você evita a codificação em funções gerais.
Você passa parâmetros e mantém suas informações em arquivos de configuração.
Nessa situação, não há absolutamente nenhuma diferença na engenharia de software entre escrever um mecanismo e escrever uma classe.
Em seguida, o código do cliente lê um arquivo de configuração "mestre" ( este é codificado ou passado como um argumento de linha de comando) que contém as informações que informam onde estão os arquivos de ativos e o mapa que eles contêm.
A partir daí, tudo é direcionado pelo arquivo de configuração "mestre".
fonte
Eu gosto das outras respostas, então vou ser um pouco contrário. ;)
Você não pode evitar codificar o conhecimento sobre seus dados no seu mecanismo. De onde quer que a informação venha, o mecanismo deve saber para procurá-la. No entanto, você pode evitar a codificação das informações reais em seu mecanismo.
Uma abordagem orientada a dados "puros" faria com que você iniciasse o executável com os parâmetros de linha de comando necessários para carregar a configuração inicial, mas o mecanismo precisará ser codificado para saber como interpretar essas informações. Por exemplo, se seus arquivos de configuração são JSON, você precisa codificar as variáveis que você procurar, por exemplo, o motor terá que saber procurar
"intro_movies"
e"level_list"
e assim por diante.No entanto, um mecanismo "bem construído" pode funcionar para muitos jogos diferentes apenas trocando os dados de configuração e os dados que ele faz referência.
Portanto, o mantra não é tanto para evitar a codificação rígida, mas também para garantir que você possa fazer alterações com o mínimo de esforço possível.
Para contrastar com a abordagem dos arquivos de dados (que eu apoio sinceramente), pode ser que você esteja bem compilando os dados em seu mecanismo. Se o "custo" de fazer isso for menor, não haverá nenhum dano real; se você é o único que trabalha nisso, pode adiar o tratamento de arquivos para uma data posterior e não necessariamente se ferrar. Meus primeiros projetos de jogos tinham grandes tabelas de dados codificadas no próprio jogo, por exemplo, uma lista de armas e seus dados variados:
Então você coloca esses dados em algum lugar fácil de referenciar e é fácil editar, conforme necessário. O ideal seria colocar essas coisas em um arquivo de configuração de algum tipo, mas você precisará analisar e traduzir e todo esse jazz, além de conectar referências entre estruturas pode se tornar uma dor adicional que você realmente não deseja. Lide com.
fonte