Cursos / Jogos Digitais / Inteligência Artificial para Jogos / Aula
Até o momento, tudo que você fez foi o jogador ir de um lado para o outro. Todo o comportamento do jogo encontra-se em um único script, associado ao jogador. Agora, você tem mais objetos e pode melhorar a organização de mais scripts.
Comece colocando o jogador para cobrar um pênalti. Ele deve escolher um dos lados da trave para chutar, seguindo uma probabilidade, tanto para a esquerda quanto para a direita. Para isso, reestruture o jogo, deixando-o mais organizado. Já que você vai modelar a cobrança de pênalti, têm de ter no mínimo o cobrador, o goleiro e o gol. Além disso, precisa saber quando o chute fez gol, quando o goleiro conseguiu defendê-lo, bem como fazer com que o goleiro pule para um lado quando o jogador chutar. É bastante coisa! Vai ser necessária uma completa remodelagem do que se tem até agora.
Normalmente, os desenvolvedores de jogos usam um objeto que coordena ou sincroniza elementos com os demais. Faça o mesmo, crie o objeto GameManager. Ele vai ser responsável por contar os pênaltis realizados, o número de gols feitos, assim como por reinicializar o cenário após cada pênalti. Como esse objeto é um “coordenador” do que acontece no jogo, ele precisa ser único (só existir uma instância da classe) e facilmente encontrado pelos demais objetos do jogo.
Essas são características comuns presentes em alguns objetos de muitos sistemas. Por isso, alguns programadores já criaram “padrões” que facilitam a criação de objetos com essas características. Inclusive, cada padrão tem um nome para que os programadores possam se entender mais facilmente. Você utilizará o que se chama de Singleton, usado para restringir uma classe para que ela tenha apenas um objeto e que ele seja facilmente encontrado pelos demais. Veja como se implementa um Singleton no Unity.
Além do GameManager, crie também um script para associar a uma área chamada Goal (a área de gol). O objeto representando essa área será responsável por avisar ao GameManager quando um gol ocorre, no momento que houver uma colisão da bola com a área de gol delimitada. Por isso, será necessário ter um BoxCollider associado a ele, de uma forma que quando a bola bater nesta área, você poderá sair gritando Goooooooooool!!!
A Figura 01 mostra a representação visual dos objetos que foram descritos até o momento, inclusive do GameManager e do Goal, que são “invisíveis”, ou seja, nem um nem o outro possuem visual (não há SpriterRenderer associado). Porém, Goal possui um BoxCollider para especificar a região em que, se a bola colidir, será considerado como gol. Além desses, o jogo deverá ter scripts tanto para o cobrador de pênalti quanto para o goleiro. O cobrador precisa escolher de forma aleatória um dos lados do gol para chutar. Por sua vez, o goleiro precisa esperar o chute do batedor para poder saltar para um dos lados, escolhido também de forma aleatória.
Após o chute, o objeto GameManager reiniciará os objetos que precisarão voltar às suas posições, ou seja, o cobrador, o goleiro e a bola. Por essa razão, você tem de ter nos scripts associados aos três objetos um método chamado Reset(), com o objetivo de reiniciar a posição dos três. O Código 01 mostra como a reinicialização pode ser feita no script a ser associado à bola. Códigos similares devem ser igualmente implementados nos scripts do cobrador do pênalti e do goleiro. Esse script guarda a informação da posição inicial da bola e, quando for solicitado, através do método Reset(), sua posição é restaurada e o seu deslocamento é zerado. Assim, o script tem como único objetivo reinicializar a posição e velocidade para um novo pênalti.
Ótimo! Agora você tem ideia de todos os objetos que irão compor o cenário e seus respectivos scripts. Comece a trabalhar pelo GameManager. Como já foi dito, ele implementa um padrão de projeto chamado Singleton. Esse padrão permite que exista apenas uma instância (objeto) da classe e que ela seja facilmente acessível por todos os objetos do jogo.
Primeiramente, como se tem apenas uma instância, guarde-a na própria classe. Ou seja, ela deve ser um atributo da classe (para isso, use o termo static na definição do atributo). Você irá também defini-la como uma propriedade, tendo os métodos get e set específicos para cada uma. Além disso, para evitar que objetos de outras classes alterem quem é a instância que você está guardando, altere o acesso do método set para que apenas a própria classe GameManager possa alterá-la. Por fim, se o script de GameManager for associado a mais de um objeto do jogo, como a instância deve ser a mesma, se ela existir, a antiga deve ser desalocada da memória, e a instância válida passa a ser a última criada. Coloque esse teste no método Awake() do objeto. Essas observações vão resultar no trecho de código apresentado abaixo.
Para as suas estatísticas, o GameManager precisa armazenar quantos chutes foram dados e quantos deles resultaram em gol. Você irá, então, definir dois atributos com esse propósito (score e shots) e dois métodos que serão chamados para registrar as mudanças nas variáveis - OnShot() e Goal(). O primeiro será registrado como um evento do jogo, pois quando o jogador chutar, todos têm de saber que houve o chute (goleiro, defesa e o GameManager!). O segundo também pode ser um evento do jogo, mas como só está modelando as cobranças de pênalti no momento, apenas o GameManager precisa saber do gol para incrementar a variável score e reiniciar os objetos para uma nova cobrança de pênalti. Assim, o GameManager pode ser implementado como no Código 03. O atributo OnShot de soccer é um evento em que você cadastrou o método OnShot() do GameManager quando ele ocorrer.
O script do goleiro usará os comportamentos de navegação para “pular” para direita ou esquerda (escolhido de forma aleatória) quando o jogador chutar a bola. Você precisará, então, de um atributo target, que armazenará a posição para onde ele pulará, e um atributo para indicar quando ele pulou: jumped. Sim, porque só usará o comportamento de navegação quando ele pular.
Como no GameManager, você ficará “prestando atenção” quando o evento de chute ocorrer. O goleiro deve optar aleatoriamente para um dos lados do gol e defini-lo como alvo do comportamento de navegação. Coloque também um collider verificando quando ocorrer o evento dele (tocar na bola). Caso isso ocorra, chame a reinicialização do GameManager porque a bola foi agarrada pelo goleiro e um novo pênalti deve ser cobrado.
O Código 04 apresenta a implementação do goleiro. A posição para a qual o goleiro irá “pular” é definida no método OnShot(), baseada no objeto da área do gol. Consulte o retângulo que envolve o objeto (bounds) e defina dois pontos. O mais acima (max.y) estará à direita do goleiro e o mais abaixo (min.y) estará à esquerda dele.
Em relação ao cobrador de pênalti, precisa-se inicialmente definir o evento de chute. Quando o cobrador colidir com a bola, ele disparará o evento, fazendo com que o GameManager e o GoalKeeper “escutem” o evento. Além disso, você precisará também de um atributo para controlar quando o cobrador vai chutar a bola. Isso porque se você quiser que ele use o comportamento de navegação Arrival para ir em direção à bola (alvo do comportamento), deve fazer com que ele pare de correr atrás dela depois do chute. Por fim, para facilitar a configuração da força do chute, coloque um atributo público com essa finalidade. As demais escolhas são similares ao GoalKeeper.
Versão 5.3 - Todos os Direitos reservados