Ultimamente, tenho me divertido programando um jogo de aventura simples baseado em texto e estou preso ao que parece ser uma questão de design muito simples.
Para dar uma breve visão geral: o jogo é dividido em Room
objetos. Cada Room
um tem uma lista de Entity
objetos que estão naquela sala. Cada Entity
um possui um estado de evento, que é um mapa simples de string-> booleano e uma lista de ações, que é um mapa de string-> função.
A entrada do usuário assume o formulário [action] [entity]
. Ele Room
usa o nome da entidade para retornar o Entity
objeto apropriado , que usa o nome da ação para encontrar a função correta e a executa.
Para gerar a descrição da sala, cada Room
objeto exibe sua própria string de descrição e, em seguida, anexa as strings de descrição de cada Entity
. A Entity
descrição pode mudar com base no seu estado ("A porta está aberta", "A porta está fechada", "A porta está trancada", etc).
Aqui está o problema: usando esse método, o número de funções de descrição e ação que preciso implementar rapidamente fica fora de controle. Somente minha sala de partida possui cerca de 20 funções entre 5 entidades.
Posso combinar todas as ações em uma única função e alternar entre elas, mas ainda são duas funções por entidade. Também posso criar Entity
subclasses específicas para objetos comuns / genéricos, como portas e chaves, mas isso só me leva até agora.
EDIT 1: Conforme solicitado, exemplos de pseudo-código dessas funções de ação.
string outsideDungeonBushesSearch(currentRoom, thisEntity, player)
if thisEntity["is_searched"] then
return "There was nothing more in the bushes."
else
thisEntity["is_searched"] := true
currentRoom.setEntity("dungeonDoorKey")
return "You found a key in the bushes."
end if
string dungeonDoorKeyUse(currentRoom, thisEntity, player)
if getEntity("outsideDungeonDoor")["is_locked"] then
getEntity("outsideDungeonDoor")["is_locked"] := false
return "You unlocked the door."
else
return "The door is already unlocked."
end if
As funções de descrição agem da mesma maneira, verificando o estado e retornando a string apropriada.
EDIT 2: Revisou a redação da minha pergunta. Suponha que possa haver um número significativo de objetos no jogo que não compartilhem comportamento comum (respostas baseadas em estado a ações específicas) com outros objetos. Existe uma maneira de definir esses comportamentos exclusivos de maneira mais limpa e sustentável do que escrever uma função personalizada para cada ação específica da entidade?
Respostas:
Em vez de criar uma função separada para cada combinação de substantivos e verbos, você deve configurar uma arquitetura em que exista uma interface comum que todos os objetos do jogo implementem.
Uma abordagem em cima da minha cabeça seria definir um objeto de entidade que todos os objetos específicos do seu jogo estendem. Cada entidade terá uma tabela (qualquer estrutura de dados que seu idioma use para matrizes associativas) que associe ações diferentes a resultados diferentes. As ações na tabela provavelmente serão Strings (por exemplo, "open") enquanto o resultado associado pode até ser uma função privada no objeto se o seu idioma suportar funções de primeira classe.
Da mesma forma, o estado do objeto é armazenado em vários campos do objeto. Assim, por exemplo, você pode ter uma variedade de coisas em um Bush, e a função associada à "pesquisa" atuará nessa matriz, retornando o objeto encontrado ou a string "Não havia mais nada nos arbustos".
Enquanto isso, um dos métodos públicos é algo como Entity.actOn (ação String). Nesse método, compare a ação transmitida com a tabela de ações para esse objeto; se essa ação estiver na tabela, retorne o resultado.
Agora, todas as diferentes funções necessárias para cada objeto estarão contidas no objeto, facilitando a repetição desse objeto em outras salas (por exemplo, instanciar o objeto Door em todas as salas que possuem uma porta)
Por fim, defina todas as salas em XML ou JSON ou o que for, para que você possa ter várias salas exclusivas sem precisar escrever código separado para cada sala. Carregue esse arquivo de dados quando o jogo iniciar e analise os dados para instanciar os objetos que preenchem seu jogo. Algo como:
ADICIONAL: aha, acabei de ler a resposta do FxIII e este pouco perto do fim saltou para mim:
Embora eu discorde que uma armadilha de chamas sendo acionada é algo que só aconteceria uma vez (eu podia ver essa armadilha sendo reutilizada para muitos objetos diferentes), acho que finalmente entendi o que você quis dizer sobre entidades que reagem exclusivamente à entrada do usuário. Eu provavelmente abordaria coisas como fazer uma porta em sua masmorra ter uma armadilha de bola de fogo, construindo todas as minhas entidades com uma arquitetura de componentes (explicada em detalhes em outro lugar).
Dessa forma, cada entidade do Door é construída como um pacote de componentes, e posso misturar e combinar componentes de maneira flexível entre diferentes entidades. Por exemplo, a maioria das portas teria configurações semelhantes a
mas a única porta com uma armadilha de bola de fogo seria
e então o único código único que eu teria que escrever para essa porta é o componente FireballTrap. Ele usaria os mesmos componentes de bloqueio e portal que todas as outras portas, e se mais tarde eu decidisse usar o FireballTrap em uma arca do tesouro ou algo tão simples quanto adicionar o componente FireballTrap a essa arca.
Se você define ou não todos os componentes no código compilado ou em uma linguagem de script separada não é uma grande distinção em minha mente (de qualquer forma, você escreverá o código em algum lugar ), mas o importante é que você pode reduzir significativamente o quantidade de código exclusivo que você precisa escrever. Heck, se você não está preocupado com a flexibilidade para designers / modders de nível (você está escrevendo este jogo sozinho), você pode até mesmo fazer com que todas as entidades sejam herdadas da Entity e adicione componentes no construtor em vez de um arquivo ou script de configuração ou tanto faz:
fonte
Entity
apenas para um único objeto agrupa o código, mas não reduz a quantidade de código que tenho que escrever. Ou isso é uma armadilha inevitável a esse respeito?Entity
e os atributos definem seu estado inicial. Suponho que as tags filho da entidade atuem como parâmetros para qualquer ação à qual a tag esteja associada, certo?O problema dimensional que você aborda é bastante normal e quase inevitável. Você deseja encontrar uma maneira de expressar suas entidades que seja coincidente e flexível .
Um "contêiner" (o arbusto na resposta jhocking) é uma maneira coincidente , mas você percebe que não é suficientemente flexível .
Eu não sugiro que você tente encontrar uma interface genérica e use arquivos de configuração para especificar os comportamentos, porque você sempre terá a sensação desagradável de estar entre uma rocha (entidades padrão e chatas, fáceis de descrever) e um local difícil ( entidades fantásticas únicas, mas muito longas para serem implementadas).
Minha sugestão é usar uma linguagem interpretada para codificar comportamentos.
Pense no exemplo do mato: é um contêiner, mas nosso mato precisa ter itens específicos; o objeto contêiner pode ter:
Um desses itens tem uma corda que aciona uma engenhoca que, por sua vez, dispara uma chama que queima o mato ... (você vê, eu posso ler sua mente para saber o que você gosta).
Você pode usar um script para descrever esse arbusto, em vez de um arquivo de configuração, colocando o código extra relevante em um gancho que você executa no seu programa principal sempre que alguém escolhe um item de um contêiner.
Agora você tem muitas opções de arquitetura: pode definir ferramentas comportamentais como classes base usando sua linguagem de código ou a linguagem de script (coisas como contêineres, como portas e assim por diante). O objetivo dessas coisas é permitir que você descreva as entidades que agregam facilmente comportamentos simples e os configuram usando ligações em uma linguagem de script .
Todas as entidades devem estar acessíveis ao script: você pode associar um identificador a cada entidade e colocá-los em um contêiner que é exportado no estendido do script da linguagem de script.
O uso de estratégias de script permite manter sua configuração simples (nada
<item triggerFlamesOnPicking="true">
disso você usará apenas uma vez), ao mesmo tempo em que expressa beaviours estranhos (os divertidos), adicionando alguma linha de códigoEm poucas palavras: scripts como arquivo de configuração que pode executar código.
fonte