Desenvolvendo um Jogo Digital do Zero: Parte 13 – Inteligência Artificial

No Shark Pong, já implementamos o sistema de interação, a física de movimentação do tubarão e o sistema de colisão.

O jogo já tem a sua primeira versão jogável, porém não há nenhuma resposta do jogador movimentado pela máquina.

Hoje programaremos a inteligência artificial do jogo de forma que o aparelho movimente do NPC.

Todas as partes da saga:

https://fabricadejogos.net/colunas/producao-jogo-digital-do-zero

Vimos no último artigo como implementar o sistema de colisão da barbatana do tubarão com as paredes e com as pás (pranchas dos surfistas). Além disso, fizemos com que o tubarão andasse mais rápido com o passar do tempo, deixando o jogo mais desafiador para o jogador. Com isso, fizemos uma primeira versão jogável.

Claramente, falta implementar o sistema de inteligência artificial e o término do game para que ele possa ser considerado um jogo com início, meio e fim. Hoje implementaremos o sistema de inteligência artificial que faz com que o NPC responda constantemente à movimentação do tubarão. Partiu.

 

Decisões do NPCDecisoes

 Normalmente, o projetista do jogo (game designer), que nesse caso é o redator Fabiano, pede uma opinião sobre como funcionará o motor da inteligência artificial. Após entrarmos em um acordo, a lógica é escrita no Game Design Document, para que toda a equipe esteja ciente de como será o funcionamento e, consequentemente, para que todos possam implementar seguindo o mesmo caminho.

Dessa forma, se você for abrir o GDD e ler a parte de inteligência artificial, perceberá que optamos por implementar as reações do NPC por meio de uma máquina de estados finitos. Caso você não saiba o que é uma máquina de estados finitos, ela é um modelo matemático de uma máquina que possui um conjunto de estados e transições entre os estados. A máquina precisa estar em um estado e, dependendo de uma entrada, ela passa para outro estado.

A explicação é um pouco abstrata, mas, se você analisar a máquina de estados que modela a inteligência artificial do Shark Pong, perceberá que não é um bicho de sete cabeças. A Figura 1 mostra a modelagem da máquina de estados projetada por mim e pelo game designer que decidirá as ações do NPC durante a execução do jogo.
FluxogramaIA

Figura 1 – Máquina de estados da IA

Note que, após o início do jogo, é requisitado que o NPC realize uma escolha. Essa escolha é feita entre três opções (estados) disponíveis: NPC vai até a altura do tubarão, NPC anda para cima ou para baixo e NPC fica parado onde está. Note que, pelo fato de se ter três opções, o NPC escolhe uma dessas três para executar a ação correspondente. Dessa forma, durante a execução do jogo, o NPC realizará uma sequência de ações que variam entre essas três previamente explicadas. Essa sequência é determinada por meio de uma sequência de sorteios, onde cada ação possui uma certa probabilidade de ser escolhida pelo NPC.

Olhando melhor a Figura 1, você perceberá que temos um total de seis variáveis que ainda não possuem valores. São elas: P1, P2, P3, t1, t2 e tp. Essas variáveis são, respectivamente: probabilidade do NPC ir até a altura do tubarão, probabilidade do NPC andar para cima ou para baixo, probabilidade do NPC ficar parado, tempo mínimo do NPC andar para cima ou para baixo, tempo máximo do NPC andar para cima ou para baixo e tempo do NPC ficar parado. Note que, se esses valores serem mudados, o NPC se torna mais inteligente ou não. Por exemplo, se a probabilidade do NPC ir até a altura do tubarão ser próximo a 100%, muito dificilmente ele não rebaterá o tubarão.

Dependendo da valoração dessas variáveis, nós deixamos o jogo mais fácil ou mais difícil. Valorá-las não é trivial e precisa da realização de vários testes pelo game designer para que ele possa deixar o jogo desafiante, porém, não frustrante. Por isso, após o término desse artigo, o jogo pode parecer muito difícil ou muito fácil. Isso será corrigido após o game designer valorar essas variáveis por meio dos testes que eu comentei. A valoração inicial proposta no GDD é P1=50%, P2=25%, P3=25%, t1=0,1 segundos, t2=0,3 segundos e tp=0,3 segundos. Provavelmente esses valores serão modificados em um artigo futuro com o objetivo de balancear a dificuldade do jogo.

 

Implementação da máquina de estados

BabbageDifferenceEngineConhecendo o funcionamento lógico da inteligência artificial do jogo, vamos implementá-la com código. Será necessário implementar apenas um único método, que fará a escolha de qual ação que o NPC tomará após o término da ação anterior. Dessa forma, esse método será chamado constantemente para determinar qual será a próxima ação que o NPC tomará, dentre aquelas explicadas anteriormente.

Abra o arquivo “HelloWorldScene.h” e adicione o protótipo do método “tomaDecisaoNPC”. Para isso, inclua a seguinte linha de código:

void tomaDecisaoNPC();

logo abaixo dessa:

float posicaoInicialTubarao[2];

Salve esse arquivo e abra o arquivo “HelloWorldScene.cpp”. Primeiramente, realizaremos a primeira chamada do método “tomaDecisaoNPC” para que a inteligência artificial seja iniciada. Assim, adicione a seguinte linha de código:

HelloWorld::tomaDecisaoNPC();

logo abaixo dessa:

schedule(schedule_selector(HelloWorld::aumentaVelocidadeTubarao),10);

Agora implementaremos o método “tomaDecisaoNPC”. Adicione as seguintes linhas de código no final do arquivo:

void HelloWorld::tomaDecisaoNPC() {

    static unsigned int r = time(NULL);

    float prob,tempo;

    CCSize size = CCDirector::sharedDirector()->getWinSize();

    CCMoveTo* andar;

    CCCallFunc* novaDecisao = CCCallFunc::create(this,callfunc_selector(HelloWorld::tomaDecisaoNPC));

    CCSequence* seq;

    srand(r);

    r = rand();

    prob = ((float)r)/RAND_MAX;

    if(prob<=0.5) {

        //NPC vai até a altura atual do tubarão

        if(HelloWorld::tubarao->getPositionY()>HelloWorld::paIA->getPositionY()) {

            tempo = (fminf(HelloWorld::tubarao->getPositionY(),0.9*size.height) – HelloWorld::paIA->getPositionY())/300.0;

            andar = CCMoveTo::create(tempo,ccp(HelloWorld::paIA->getPositionX(),fminf(HelloWorld::tubarao->getPositionY(),0.9*size.height)));

            HelloWorld::paIA->setFlipY(false);

        } else {

            tempo = (HelloWorld::paIA->getPositionY() – fmaxf(HelloWorld::tubarao->getPositionY(),0.3*size.height))/300.0;

            andar = CCMoveTo::create(tempo,ccp(HelloWorld::paIA->getPositionX(),fmaxf(HelloWorld::tubarao->getPositionY(),0.3*size.height)));

            HelloWorld::paIA->setFlipY(true);

        }

        seq = CCSequence::create(andar,novaDecisao,NULL);

    } else if(prob<=0.75) {

        //NPC anda para cima ou para baixo

        srand(r);

        r = rand();

        if(r<RAND_MAX/2) {

            srand(r);

            r = rand();

            tempo = fminf(abs(0.9*size.height – HelloWorld::paIA->getPositionY())/300.0,0.1 + (((float)r)/RAND_MAX)*0.2);

            andar = CCMoveTo::create(tempo,ccp(HelloWorld::paIA->getPositionX(),HelloWorld::paIA->getPositionY() + tempo*300.0));

            HelloWorld::paIA->setFlipY(false);

        } else {

            srand(r);

            r = rand();

            tempo = fminf(abs(0.3*size.height – HelloWorld::paIA->getPositionY())/300.0,0.1 + (((float)r)/RAND_MAX)*0.2);

            andar = CCMoveTo::create(tempo,ccp(HelloWorld::paIA->getPositionX(),HelloWorld::paIA->getPositionY() – tempo*300.0));

            HelloWorld::paIA->setFlipY(true);

        }

        seq = CCSequence::create(andar,novaDecisao,NULL);

    } else {

        //NPC fica parado

        CCDelayTime* tempoParado = CCDelayTime::create(0.3);

        seq = CCSequence::create(tempoParado,novaDecisao,NULL);

    }

    HelloWorld::paIA->runAction(seq);

}

Se você analisar o método implementado, perceberá que ele é dividido nas três opções disponíveis para o NPC. Primeiramente, é sorteado um valor entre 0 e 1. Se esse valor for menor do que 0,5, então o NPC subirá até a altura atual do tubarão. Se o valor sorteado está entre 0,5 e 0,75, então o NPC anda ou para cima ou para baixo. Caso contrário o NPC fica parado. Perceba que o sistema de escolha funciona como uma roleta, onde o valor sorteado se encontra em alguma das porcentagens determinadas.

Se o NPC escolhe subir até a altura do tubarão ou escolhe subir ou descer aleatoriamente, então é calculado o tempo necessário para ele realizar tais ações e a altura da tela para onde o NPC deverá se movimentar. Note que a lógica é simples e funciona exatamente como descrito no GDD. O resultado vocês podem olhar no vídeo a seguir. Lembrem-se que o game designer talvez modificará os valores das variáveis P1, P2, P3, t1, t2 e tp para balancear a dificuldade do jogo.

Vimos nesse artigo como funciona a inteligência artificial do Shark Pong. Após uma breve explicação do que é uma máquina de estados, nós vimos como funciona a máquina de estados que faz o NPC decidir qual ação tomará. Por último, implementamos o único método necessário que realiza o processamento da máquina de estados criada.

Implementaremos no próximo tutorial as animações criadas pelo Filipe em seus artigos de design visual e, provavelmente, atualizaremos as variáveis da inteligência artificial. Além disso, implementaremos o power up do polvo.

Um grande abraço e até mais. []

Santiago Viertel

Santiago Viertel

Formado em Bacharelado em Ciência da Computação (UDESC), mestre e doutorando em Análise de Algoritmos (UFPR). Foi programador da Céu Games por 8 anos. Possui a preferência por jogos de estratégia e de tiro em primeira pessoa. Jogando bastante DotA 2, Left 4 Dead 2 e Age of Empires II HD.

Send this to a friend