🤔 Para Refletir :
"Fazer um jogo é um ótimo aprendizado para se notar que normalmente o que é bom não é por acaso e sim fruto de muito planejamento e trabalho."
- Rafael_Sol_MAKER

Desafio: Fazer com que o personagem se mova até um local específico do mapa.

DadoCWB Masculino

Duque
Membro
Membro
"Esto no me gusta"
Juntou-se
02 de Setembro de 2018
Postagens
724
Bravecoins
1.826

Dando continuidade a meus estudos com o RPG MAKER, encarei o desafio de fazer com que o personagem reconheça o mapa ao seu redor e encontre um caminho livre até um local objetivo. Caso alguém se interesse por esse tipo de quebra cabeça, esteja a vontade para participar da conversa. Eu vou usar o MZ e programar por script mas a solução pra esse tipo de problema independe da linguagem ou da ferramenta; Na boa, eu não sei nem por onde começar.

desafio.jpg

Nota 1: As montanha são obstáculos intransponíveis.
Nota 2: As bordas do mapa devem ser bloqueadas.
Nota 3: O objetivo não pode bloquear o caminho do personagem.
objetivo.jpg


Dica para configurar o evento que alerta que o personagem alcançou o objetivo:
OBJETIVO2.jpg



 
Última edição:
Eu ficaria imensamente agradecido.... Nunca fiz nada parecido com isso antes...
Uma ideia inicial é fazer o personagem ficar andando aleatoriamente até que um dia, talvez, ele encontre o objetivo.



A primeira solução que encontrei faz o personagem ficar andando que nem um doido pra lá e pra cá dentro do mapa até que um dia ele meio que esbarra no objetivo. É um script meio burro mas funciona. Penso que se no lugar de movimentar o personagem ele movimentar inimigos dentro de uma masmorra essa solução pode ser de bastante utilidade.


O código funciona tanto no MZ quanto no MV:
Código:
//=============================================================================
// RPG Maker MZ -  IA v0.1    16-09-2020
//=============================================================================
/*:
* @target MZ
* @plugindesc desafio IA: fazer o pers. ir até um local  obetivo
* @author DadoCWB eudado gmail.com
*
* @help IA.js
*
*/

function IAforEvents(){
    this.initialize.apply(this);
};

IAforEvents.prototype.initialize=function(){
    //flag que mantem o personagem se movendo sozinho
    this._mover = true;
};

//liga/desliga o movimento automata do personagem
IAforEvents.prototype.setMover=function(valor){
    this._mover =valor;
};


IAforEvents.prototype.update=function(x=$gamePlayer.x,y=$gamePlayer.y,d=$gamePlayer.direction()){
    //executa apenas se não estiver se movendo e se a flag this._mover estiver ligada
    if (!$gamePlayer.isMoving() && this._mover ==true){
        //verifica quais as direções livres
        var livres     = this.options(x,y);
        //tam é a quantidade de direções livres
        var tam     = livres.length;
        //se existir mais de uma opção, impede que o personagem escolha "voltar"
        if(tam>1){
            var index = livres.indexOf(10-d);
            livres.splice(index,1);
        }
        //sorteia uma direção livre
        var n        = Math.randomInt(tam);
        var dir     = livres[n];
        //move na direção livre
        $gamePlayer.moveStraight(dir);
    }
};
//verifica as opcoes que o personagem tem para seguir em frente
IAforEvents.prototype.options=function(x,y){
    var array = new Array();
    var up         = $gameMap.isPassable(x,y-1);
    var down     = $gameMap.isPassable(x,y+1);
    var left     = $gameMap.isPassable(x-1,y);
    var right     = $gameMap.isPassable(x+1,y);

    if(up==true){
        array.push(8);
    }
    if(down==true){
        array.push(2);
    }
    if(left==true){
        array.push(4);
    }
    if(right==true){
        array.push(6);
    }

    return array;
}

var $gameIA=null; //objeto que atualiza as configuraoes gerais IN GAME

var _createIA  = DataManager.createGameObjects;
DataManager.createGameObjects = function(){
    _createIA.call(this);
    $gameIA= new IAforEvents();
}

//------------------------------------------------------
var _Scene_Map_MyGame = Scene_Map.prototype.update;
Scene_Map.prototype.update = function() {
    _Scene_Map_MyGame.call(this);

    $gameIA.update();
};


Para quem tiver curiosidade, a parte do código onde as coisas acontecem é essa:

codigo.jpg



Dentro do código tem um variável que "liga" e "desliga" movimento do personagem. Quando o código é iniciado essa variável é ligada.
true.jpg


Nesse Desafio específico quem desativa ela é o evento designado como objetivo no próprio mapa:

false.jpg


Qual é o primeiro problema que eu vejo nesse script: As vezes o personagem passa "reto" igual a uma mula ao lado do objetivo. Isso acontece por que ele não sabe que está procurando por um objetivo. Ele sabe apenas que tem que ficar andando.

O próximo passo é fazer o personagem reconhecer o objetivo caso chegue próximo a ele...

alcancado2.jpg


Na nova implementação do código, o personagem ficou menos burro. Ele agora reconhece um objetivo quando passa adjacente a ele. E quando reconhece esse objetivo, ele não perde tempo e agarra o danado. O novo código ficou assim:​
Código:
//=============================================================================
// RPG Maker MZ -  IA v0.2    16-09-2020
//=============================================================================
/*:
* @target MZ
* @plugindesc desafio IA: fazer o pers. ir até um local  obetivo
* @author DadoCWB eudado gmail.com
*
* @help IA.js
*
*/

function IAforEvents(){
    this.initialize.apply(this);
};

IAforEvents.prototype.initialize=function(){
    //flag que mantem o personagem se movendo sozinho
    this._mover = true;
    //array que armazena a ID dos objetivos. Os objetivos são eventos específicos.
    this._objetivos= new Array();
};

//liga/desliga o movimento automata do personagem
IAforEvents.prototype.setMover=function(valor){
    this._mover =valor;
};

//seta os objetivos para o personagem. (Id dos eventos)
IAforEvents.prototype.setObjetivo=function(eventId){
    this._objetivos.push(eventId);
};



IAforEvents.prototype.update=function(x=$gamePlayer.x,y=$gamePlayer.y,d=$gamePlayer.direction()){
    //executa apenas se não estiver se movendo e se a flag this._mover estiver ligada
    if (!$gamePlayer.isMoving() && this._mover ==true){
        //verifica se existe um objetivo adjacente
        var obj = this.ifObjetivo(x,y);
        if ( obj>0){
            //se existe um objetivo adjacente, move o personagem na direção do objetivo
            $gamePlayer.moveStraight(obj);
            //encerra a procura pelo objetivo
            this.setObjetivo(false);
        }else{
            //verifica quais as direções livres
            var livres     = this.options(x,y);
            //tam é a quantidade de direções livres
            var tam     = livres.length;
            //se existir mais de uma opção, impede que o personagem escolha "voltar"
            if(tam>1){
                var index = livres.indexOf(10-d);
                livres.splice(index,1);
            }
            //sorteia uma direção livre
            var n        = Math.randomInt(tam);
            var dir     = livres[n];
            //move na direção livre
            $gamePlayer.moveStraight(dir);
        }
    }
};

//verifica se existe objetivos adjacentes ao personagem
IAforEvents.prototype.ifObjetivo=function(x,y){
    var up         = this.isObjetivo(x,y-1);
    var down     = this.isObjetivo(x,y+1);
    var left     = this.isObjetivo(x-1,y);
    var right     = this.isObjetivo(x+1,y);

    if(up==true){
        return 8;
    }
    else if(down==true){
        return 2;
    }
    else if(left==true){
        return 4;
    }
    else if(right==true){
        return 6;
    }
    else{
        return 0
    }
}



//verifica se existe um objetivo na coordenada x,y
IAforEvents.prototype.isObjetivo=function(x,y){
    var eventId = $gameMap.eventIdXy(x, y);
    var r = this._objetivos.indexOf(eventId);
    if (r>=0){
        return true;
    }else{
        return false;
    }
}



//verifica as opcoes que o personagem tem para seguir em frente
IAforEvents.prototype.options=function(x,y){
    var array = new Array();
    var up         = $gameMap.isPassable(x,y-1);
    var down     = $gameMap.isPassable(x,y+1);
    var left     = $gameMap.isPassable(x-1,y);
    var right     = $gameMap.isPassable(x+1,y);

    if(up==true){
        array.push(8);
    }
    if(down==true){
        array.push(2);
    }
    if(left==true){
        array.push(4);
    }
    if(right==true){
        array.push(6);
    }

    return array;
}

var $gameIA=null; //objeto que atualiza as configuraoes gerais IN GAME

var _createIA  = DataManager.createGameObjects;
DataManager.createGameObjects = function(){
    _createIA.call(this);
    $gameIA= new IAforEvents();
    $gameIA.setObjetivo(1); //informa que o evento de ID 1 é um objetivo
}

var _Scene_Map_MyGame = Scene_Map.prototype.update;
Scene_Map.prototype.update = function() {
    _Scene_Map_MyGame.call(this);

    $gameIA.update();
};

Na próxima implementação haverá mais de um objetivo a ser alcançado no mapa. Assim a movimentação do herói só irá cessar quando ele pegar todos os objetivos.

ver3.jpg

O mapa agora contém 3 objetivos que podem ser coletados pelo personagem. Quando ele coleta os três objetivos "a brincadeira" acaba.
Código:
//=============================================================================
// RPG Maker MZ -  IA v0.3    16-09-2020
//=============================================================================
/*:
* @target MZ
* @plugindesc desafio IA: fazer o pers. ir até um local  obetivo
* @author DadoCWB eudado gmail.com
*
* @help IA.js
*
*/

function IAforEvents(){
    this.initialize.apply(this);
};

IAforEvents.prototype.initialize=function(){
    //flag que mantem o personagem se movendo sozinho
    this._mover = true;
    //array que armazena a ID dos objetivos. Os objetivos são eventos específicos.
    this._objetivos= new Array();
};

//liga/desliga o movimento automata do personagem
IAforEvents.prototype.setMover=function(valor){
    this._mover =valor;
};

//seta os objetivos para o personagem. (Id dos eventos)
IAforEvents.prototype.setObjetivo=function(eventId){
    this._objetivos.push(eventId);
};



IAforEvents.prototype.update=function(x=$gamePlayer.x,y=$gamePlayer.y,d=$gamePlayer.direction()){
    //executa apenas se não estiver se movendo e se a flag this._mover estiver ligada
    if (!$gamePlayer.isMoving() && this._mover ==true){
        //verifica se existe um objetivo adjacente
        var obj = this.ifObjetivo(x,y);
        if ( obj>0){
            //se existe um objetivo adjacente, move o personagem na direção do objetivo
            $gamePlayer.moveStraight(obj);
            //apaga o objetivo da lista de objetivos
            var eventId = $gameMap.eventIdXy(x, y);
            var index     = this._objetivos.indexOf(eventId);
            this._objetivos.splice(index,1);
            //encerra a procura pelo objetivo caso a lista esteja vazia
            if(this._objetivo.length<=0){
                this._mover=false;
            }
        }else{
            //verifica quais as direções livres
            var livres     = this.options(x,y);
            //tam é a quantidade de direções livres
            var tam     = livres.length;
            //se existir mais de uma opção, impede que o personagem escolha "voltar"
            if(tam>1){
                var index = livres.indexOf(10-d);
                livres.splice(index,1);
            }
            //sorteia uma direção livre
            var n        = Math.randomInt(tam);
            var dir     = livres[n];
            //move na direção livre
            $gamePlayer.moveStraight(dir);
        }
    }
};

//verifica se existe objetivos adjacentes ao personagem
IAforEvents.prototype.ifObjetivo=function(x,y){
    var up         = this.isObjetivo(x,y-1);
    var down     = this.isObjetivo(x,y+1);
    var left     = this.isObjetivo(x-1,y);
    var right     = this.isObjetivo(x+1,y);

    if(up==true){
        return 8;
    }
    else if(down==true){
        return 2;
    }
    else if(left==true){
        return 4;
    }
    else if(right==true){
        return 6;
    }
    else{
        return 0
    }
}



//verifica se existe um objetivo na coordenada x,y
IAforEvents.prototype.isObjetivo=function(x,y){
    var eventId = $gameMap.eventIdXy(x, y);
    var r = this._objetivos.indexOf(eventId);
    if (r>=0){
        return true;
    }else{
        return false;
    }
}



//verifica as opcoes que o personagem tem para seguir em frente
IAforEvents.prototype.options=function(x,y){
    var array = new Array();
    var up         = $gameMap.isPassable(x,y-1);
    var down     = $gameMap.isPassable(x,y+1);
    var left     = $gameMap.isPassable(x-1,y);
    var right     = $gameMap.isPassable(x+1,y);

    if(up==true){
        array.push(8);
    }
    if(down==true){
        array.push(2);
    }
    if(left==true){
        array.push(4);
    }
    if(right==true){
        array.push(6);
    }
 
    return array;
}

var $gameIA=null; //objeto que atualiza as configuraoes gerais IN GAME

var _createIA  = DataManager.createGameObjects;
DataManager.createGameObjects = function(){
    _createIA.call(this);
    $gameIA= new IAforEvents();
    $gameIA.setObjetivo(1); //informa que o evento de ID 1 é um objetivo
    $gameIA.setObjetivo(2); //informa que o evento de ID 2 é um objetivo
    $gameIA.setObjetivo(3); //informa que o evento de ID 3 é um objetivo

}

var _Scene_Map_MyGame = Scene_Map.prototype.update;
Scene_Map.prototype.update = function() {
    _Scene_Map_MyGame.call(this);

    $gameIA.update();
};

O próximo problema a ser resolvido é que o personagem pode levar um tempo muito grande para encontrar os objetivos. As vezes ele fica "procurando" sempre pelos lugares onde já esteve. Então a ideia é fazer ele memorizar os locais onde já esteve e diminuir a chance dele se movimentar pra esses locais. (honestamente não sei se consigo fazer isso).

v.04
O sistema de memória ficou bom. Não ficou perfeito já que o personagem continuar voltando aos lugares onde ja esteve, porém com menos frequência que antes. Ele agora lembra dos locais onde já esteve e isso influência estatisticamente quando ele escolha a direção para onde seguir.

A solução implementada baseia-se em uma matriz 256 x 256 que armazena em cada celula [x][y] o numero de vezes que opersonagem ja passou por uma posicão do mapa. Quanto mais vezes ele passou por uma posição menos chance existirá dele seguir por ela.

Código:
//=============================================================================
// RPG Maker MZ -  IA v0.4    16-09-2020
//=============================================================================
/*:
* @target MZ
* @plugindesc desafio IA: fazer o pers. ir até um local  obetivo
* @author DadoCWB eudado gmail.com
*
* @help IA.js
*
*/

function IAforEvents(){
    this.initialize.apply(this);
};

IAforEvents.prototype.initialize=function(){
    //flag que mantem o personagem se movendo sozinho
    this._mover = true;
    //array que armazena a ID dos objetivos. Os objetivos são eventos específicos.
    this._objetivos= new Array();
    //memoria é uma matriz com a maxima dimensão de um mapa
    this._memoria=new Array(256);
    var i =0;
    for(i=0; i<256;i++){
        this._memoria[i]=new Array(256).fill(0);
    }
};

//liga/desliga o movimento automata do personagem
IAforEvents.prototype.setMover=function(valor){
    this._mover =valor;  
};

//seta os objetivos para o personagem. (Id dos eventos)
IAforEvents.prototype.setObjetivo=function(eventId){
    this._objetivos.push(eventId);  
};
//Memorizar significa aumentar um contador na posição [x][y] da memoria
IAforEvents.prototype.memorizar=function(x,y){
    this._memoria[x][y]+=1;
};

//Pega o contador na posicao [x][y] da memoria
IAforEvents.prototype.getMemoria=function(x,y){
    return this._memoria[x][y];
};


IAforEvents.prototype.update=function(x=$gamePlayer.x,y=$gamePlayer.y,d=$gamePlayer.direction()){
    //executa apenas se não estiver se movendo e se a flag this._mover estiver ligada
    if (!$gamePlayer.isMoving() && this._mover ==true){  
        //verifica se existe um objetivo adjacente
        var obj = this.ifObjetivo(x,y);
        if ( obj>0){
            //se existe um objetivo adjacente, move o personagem na direção do objetivo
            $gamePlayer.moveStraight(obj);
            //apaga o objetivo da lista de objetivos
            var eventId = $gameMap.eventIdXy(x, y);
            var index     = this._objetivos.indexOf(eventId);
            this._objetivos.splice(index,1);
            //encerra a procura pelo objetivo caso a lista esteja vazia
            if(this._objetivo.length<=0){
                this._mover=false;  
            }
        }else{
            //verifica quais as direções livres
            var livres     = this.options(x,y);
            //tam é a quantidade de direções livres
            var tam     = livres.length;
            //se existir mais de uma opção, impede que o personagem escolha "voltar"
            if(tam>1){
                var index = livres.indexOf(10-d);
                livres.splice(index,1);
            }
            //sorteia uma direção livre
            var n        = Math.randomInt(tam);
            var dir     = livres[n];
            //move na direção livre
            $gamePlayer.moveStraight(dir);
            //memoriza a posicao
            this.memorizar(x,y);
        }
    }
};

//

//verifica se existe objetivos adjacentes ao personagem
IAforEvents.prototype.ifObjetivo=function(x,y){
    var up         = this.isObjetivo(x,y-1);
    var down     = this.isObjetivo(x,y+1);
    var left     = this.isObjetivo(x-1,y);
    var right     = this.isObjetivo(x+1,y);

    if(up==true){
        return 8;
    }
    else if(down==true){
        return 2;
    }
    else if(left==true){
        return 4;
    }
    else if(right==true){
        return 6;
    }
    else{
        return 0
    }
}



//verifica se existe um objetivo na coordenada x,y
IAforEvents.prototype.isObjetivo=function(x,y){
    var eventId = $gameMap.eventIdXy(x, y);
    var r = this._objetivos.indexOf(eventId);
    if (r>=0){
        return true;
    }else{
        return false;
    }
}



//verifica as opcoes que o personagem tem para seguir em frente
IAforEvents.prototype.options=function(x,y){
    var array = new Array();
    var up         = $gameMap.isPassable(x,y-1);
    var down     = $gameMap.isPassable(x,y+1);
    var left     = $gameMap.isPassable(x-1,y);
    var right     = $gameMap.isPassable(x+1,y);
    //pega a memoria das posicoes
    var memUp        =this.getMemoria(x,y-1);
    var memDown        =this.getMemoria(x,y+1);
    var memLeft        =this.getMemoria(x-1,y);
    var memRight    =this.getMemoria(x+1,y);
    //verifica qual memoria é maior
    var maior = memUp;
    if (memDown>maior){
        maior=memDown
    }
    if(memLeft>maior){
        maior =memLeft;
    }
    if(memRight>maior){
        maior=memRight;
    }
    //adiciona 1 à maior memoria
    maior+=1;
    //subtrai todas as memorias da maior
    memUp         = maior - memUp;
    memDown     = maior- memDown;
    memRight     = maior - memRight;
    memLeft     = maior - memLeft;

    var i;
    if(up==true){
        for(i=0; i< memUp;i++){
            array.push(8);
        }
    }
    if(down==true){
        for(i=0; i< memDown;i++){
            array.push(2);
        }
    }
    if(left==true){
        for(i=0; i< memLeft;i++){
            array.push(4);
        }
    }
    if(right==true){
        for(i=0; i< memRight;i++){
            array.push(6);
        }
    }
   
    return array;
}

var $gameIA=null; //objeto que atualiza as configuraoes gerais IN GAME

var _createIA  = DataManager.createGameObjects;
DataManager.createGameObjects = function(){
    _createIA.call(this);
    $gameIA= new IAforEvents();
    $gameIA.setObjetivo(1); //informa que o evento de ID 1 é um objetivo
    $gameIA.setObjetivo(2); //informa que o evento de ID 2 é um objetivo
    $gameIA.setObjetivo(3); //informa que o evento de ID 3 é um objetivo

}

var _Scene_Map_MyGame = Scene_Map.prototype.update;
Scene_Map.prototype.update = function() {
    _Scene_Map_MyGame.call(this);

    $gameIA.update();

};

Para encerrar esse estudo acredito que seja preciso fazer o personagem "enxergar" o objetivo à distância. Vamos dizer que a capacidade dele enxergar a distância é de 10 quadrados desde que sua linha de visão não esteja obstruída. Mas isso fica pra outro dia, pecisodormir um pouco....
 

Anexos

  • codigo.jpg
    codigo.jpg
    96,8 KB · Visualizações: 49
Última edição:
A melhor solução, com certeza, é implementar um algoritmo de busca. Temos o Dijkstra e o A*.
Bom, acho que fica melhor de entender como funciona se a gente desenhar no papel mais ou menos como funciona. Talvez seja conteúdo para o canal do Condado Braveheart 🤔 (se for uma boa ideia posso fazer um vídeo).

Mas pra vc analisar enquanto isso, eu fiz uma implementação numa aplicação de terminal usando a linguagem Java.
Se quiser dar uma estudada nele, dá uma passada lá no meu Github que eu tenho um repositório que possa te ajudar. Tá tudo comentadinho, mas se você e a galera se interessar, eu faço um vídeo tudo mastigadinho e ainda vou usar o RPG Maker pra ficar mais "rardecori" kkkkkkk !!

LLmHDqr.png
 
Opa, tem dois conceitos que é bom conhecer antes de implementar qualquer algoritmo pathfinding: um Grafo e uma Fila de prioridade.

Quanto ao algoritmo em si, se você só quer encontrar um caminho (qualquer que seja) o que você precisa é basicamente 1. uma função de V -> V[] que dado um vértice te dá todos os vértices ligados a ele (i.e. seus "vizinhos") e 2. uma forma de testar todos os caminhos possíveis.

No caso do RPG Maker, o grafo é simplificado porque é uma grade quadriculada, então determinar os vizinhos é bem simples (só somar as coordenadas!). Então, basicamente, nosso conjunto de vértices vai ser [0...m) x [0...n), onde m e n são a largura e altura do mapa (i.e., todos os pares (x, y) onde 0 <= x < m e 0 <= y < n), e nosso conjunto de arestas vai ser A = { (p, q) | p, q ∈ V e d1(p, q) = 1 } (d1 = distância de manhattan, que é um jeito bonito de dizer "quantos quadradinhos de distância"). Isso assumindo só movimento em 4 direções, movimento diagonal fica como exercício haha

(Status: temos V = [0...m) x [0...n) e A = { (p, q) | p, q ∈ V e d1(p, q) = 1 })

Agora vamos supor um mapa assim:
v09w7vC.png

Temos: V = [1...6] x [1...9], S = (1, 5), E = (6, 1).

Agora, o que significa agora achar um caminho de S para E? Um caminho é uma sequência finita de vértices onde:

jVvjZa0.png


(n = tamanho da sequência)

Ou seja, o primeiro elemento da sequência é S, o último é E, e de cada elemento para o próximo existe uma aresta. Dado que aceitamos essa definição, qualquer sequência que encontrarmos e que satisfaça essa condição resolve nosso problema.

Tem um detalhe nesse mapa, porém, que também existe no RPG Maker e que nossa definição do grafo ainda não cobre: existem posições no mapa que não são atravessáveis, e essas posições não podem fazer parte de um caminho.

Tem um jeito fácil de resolver isso: sumir com os vértices das posições que não são atravessáveis. Pra isso, vou invocar uma função qualquer pass(v) que me diz se um tile é passável (no RPG Maker, essa seria a Game_Map#isPassable). Com ela, os novos vértices ficam: V' = { p ∈ V | pass(p) } (ou seja, os mesmos vértices de antes, desde que sejam atravessáveis).

Se fosse para desenhar no mapa os vértices e arestas do grafo agora, teríamos a seguinte figura:

55LSjcf.png

Com isso, temos a base pra fazer um algoritmo simples de busca de caminho. O mais simples seria assim (pseudocódigo):

Código:
def find_path(vertices, edges, start, end, visited = {}, path = []):
    if start = end:
        return path + [end]

    else:
        visited' := visited ∪ { start }
        path' := path + [start]

        for neighbor in { v | v ∉ visited && (start, v) ∈ edges }:
            path'' := find_path(vertices, edges, neighbor, end, visited', path')

            if path'' != ∅:
                return path''

        return ∅

Explicando: é uma função recursiva, que varia o ponto de início e vai construindo o caminho em uma variável passada por argumento.

Como toda boa função recursiva, temos um caso base:
Código:
    if start == end:
        return path + [end]

Nesse caso, a base é start == end, que significa um caminho de um ponto para ele mesmo. Nesse caso, claro que o caminho tem só um ponto, que é o próprio fim (e começo, eles são iguais).

Além do caso base, claro, temos um passo recursivo:
Código:
        visited' := visited ∪ { start }
        path' := path + [start]

        for neighbor in { v | v ∉ visited && (start, v) ∈ edges }:
            path'' := find_path(vertices, edges, neighbor, end, visited', path')

            if path'' != ∅:
                return path''

        return ∅

Aqui a gente mantém um conjunto de vértices já visitados (pra não entrar em loop: como as arestas são bidirecionais, se não fizermos isso o próximo vizinho vai chamar a função pra esse mesmo ponto de novo) e colocamos o vértice que visitamos no caminho que estamos construindo. Daí, a recursão acontece: o caminho atual é o caminho que achamos até agora, mais qualquer que seja o caminho que vamos achar a partir do nosso vizinho.

Tem um caso especial também que é quando não é possível achar nenhum caminho, porque não tem vizinhos pelos quais dá pra achar um caminho. Nesse caso, a gente retorna ∅ (vazio, nulo, nenhum caminho).

Coisas a se manter em mente: esse algoritmo é MUITO INEFICIENTE. O tempo de execução dele é exponencial de acordo com o número de arestas. Ele também não garante que vai achar o menor caminho, o que geralmente é desejável.

Por que eu expliquei ele? Primeiro, porque todo mundo sempre lembra do Dijkstra e A* quando a gente pensa em buscar caminhos, mas esse algoritmo tem um nome também: Backtracking (uma variação dele, pelo menos). É um algoritmo bem essencial e mais geral que Dijkstra ou A*, que você pode usar pra outras coisas como por exemplo resolver sudoku.

Segundo, porque é um algoritmo mais simples de entender que os outros mais especializados, e a base é parecida (a maior diferença vai ser que ao invés de usar a pilha, fazendo recursão, você vai usar uma fila ou fila de prioridade, e botar uma heurística no caso do A*).

Se quiser uma implementação pronta de A* no RPG Maker, tem o meu gist pro VX Ace: Script de pathfinding (menor caminho) para RPG Maker VX Ace. A maior diferença pro MZ é que é em Ruby, em termos de API teve muito pouca mudança desde o VX Ace, pelo menos no que tange a parte que o script usa.
 
Última edição:
Vou ter que dar uma estudada nessas coisas que vocês trouxeram. Confesso que eu sequer sabia da existência disso tudo.
Obriado pelos links do git, vou fuçar os códigos recomendados.

brandt.jpg
cenoura.jpg
G

@Dr.XGB Uma vídeo aula seria show hein! Valew...

@Bradt, cara obrigado pela aula... vou me esforçar p entender isso mais a fundo e valer todo o trabalho que teve pra explicar ... Obrigado.



v05
Para encerrar esse estudo acredito que seja preciso fazer o personagem "enxergar" o objetivo à distância. Vamos dizer que a capacidade dele enxergar a distância é de 10 quadrados desde que sua linha de visão não esteja obstruída. Apesar da visão ser limitada em linha reta, nosso personagem já ficou "maladrinho" e consegue recolher os objetivos com mais rapidez.

O bacana desse código é que ele simula o fato de que personagem não sabe onde está os objetivos no mapa e nem sabe qual a melhor rota para chegar até eles. Ele vai procurando, procurando até que vai "se ligando" onde os objetivos não estão.

Infelizmente o código ficou monstruoso e deselgante, já que foi feito "nas coxas" e sem planejamento nenhum. Em breve venho (não sei quando) trarei a versão final desse código, otimizado e mais organizado. Obrigado a todos que chegaram até aqui.

Código:
//=============================================================================
// RPG Maker MZ -  IA v0.5    16-09-2020
//=============================================================================
/*:
* @target MZ
* @plugindesc desafio IA: fazer o pers. ir até um local  obetivo
* @author DadoCWB eudado gmail.com
*
* @help IA.js
*
*/

function IAforEvents(){
    this.initialize.apply(this);
};

IAforEvents.prototype.initialize=function(){
    //flag que mantem o personagem se movendo sozinho
    this._mover = true;
    //array que armazena a ID dos objetivos. Os objetivos são eventos específicos.
    this._objetivos= new Array();
    //memoria é uma matriz com a maxima dimensão de um mapa
    this._memoria=new Array(256);
    var i =0;
    for(i=0; i<256;i++){
        this._memoria[i]=new Array(256).fill(0);
    }
};

//liga/desliga o movimento automata do personagem
IAforEvents.prototype.setMover=function(valor){
    this._mover =valor;
};

//seta os objetivos para o personagem. (Id dos eventos)
IAforEvents.prototype.setObjetivo=function(eventId){
    this._objetivos.push(eventId);
};
//Memorizar significa aumentar um contador na posição [x][y] da memoria
IAforEvents.prototype.memorizar=function(x,y){
    this._memoria[x][y]+=1;
};

//Pega o contador na posicao [x][y] da memoria
IAforEvents.prototype.getMemoria=function(x,y){
    return this._memoria[x][y];
};


IAforEvents.prototype.update=function(x=$gamePlayer.x,y=$gamePlayer.y,d=$gamePlayer.direction()){
    //executa apenas se não estiver se movendo e se a flag this._mover estiver ligada
    if (!$gamePlayer.isMoving() && this._mover ==true){
        //verifica se existe um objetivo adjacente
        var obj = this.ifObjetivo(x,y);
        if ( obj>0){
            //se existe um objetivo adjacente, move o personagem na direção do objetivo
            $gamePlayer.moveStraight(obj);
            //apaga o objetivo da lista de objetivos
            var eventId = $gameMap.eventIdXy(x, y);
            var index     = this._objetivos.indexOf(eventId);
            this._objetivos.splice(index,1);
            //encerra a procura pelo objetivo caso a lista esteja vazia
            if(this._objetivos.length<=0){
                this._mover=false;
            }
        }//verifica se existe um objeto na linha de visada
        else if(this._vision==true){
            //se existe um objetivo adjacente, move o personagem na sua linha de visada
            $gamePlayer.moveStraight(d);
            //apaga o objetivo da lista de objetivos
            var eventId = $gameMap.eventIdXy(x, y);
            var index     = this._objetivos.indexOf(eventId);
            this._objetivos.splice(index,1);
            //encerra a procura pelo objetivo caso a lista esteja vazia
            if(this._objetivos.length<=0){
                this._mover=false;
            }
     
        }
        else{
            //verifica quais as direções livres
            var livres     = this.options(x,y);
            //tam é a quantidade de direções livres
            var tam     = livres.length;
            //se existir mais de uma opção, impede que o personagem escolha "voltar"
            if(tam>1){
                var index = livres.indexOf(10-d);
                livres.splice(index,1);
            }
            //sorteia uma direção livre
            var n        = Math.randomInt(tam);
            var dir     = livres[n];
            //move na direção livre
            $gamePlayer.moveStraight(dir);
            //memoriza a posicao
            this.memorizar(x,y);
        }
    }
};

//

//verifica se existe objetivos adjacentes ao personagem
IAforEvents.prototype.ifObjetivo=function(x,y){
    var up         = this.isObjetivo(x,y-1);
    var down     = this.isObjetivo(x,y+1);
    var left     = this.isObjetivo(x-1,y);
    var right     = this.isObjetivo(x+1,y);

    if(up==true){
        return 8;
    }
    else if(down==true){
        return 2;
    }
    else if(left==true){
        return 4;
    }
    else if(right==true){
        return 6;
    }
    else{
        return 0
    }
}



//verifica se existe um objetivo na coordenada x,y
IAforEvents.prototype.isObjetivo=function(x,y){
    var eventId = $gameMap.eventIdXy(x, y);
    var r = this._objetivos.indexOf(eventId);
    if (r>=0){
        return true;
    }else{
        return false;
    }
}



//verifica as opcoes que o personagem tem para seguir em frente
IAforEvents.prototype.options=function(x,y){
    var array = new Array();
    var up         = $gameMap.isPassable(x,y-1);
    var down     = $gameMap.isPassable(x,y+1);
    var left     = $gameMap.isPassable(x-1,y);
    var right     = $gameMap.isPassable(x+1,y);
    //pega a memoria das posicoes
    var memUp        =this.getMemoria(x,y-1);
    var memDown        =this.getMemoria(x,y+1);
    var memLeft        =this.getMemoria(x-1,y);
    var memRight    =this.getMemoria(x+1,y);
    //verifica qual memoria é maior
    var maior = memUp;
    if (memDown>maior){
        maior=memDown
    }
    if(memLeft>maior){
        maior =memLeft;
    }
    if(memRight>maior){
        maior=memRight;
    }
    //adiciona 1 à maior memoria
    maior+=1;
    //subtrai todas as memorias da maior
    memUp         = maior - memUp;
    memDown     = maior- memDown;
    memRight     = maior - memRight;
    memLeft     = maior - memLeft;

    var i;
    if(up==true){
        for(i=0; i< memUp;i++){
            array.push(8);
        }
    }
    if(down==true){
        for(i=0; i< memDown;i++){
            array.push(2);
        }
    }
    if(left==true){
        for(i=0; i< memLeft;i++){
            array.push(4);
        }
    }
    if(right==true){
        for(i=0; i< memRight;i++){
            array.push(6);
        }
    }
 
    return array;
}

IAforEvents.prototype.vision=function(x,y,d,alcance=10){
    var xf = x;
    var yf = y;
    var array = new Array();
    var i=x;
    var j=y;

    switch(d){
    case 2:
        yf=y-alcance;
        for(j=y; j>yf;j--){
            if ($gameMap.isPassable(i,j)){;
                array.push(i,j);
            }else{
                break;
            }
        }
        break;
    case 4:
        xf=x-alcance;
        for(i=x; i>xf;i--){
            if ($gameMap.isPassable(i,j)){;
                array.push(i,j);
            }else{
                break;
            }
        }
        break;
    case 6:
        xf=x+alcance
        for(i=x; i<xf;i++){
            if ($gameMap.isPassable(i,j)){;
                array.push(i,j);
            }else{
                break;
            }
        }
        break;
    case 8:
        yf=y+alcance;
        for(j=y; j<yf;j++){
            if ($gameMap.isPassable(i,j)){;
                array.push(i,j);
            }else{
                break;
            }
        }
        break;
    }
    if(array.length>0){
        for(i=0; i<array.length; i++){
            let xy = array[i];
            let ex = xy[0];
            let ey = xy[1];
            var eventId = $gameMap.eventIdXy(ex, ey);
            var index     = this._objetivos.indexOf(eventId);
            if(index>=0){
                return true;
            }else{
                return false;
            }
        }
    }else{
        return false;
    }
}


var $gameIA=null; //objeto que atualiza as configuraoes gerais IN GAME

var _createIA  = DataManager.createGameObjects;
DataManager.createGameObjects = function(){
    _createIA.call(this);
    $gameIA= new IAforEvents();
    $gameIA.setObjetivo(1); //informa que o evento de ID 1 é um objetivo
    $gameIA.setObjetivo(2); //informa que o evento de ID 2 é um objetivo
    $gameIA.setObjetivo(3); //informa que o evento de ID 3 é um objetivo

}

var _Scene_Map_MyGame = Scene_Map.prototype.update;
Scene_Map.prototype.update = function() {
    _Scene_Map_MyGame.call(this);

    $gameIA.update();
};

 
Última edição:
Voltar
Topo Inferior