[Resolu][DB/MY-AL]PathFinding perso

Pour les scripts écrits en C#
Règles du forum
Merci de respecter la NOMENCLATURE suivante pour vos TITRES de messages :

Commencez par le niveau de vos scripts
DB = Débutant
MY = Moyen
CF = Confirmé

Puis le domaine d'application
-RS = Réseau
-AL = Algorithmie

Exemple :

[DB-RS] Mouvement perso multijoueur
djulio74
Messages : 682
Inscription : 19 Déc 2009 22:55

Re: [DB/MY-AL]PathFinding perso

Message par djulio74 » 16 Oct 2018 13:09

Allé juste pour taquiner un peu, juste pour chipoter. :lol:
Étant donner que les GetComponent sont gourmand, et que pour ta recherche de chemin tu va encore et encore devoir rechercher les voisins avec GetComponent, pour les éviter :

Code : Tout sélectionner

private GameObject[] Cellules;
public static List<List<GameObject>> voisin; 
	
   /// <summary>
    /// Parcours toutes les celules et ajoute à Voisins de chacune les cellules 
    /// qui sont à 1 case de décalage dans chaque direction
    /// </summary>
private void DefinitLesVoisins(){
		
	Cellules = GameObject.FindGameObjectsWithTag("Cellule"); // array de toute les cellules		
		
	// initialisation de voisin	
	// chaque cellule aura sa liste de voisin. voisin[i] = liste de voisin de Cellules[i]
	// permet de stocker les voisin pour chaque cellule, pour éviter les GetComponent (gourmand);
	voisin = new List<List<GameObject>>(); 
	for ( int i = 0 ; i < Cellules.Length ; i++){
		voisin.Add(new List<GameObject>() ); 
	}		

	for (int i = 0; i < Cellules.Length-1; i++) {
		for (int j = i + 1; j < Cellules.Length; j++) {
			Vector3 PositionItem1 = Cellules[i].transform.position;
			Vector3 PositionItem2 = Cellules[j].transform.position;
			float distance = (PositionItem1 - PositionItem2).magnitude;
			if ( Mathf.Approximately(distance ,1f) ){
				if (!voisin[i].Contains(Cellules[j])){
					voisin[i].Add(Cellules[j]);
					voisin[j].Add(Cellules[i]);
				}
			}
		}
	}
}

______________________________________________________________
\_______________________ Impossible is nothing _______________________/

EmileF
Messages : 676
Inscription : 18 Mars 2017 19:39

Re: [DB/MY-AL]PathFinding perso

Message par EmileF » 16 Oct 2018 13:37

Je comprends, mais plus loin chaque cellule à aussi un champ Visite, il me faut donc quand même faire appel au getComponent.

Ne serait-il pas mieux de convertir une bonne fois dès le départ les GameObject en Tile dans Cellules.

Code : Tout sélectionner

List<Tile> Cellules;

    public void DefinitCellulesEtVoisins()
    {
        Cellules = new List<Tile>();
        foreach (var item in GameObject.FindGameObjectsWithTag("Cellule"))
        {
            Cellules.Add(item.GetComponent<Tile>());
        }
        DefinitLesVoisins();
    }
Mais ça m'obligerait de faire la même opération en sens inverse pour retourner le chemin pour le perso

Code : Tout sélectionner

    public List<GameObject> Chemin
    {
        get
        {
            List<GameObject> result = new List<GameObject>();
            foreach (var item in Path)
            {
                result.Add(item.gameObject);
            }
            return result;
        }
    }

De cette façon je n'ai plus de getComponent dans tout le reste du code.
La différence entre l'intelligence et la stupidité est que l'intelligence est limitée.

djulio74
Messages : 682
Inscription : 19 Déc 2009 22:55

Re: [DB/MY-AL]PathFinding perso

Message par djulio74 » 16 Oct 2018 14:07

Je vois ce que tu veux dire.
En gros chaque cellule a un script Tile avec des donnée (voisin, visitée notamment).
Dans e cas encore mieux serait un struct avec toute les données nécessaire des cellule. que tu initialise au lancement du niveau. Ce sont toute les donnée non propre au type de cellule ( qui ne sont pas definit avant le lancement)

Code : Tout sélectionner

	
	public struct CELL{ 
		pubic Vector3 postion;
		public bool visite;
		public List<int> voisin;
		public bool carrefour;

        public CELL( Vector3 postion; bool visite, List<int> voisin,  bool carrefour) {
        		this.position = position;
 			this.visite = visite;
			this.voisin = voisin;
			this.carrefour= carrefour;
        }		
    }
    
    private CELL[] Cellules;
    
    void Start(){
    	GameObject[] Cel = GameObject.FindGameObjectsWithTag("cellule");
    	Cellules = new CELL[Cel.Length];
    	// initialise les valeurs par defaut //
  	for (int i = 0; i < Cel.Length ; i++){
 	 	Cellules[i].position = Cel[i].transform.position.
		Cellules[i].visite = false;
		Cellules[i].voisin = new List<int>();
		Cellules[i].carrefour = false.
  	}    
    }
Tu recense dès le départ toute les info dont tu aura besoin, et tu n'utilisera plus jamais les gameObject, getComponent.
ton path ne sera plus un List<GameObject> mais un List<int>. le int renvera a la position dans cellules.
idem pour la list<GameObject> pour les voisins qui sera une List<int> pour les même raisons.
pour le carrefour, après avoir cherché les voisins, tu check s'il en as plus de deux, si oui tu passe a true la valeur pour la cellule correspondante.
Certes ça fait un sacré boulot au départ...

______________________________________________________________
\_______________________ Impossible is nothing _______________________/

EmileF
Messages : 676
Inscription : 18 Mars 2017 19:39

Re: [DB/MY-AL]PathFinding perso

Message par EmileF » 16 Oct 2018 15:23

Après avoir fait ma petite marche de 5 kilomètres, j'ai l'esprit plus clair,

D'abord, je n'ai pas besoin de convertir en gameobjet la liste des tiles du chemin, car après j'utilise les transform et ça marche très bien avec les tiles.

j'ai un peu de mal à comprendre et à voir comment utiliser tes struct. Je ne les ai déjà jamais utilisé, je ne vois pas à quoi il peuvent servir, les class pour moi ça me va très bien.

Mais enfin j'ai l'impression que ça va compliquer tout plutôt que d'utiliser mon type Tile qui possèdent toutes les propriétés dont j'ai besoin

Voici le code que j'ai mis à jour

Code : Tout sélectionner

    [SerializeField]
    Tile start;
    [SerializeField]
    Tile end;
    [SerializeField]
    List<Tile> Cellules;
    [SerializeField]
    List<DataChemin> Paths;
    [SerializeField]
    public List<Tile> Path;
    [SerializeField]
    Tile EnCours;



    /// <summary>
    /// On recherche le chemin le plus court qui pourra être utilise
    /// Path contiendra le chemin à utiliser
    /// </summary>
    void RechercheChemin()
    {

        InitCellules();
        Paths = new List<DataChemin>();

        retour:
        Path = new List<Tile>();
        EnCours = start;
        Path.Add(EnCours);
        EnCours.Visite = true;
        RechercheCheminPossible(); //dans Path, le résultat de la recherche
        //si la longueur du chemin est courte on sort
        if (Path.Count > 0 && Path.Count < 5)
            return;
        //sinon on stoque le chemin
        if (Path.Count > 1)
            Paths.Add(new DataChemin(Path));

        //on repart au départ
        EnCours = start;
        //on va au premier carrefour et on recherche un autre chemin
        if (RechercheCarrefour(EnCours))
            goto retour;

        //s'il n'y a pas de carrefour, ou que tout a été visité
        if (Paths.Count > 0)
            //on recherche le chemin le plus court et on sort 
            RechercheLePlusCourt();

    }

    /// <summary>
    /// Met la valeur Visite de toutes les cellules à false
    /// </summary>
    void InitCellules()
    {
        foreach (Tile item in Cellules)
        {
            item.Visite = false;
        }
    }

    void RechercheCheminPossible()
    {
        retour:
        //s'il y a une cellule voisine à de la cellule en cours
        if (RechercheCarrefour(EnCours))
        {
            //on l'ajoute au chemin
            Path.Add(EnCours);
            EnCours.GetComponent<Tile>().Visite = true;
            //si on est arrivé
            if (EnCours == end)
            {
                //on demarque visité de la dernière cellule en cas de recherche supplémentaire et on sort
                end.GetComponent<Tile>().Visite = false;
                return;
            }
            //sinon on continue
            goto retour;
        }
        //il n'y a pas de cellule accessible autour de la cellule en cours
        for (int i = Path.Count - 1; i > -1; i--)
        {
            //on remonte le chemin à la recherche d'un carrefour
            if (!RechercheCarrefour(Path[i]))
            {
                Path.Remove(Path[i]);
            }
            else
            {
                //on reprend à partir du carrefour
                EnCours = Path[i];
                goto retour;
            }
        }
        return;

    }

    /// <summary>
    /// Recherche une cellule libre autour de la cellule donnée.
    /// </summary>
    /// <param name="cellule"></param>
    /// <returns>retourne true si il y a une cellule libre et EnCours contient la cellule libre trouvée</returns>
    bool RechercheCarrefour(Tile cellule)
    {
        foreach (var item in cellule.GetComponent<Tile>().Voisins)
        {
            if (item != null && item.gameObject.activeSelf)
                if (!item.GetComponent<Tile>().Visite)
                {
                    EnCours = item;
                    return true;
                }
        }
        return false;
    }

    /// <summary>
    /// "Chemin" sera égal au chemin le plus petit entre plusieurs chemins contenus dans "Chemins"
    /// </summary>
    private void RechercheLePlusCourt()
    {

        Path = Paths[0].Chemin;
        foreach (DataChemin item in Paths)
        {
            if (item.Chemin.Count < Path.Count)
                Path = item.Chemin;
        }
    }

La différence entre l'intelligence et la stupidité est que l'intelligence est limitée.

djulio74
Messages : 682
Inscription : 19 Déc 2009 22:55

Re: [DB/MY-AL]PathFinding perso

Message par djulio74 » 16 Oct 2018 16:49

Alors en fait pour les struc : Par exemple Vector3 est un struc, avec x, y et z comme éléments. Un struc c'est comme une variable, qui aurait plusieurs élément. Par exemple :

Code : Tout sélectionner

	public struct CASE { 
		public int x;
		public int y;
		public bool obstacle;
		public GameObject G;


        public CASE(int x, int y,bool obstacle,GameObject G) {
 			this.x = x;
			this.y = y;
			this.obstacle = obstacle;
			this.G = G;
        }	
        
        CASE perso;	// créé une variable CASE
        CASE[] persoMultiple // defini un aarray de la struct CASE
    }
Je peux donc accéder comme pour un vector3 :
perso.x => l'élément int x de la variable perso, étant une struct CASE.. etc
persoMultiple.x => l'élément int x de la CASE persoMultiple a l'index i.. etc
tu auras compris, dans une struct tu peut lui assigner autant d'élément que tu veux, de n'importe quel type (float, int, component,GameObjec... cetc)

En me servant de ce principe, me suis permis d'en modifier ton code pour s'en servir, si tu veux jeter un coup d'oeil :
chaque cellule devra se voir attribuer un collider et ce script :

Code : Tout sélectionner

using UnityEngine;


public class Tile : MonoBehaviour {	
	
	public int Index; // sera assigné au lancement du niveau. il correpondra a son index dans l'array cellule( array de struct)
	
	
	// !! la cellule doit comporter un collider couvrant sa surface!!
	void OnMouseEnter(){
		// si on survole avec la souris la cellule
		// on assign
		chemin.ACTIVE = Index;		
	}
	
	void OnMouseDown(){
		// si on clique sur la cellule, on assignson Index comme start, et lance la recherche de chemin
		// chemin
		chemin.end = Index;
		chemin.RechercheChemin();
		
	}	
}
et le script général :

Code : Tout sélectionner

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class chemin : MonoBehaviour {
	
	// definition d'une struc pour garder les donnée dans le script
	// pour n'acceder qu'une seule fois aux GameObject cellule.
	public struct CEL { 
		public Vector3 position;
		public bool visite;
		public List<int> voisin;
		public bool carrefour;

        	public CEL(Vector3 position , bool visite, List<int> voisin , bool carrefour) {
 				this.position = position;
				this.visite = visite;
				this.voisin = voisin;
				this.carrefour = carrefour;
       	 }		
   	}
	public static int ACTIVE;			// cellule survolée par la souris
	public static GameObject Mimi; 		// ton personnage, devra avoir le tag "mimi"
	public static CEL[] cellules;		// un array de la struct CEL defini plus haut
	public static List<List<int>> Paths; // liste de tout lein possile 
	public static List<int> Path; 		// le path qu'on est en train de calculer puis le path court retenu.
	public static int end; 				// index de cellules ou se trouve l'arrivé.
	public static int start; 			// index de cellules ou se trouve mimi.
	
	public static int EnCours; 			// l'index de la case ou en est la recherche de chemin.

	// Use this for initialization
	void Start () {
		
		// recherche du perso
		Mimi = GameObject.FindGameObjectWithTag("mimi");
		ACTIVE = 0;
		
		// lance l'initialisation de la struct
		Initialisation();
		
		Path = new List<int>();
	}
	
	void Update(){
		
		// juste un debugage que me suis servit pour visualiser les chemin.
		
		for ( int i = 0 ; i < cellules[ACTIVE].voisin.Count; i++){
			Debug.DrawLine( cellules[ACTIVE].position, cellules[cellules[ACTIVE].voisin[i]].position, Color.red);
			
		}
		
		for ( int i = 0 ; i < Path.Count-1 ; i++){
			Debug.DrawLine( cellules[Path[i]].position, cellules[Path[i+1]].position, Color.blue);
			
		}
		
		Debug.DrawRay(cellules[end].position, Vector3.up , Color.red);
		Debug.DrawRay(cellules[start].position, Vector3.up , Color.green);
		Debug.DrawRay(cellules[ACTIVE].position, Vector3.up*2 , Color.blue);
		
		
		
		
	}
	
	
	
	
	void Initialisation(){
		
		GameObject[] CelluleGO = GameObject.FindGameObjectsWithTag("cellule") ;
		cellules = new CEL[CelluleGO.Length];
		
		for ( int i = 0 ; i < cellules.Length ; i ++){
			cellules[i].position = CelluleGO[i].transform.position;
			cellules[i].visite = false;
			cellules[i].voisin = new List<int>();
			cellules[i].carrefour = false;
			CelluleGO[i].GetComponent<Tile>().Index = i; // on assigne au GameObject cellule l'index dans lequel il se trouve
		}	
		RechercheVoisin();		
		
	}
	
	void RechercheVoisin(){
		
		for ( int i = 0 ; i < cellules.Length-1; i++){
			for ( int j = i+1; j < cellules.Length; j++){
				float distance = (cellules[i].position - cellules[j].position).magnitude;
				if( Mathf.Approximately(distance, 1f)){
					if( !cellules[i].voisin.Contains(j)){
						cellules[i].voisin.Add(j);
						cellules[j].voisin.Add(i);
						
						
					}					
				}				
			}			
		}
		// apres avoir trouvé tout les voisins
		// on assigne le bool carrefour en fonction du nombre de voisins.
		for ( int i = 0 ; i < cellules.Length-1; i++){
			if( cellules[i].voisin.Count>2){
				cellules[i].carrefour = true;	
			}			
		}
	}
	
	public static void OuEstMimi(){
		
		// on determine sur quelle case se trouve mimi
		// pour le point de depart des path
		
		for ( int i = 0 ; i < cellules.Length ; i++){
			float distance = (cellules[i].position - Mimi.transform.position).magnitude;
			if( distance< 1.0f){
				start = i;
				return;
			}		
		}		
	}
	
	public static void RechercheChemin(){
		
		
		// on assign false a toute les cellule ( a sa struct qui lui correspond)
		for ( int i = 0 ; i < cellules.Length ; i++){
			
			cellules[i].visite = false;	
		}
		
		// remise a zero des paths possible. On efface tout on recommence
		Paths = new List<List<int>>();
		Path = new List<int>(); 
		
		retour:							
		
		OuEstMimi();	// recherche de la case ou se trouve mimi		
		
		EnCours = start; 
        Path.Add(EnCours);       			// ajout de la case
		cellules[EnCours].visite = true; 	// la cellule ou se trouve mimi est marquée visitée.
		RechercheCheminPossible();
		
		
		
		if (Path.Count > 0 && Path.Count < 5){
         return;
		}
	
		if (Path.Count > 1){
			
            Paths.Add(Path);
			
		}
		
		
		
		EnCours = start;
		
		if (RechercheCarrefour(EnCours)){
            goto retour;
		}
		
		if (Paths.Count > 0){
			RechercheLePlusCourt();
			
		}
		
	}
	
	public static void RechercheCheminPossible(){
        retour:
        //s'il y a une cellule voisine à de la cellule en cours
        if (RechercheCarrefour(EnCours)){
			
            //on l'ajoute au chemin
            Path.Add(EnCours); // ajout de l'index de la cellule au chemin
            cellules[EnCours].visite = true;
            //si on est arrivé
            if (EnCours == end)
            {
                //on demarque visité de la dernière cellule en cas de recherche supplémentaire et on sort
				cellules[EnCours].visite = false;
                return;
            }
            //sinon on continue
            goto retour;
        }
        //il n'y a pas de cellule accessible autour de la cellule en cours
        for (int i = Path.Count - 1; i > -1; i--){
            //on remonte le chemin à la recherche d'un carrefour
            if (!RechercheCarrefour(Path[i])){
                Path.Remove(Path[i]);
            }
            else{
                //on reprend à partir du carrefour
                EnCours = Path[i];
                goto retour;
            }
        }
        return;
    }
	
	static bool RechercheCarrefour(int index) {
		

        foreach (int item in cellules[index].voisin) {
			//if (item != null && item.gameObject.activeSelf)
			if (cellules[item].visite == false){
				EnCours = item;
				return true;				
			}			
        }
        return false;
    }
	
	public static void RechercheLePlusCourt(){

		Path = Paths[0]; // on prend le premier Path de Paths
        foreach (var item in Paths){
            if (item.Count < Path.Count)
                Path = item;
        }
		
    }
}
Pour la partie pathFinding, j'ai rien touché, ça marche plutôt bien! ;) Juste adapter pour le struct.

juste ajouté une fonction OuEstMimi(), pour savoir sur quelle case ( index) elle se trouve quand un cherche un chemin. Je savais pas comment tu gérais ça.
Et un petit debug dans la void Update() pour voir si tout est bien en place

______________________________________________________________
\_______________________ Impossible is nothing _______________________/

EmileF
Messages : 676
Inscription : 18 Mars 2017 19:39

Re: [DB/MY-AL]PathFinding perso

Message par EmileF » 16 Oct 2018 17:10

Hou la la.

Je vais copier tout cela et l'étudier pour voir comment ça marche.
Ca m'a l'air intéressant, mais j'aime bien comprendre, et je vais voir comment l'adapter avec le reste.

Je reviens dès que c'est fait.

En tout cas grand merci, je suis content que tu me dise que la partie pathfinding marche plutôt bien, par rapport à ce matin, j 'avais quelques soucis ::d
La différence entre l'intelligence et la stupidité est que l'intelligence est limitée.

djulio74
Messages : 682
Inscription : 19 Déc 2009 22:55

Re: [DB/MY-AL]PathFinding perso

Message par djulio74 » 16 Oct 2018 17:18

Vois ce que tu peux en tirer oui, ça pourrait t’être intéressant. ;) J'ai découvert les struct il y a pas si longtemps grâce a Alesk. ;)

Pour ce qui est du pathFinding, oui perso je le trouve vraiment pas mal, et bien adapté a ton cas : les "route" font une case de large, un chemin quoi.
Si tu essaye de faire par exemple une place, genre un carré de 9x9 cellules, avec le départ et l'arrivé dans 2 coins opposés, Mimi parcours quasiment toute les case (oui j'ai l'esprit tordu pour avoir essayé ça, lol) pour aller de l'un a l'autre. c'est donc pas forcément le plus rapide du coup. lol. Mais ça reste une situation (le carré de cellules) exceptionnelle on va dire, qui n'arrive normalement pas dans un niveau bien conçu.

______________________________________________________________
\_______________________ Impossible is nothing _______________________/

EmileF
Messages : 676
Inscription : 18 Mars 2017 19:39

Re: [DB/MY-AL]PathFinding perso

Message par EmileF » 16 Oct 2018 18:39

Voilà, j'ai mis en place et adapté mes scripts au tiens, celui de la Mimi surtout, ça marche plutôt pas mal sauf:

Dans certains cas, et pas toujours, la Mimi va une case plus loin que l'arrivée, et retourne ensuite sur l'arrivée.
Et il arrive aussi, mais elle ne l'a fait qu'une fois, qu'elle essaye de couper dans un coin.

Chose qui ne m'arrive pas avec mon script, je pense que ça vient peut-être à cause des index, je ne sais pas trop.

Mais, y-a-t'il un réel avantage d'utiliser ces struct plutôt que mes tile comme je l'ai présenté dernièrement.
Je trouve pas ça très clair et lisible mais c'est la première fois que je les utilise.

Je vais essayer de comprendre pourquoi j'ai ces bugs, surtout que tes drawLine montrent bien le bon chemin. Mystère pour le moment
La différence entre l'intelligence et la stupidité est que l'intelligence est limitée.

djulio74
Messages : 682
Inscription : 19 Déc 2009 22:55

Re: [DB/MY-AL]PathFinding perso

Message par djulio74 » 16 Oct 2018 18:54

en effet je ne vois pas. pour la case trop loin, si tu te sert des cellules[n].position a la place des GameObject.transform.position ce devrait arriver exactement au même.

Je viens de voir que mon appel OuEstMimi() est trop loin dans la fonction RechercheChemin(). remonte le juste de quelques ligne juste au dessus de "retour:" , il n'y a pas lieu de calculer la position de mimi pour chaque test de chemin, surtout si elle est en train de bouger.
Ça pourrait résoudre ton problème.

L'avantage des struct je trouve c'est que tu peux avoirs plein de paramètre (de nature différente) dans une seul variable. d'autant plus comme ici ou tu as des list dans la struct. Sinon il faut creer plein de variable, ça peut vite devenir genant pour trouver les noms et s'y retrouver quand elles ont toutes un rapport entre elles.

Mais évidement ce pas "LA" solution, mais perso j'aime assez m'en servir. ;)

______________________________________________________________
\_______________________ Impossible is nothing _______________________/

EmileF
Messages : 676
Inscription : 18 Mars 2017 19:39

Re: [DB/MY-AL]PathFinding perso

Message par EmileF » 16 Oct 2018 19:05

Alors, j'ai fait le test comme tu me le proposais avec un carré de 5 cellules de cotés (oui, 5 au lieu de 9, je ne voulais pas fatiguer ma Mimi)

Hé bien ça ne plait pas à ma Mimi avec ta méthode, arrivée au bout elle avait tendance à vouloir revenir au début en diagonale.

Avec mon script, pas de soucis de ce coté Youpi, je suis le meilleur :mdr3: :amen:

Je vais donc garder mon script pour le moment, mais je ne jette pas le tiens, je pense que c'est intéressant de l'étudier et d'essayer de l'adapter, ainsi que de me pencher sur ces struct, car j'ai déjà utilisé des classes sans rien d'autre dedans que des propriétés ou des champs. en fait c'est la même chose je pense.

Tu m'a envoyé ton message pendant que j'écris le mien.
Je ne me sert pas de OuEstMimi, je passe sa position en paramètre ça m'était plus facile
La différence entre l'intelligence et la stupidité est que l'intelligence est limitée.

Répondre

Revenir vers « (C#) CSharp »