Estou tentando escrever um pequeno "jogo" em que um jogador circula e luta contra monstros, mas não tenho idéia de como lidar com o combate.
Por exemplo, digamos que eu tenho um "guerreiro" e um "troll". Como os dois lutam entre si? Eu sei que posso fazer algo como
Conan = Warrior.new();
CaveTroll = Troll.new();
Conan.attack(CaveTroll);
CaveTroll.attack(Conan);
Mas que parte do jogo controla o monstro? Eu apenas coloco a sequência acima em um loop até que um deles morra? Ou o "mecanismo" do jogo precisa ter uma parte que lide especificamente com o combate? Ou esse é um aspecto da inteligência artificial do Troll que precisa cuidar de suas ações?
Além disso, quem / o que determina as ações que o monstro executa? Talvez um Troll possa bater, chutar, morder, lançar feitiços, beber poções, usar um item mágico. O mecanismo de jogo determina que ação o Troll executa ou isso é algo que a classe Troll gerencia?
Desculpe, não posso ser mais específico, mas preciso de algumas orientações sobre a direção a seguir.
Respostas:
Eu imagino uma sequência de batalha como um minigame dentro do seu jogo. Os ticks de atualização (ou turn ticks) são direcionados para um componente que manipula esses eventos. Essa abordagem encapsula a lógica da sequência de batalha em uma classe separada, deixando o loop principal do jogo livre para a transição entre os estados do jogo.
A classe de sequência de batalha ficaria assim:
Seu Troll e Warrior herdam de uma superclasse comum chamada Entidade. No HandleTurn, a entidade atacante pode se mover. Isso é equivalente a uma rotina de pensamento da IA.
O método de luta decide o que a entidade fará. Observe que isso não precisa envolver a entidade adversária, como beber uma poção ou fugir.
Atualização: Para oferecer suporte a vários monstros e um grupo de jogadores, você introduz uma classe de grupo:
A classe Group substituirá todas as ocorrências de Entidade na classe BattleSequence. A seleção e o ataque serão tratados pela própria classe Entity, para que a IA possa levar todo o grupo em consideração ao selecionar o melhor curso de ação.
fonte
Eu teria um objeto de combate dedicado que gerencia o combate. Encapsularia o estado de combate completo, incluindo itens como a lista de personagens dos jogadores, lista de inimigos, turno atual, terreno de batalha e assim por diante. O combate pode então ter um método de atualização que gerencia a lógica da batalha. Não é uma boa ideia apenas colocar o código de combate em um loop simples, porque terminaria muito rápido. Normalmente você tem tempo e estágios de batalha diferentes.
Para as ações tomadas, você certamente pode torná-lo aleatório, mas faria pouco sentido para um monstro com HP completo lançar um feitiço de cura. Vale a pena ter alguma lógica básica para determinar qual ação executar. Por exemplo, algumas ações podem ter mais prioridade do que outras (por exemplo, trolls chutam 30% do tempo), além de outras condições para tornar as batalhas mais interessantes (por exemplo, quando o troll HP é inferior a 10% do HP total, há 20% chance de lançar feitiço de cura, caso contrário, a chance é de 1%). Isso pode ser tão complexo quanto você quiser.
Eu acho que a classe de monstros deveria lidar com a seleção de qual ação fazer, o objeto de batalha pede ao monstro uma ação e o monstro faz uma escolha e depois passa a aplicá-la. Uma idéia é ter um objeto de estratégia que você conecte aos monstros e que selecione na lista de possíveis ações de monstros com base nas prioridades, categorias e condições atribuídas a cada ação de batalha. Então você pode ter uma classe OffensiveStrategy, por exemplo, que prioriza ataques em detrimento de habilidades defensivas, e outra CautiousStrategy com maior probabilidade de recuperação. Um chefe pode mudar dinamicamente a estratégia com base em sua condição atual.
Uma última coisa. Você pode querer que os dois personagens e monstros herdem da mesma classe, sejam instâncias da mesma classe (ator ou combatente por exemplo) ou compartilhem um objeto comum que encapsule a funcionalidade comum. Isso reduz a duplicação de código e também permite que você tenha NPCs controlados por IA do seu lado, que podem implementar as mesmas estratégias que você já codificou para monstros.
fonte
Sim, você precisa ter uma parte especial em seu mecanismo que lide com o combate.
Não sei exatamente como você está fazendo o seu combate, mas presumo que os jogadores perambulam pelo mundo do jogo, se encontrem com monstros e a batalha será em tempo real. Nesse caso, o troll precisa conhecer o ambiente em uma determinada área, talvez defina até que ponto o troll pode ver algo à sua frente (o troll lida com isso).
Sobre a IA, acho que o mecanismo precisa lidar com ela mesma, de modo que digamos que você tem mais de um tipo de inimigo que pode fazer a mesma coisa (mordida), basta atribuir a AI a outro monstro e pronto!
fonte
Seu jogador e seu troll nada mais são que conjuntos de dados, o que chamamos de Modelo de dados que descreve seu mundo. Vida, inventário, recursos de ataque, até mesmo o conhecimento do mundo - tudo consiste no modelo de dados.
Mantenha um único objeto Modelo principal que mantenha todos os dados que descrevem seu mundo. Ele conterá informações gerais do mundo, como dificuldade, parâmetros físicos etc. Também conterá uma lista / matriz de dados de entidades específicas , como descrevi acima. Este modelo principal pode consistir em muitos subobjetos para descrever seu mundo. Em nenhum lugar do seu modelo você deve ter funções que controlem a lógica do jogo ou exibam a lógica; getters são a única exceção e seriam usados apenas para permitir que você obtenha dados do modelo mais rapidamente (se membros públicos ainda não fizerem o truque).
Em seguida, crie funções em uma ou mais classes "controladoras"; você pode escrevê-los todos como funções auxiliares na classe principal, embora isso possa ficar um pouco grande depois de um tempo. Eles serão chamados a cada atualização para atuar sobre os dados das entidades para diferentes finalidades (movimento, ataque etc.). Manter essas funções fora de uma classe de entidade é mais eficiente em termos de recursos e, depois de saber o que descreve sua entidade, você saberá automaticamente quais funções precisam agir sobre ela.
Uma observação final é que também é útil manter sua lógica de exibição separada da lógica do jogo. A lógica de exibição seria: "Onde eu desenho isso na tela e em que cor?" vs. lógica do jogo, como descrevi no pseudocódigo acima.
(Nota do desenvolvedor: ao usar classes, isso segue livremente uma abordagem de programação funcional que considera todos os métodos como idealmente sem estado, permitindo um modelo de dados limpo e uma abordagem de processamento que minimiza os bugs causados pelo estado retido. FP é o MVC definitivo, pois atinge MVCs objetivo de separar as preocupações explicitamente. Veja esta pergunta .)
fonte