[DB/MY-AL]Parcours de Circuit

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
EmileF
Messages : 673
Inscription : 18 Mars 2017 19:39

[DB/MY-AL]Parcours de Circuit

Message par EmileF » 17 Oct 2018 10:54

Après avoir présenté "PathFinding perso" (http://www.unity3d-france.com/unity/php ... 55#p113655) sur lequel j'ai eu des retours passionnants, je vous présente un autre script que j'espère tout aussi intéressant.

Toujours dans le cadre de mon jeu (http://www.unity3d-france.com/unity/php ... 12&t=16068)
quand Mimi, le perso du jeu atteint une case jaune, que j'appelle Commande, le circuit qui est raccordé à cette case s'allume et s'il atteint un portail, celui-ci s'ouvre.

La grille du jeu est constituée de cases ou cellules contenant diverses choses, comme des contacts, par exemple, ce sont eux qui nous intéressent.

Chaque case, ayant un contact, possède un gameobject, positionné pile au milieu de la bordure de la case, et il est équipé d'un script que j'ai appelé "Ctt".
Left est le contact Ctt situé sur le bord gauche de la case, Right sur le bord Droit, Up sur le bord Haut et Down sur le bord Bas.(que d'originalités :hehe: ).
Ainsi s'il y a 2 cases voisines horizontalement avec des contacts opposés, exemple Left pour la case de droite et Right pour celle de gauche, ces 2 contacts sont à la même position. (Attention les cases peuvent pivoter, c'est ce qui me permet de suivre le circuit d'une case à l'autre.
Dans le Ctt de chaque contact, il y a un array Others qui contient les autres contacts avec lesquels il est en relation.

Donc pour établir le circuit il faut rechercher alternativement un contact qui est à la même position et une fois trouvé continuer la recherche avec les contacts qui sont en relation avec lui (Others).

Dans Ctt il y a aussi la possibilité de référencer un Portail ce qui permet de commander le portail s'il existe.

Chaque portail est aussi équipé de 2 contacts de ce fait, la connexion peut continuer le long des portails (Je n'utilise pas cette possibilité pour le moment, je trouve que ce n'est pas évident pour le joueur)

Voici donc le script:

Code : Tout sélectionner


    //la liste des contacts du jeu
    static GameObject[] ContactsDuJeu;
    //la case du départ, celle appelée Commande
    static Tile depart;
    //Le circuit final
    static List<GameObject> circuit = new List<GameObject>();
    //Eventuellement la liste des portails à ouvrir
    internal static List<GameObject> portails;

    /// <summary>
    /// A n'appeler qu'une fois à la fin de création du niveau
    /// </summary>
    internal static void Initialisation()
    {
        ContactsDuJeu = GameObject.FindGameObjectsWithTag("Contact");
    }

    /// <summary>
    /// Ceci est appellé directement par Mimi quand elle est arrivée à destination
    /// sur une cellule Jaune (Commande)
    /// </summary>
    /// <param name="gameObject"></param>
    internal static void ChercheCircuit(Tile tile)
    {
        depart = tile;
        //Quels que soient les contacts attribués à la cellule Commande dans l'éditeur
        //c'est le ContactX qui est pris comme référence pour la position des Contacts
        //au départ.

        //Les contacts du départ
        List<GameObject> ContactDeDepart = new List<GameObject>()
        {
            depart.transform.Find("ContactX").Find("Left").gameObject,
            depart.transform.Find("ContactX").Find("Up").gameObject,
            depart.transform.Find("ContactX").Find("Right").gameObject,
            depart.transform.Find("ContactX").Find("Down").gameObject,
        };

        //décolore les anciens circuits
        ColoreCircuit(Color.gray);

        //Ce sera le circuit final;
        circuit.Clear();
        //La liste des portails à ouvrir
        portails = new List<GameObject>();

        //parcours les contacts du départ
        foreach (var dep in ContactDeDepart)
        {
            //et compose le circuit à partir de ce contact
            ComposeCircuit(dep);
        }

        //colore les circuits trouvés
        ColoreCircuit(Color.yellow);
    }

    /// <summary>
    /// Est appelée quand un bouton pour pivoter les contacts a été pressé
    /// </summary>
    internal static void ChercheCircuit()
    {
        //si Mimi est encore dans la cellule Commande
        if (depart != null)
            ChercheCircuit(depart);
    }

    /// <summary>
    /// Appelée quand Mimi sort de la cellule Commande
    /// </summary>
    internal static void VideDepart()
    {
        ColoreCircuit(Color.gray);
        depart = null;
    }

    static void ComposeCircuit(GameObject contact)
    {
        //cherche un contact du jeu dans la même position
        foreach (var item in ContactsDuJeu)
        {
            //si un contact du jeu 
            if (item != contact)//si l'item est différent de contact
            {
                //on récupère le composant Ctt
                Ctt Ctt = item.GetComponent<Ctt>();
                if (item.transform.position == contact.transform.position)  // et s'il est à la même position
                {
                    AjouteContact(Ctt);
                }
                if (Ctt.Val > 0 && Ctt.Val == contact.GetComponent<Ctt>().Val)
                    AjouteContact(Ctt);
            }
        }
    }

    static void AjouteContact(Ctt Ctt)
    {
        //S'il n'est pas déjà dans le circuit
        if (!circuit.Contains(Ctt.gameObject))
            //on l'ajoute au circuit
            circuit.Add(Ctt.gameObject);


        //s'il y a un portail on l'ajoute au portails à ouvrir si possible
        if (Ctt.Portail != null)
            portails.Add(Ctt.Portail.gameObject);

        //on recherche une sortie
        foreach (Ctt sortie in Ctt.Others)
        {
            //s'il est activé on continue la recherche
            if (sortie.transform.parent.gameObject.activeSelf &&
                !circuit.Contains(sortie.gameObject))
            {
                //on l'ajoute au circuit
                circuit.Add(sortie.gameObject);
                //et on continue la recherche
                ComposeCircuit(sortie.gameObject);
            }
        }

    }


    public static void ColoreCircuit(Color color)
    {
        //DéColore les circuits existant
        foreach (var item in circuit)
        {
            if (item.GetComponent<Ctt>().Mesh != null)
                Utiles.MaterialColor(item.GetComponent<Ctt>().Mesh, color);
        }
    }

N'hésitez pas à me faire tous les commentaires que vous voulez, et surtout me signaler les erreurs que j'aurais pu faire, il est là pour ça.

S'il peut aider ou rendre service à quelqu'un j'en serais très heureux.

a bientôt j'espère
La différence entre l'intelligence et la stupidité est que l'intelligence est limitée.

Avatar de l’utilisateur
Liven
Messages : 268
Inscription : 30 Nov 2017 01:48

Re: [DB/MY-AL]Parcours de Circuit

Message par Liven » 17 Oct 2018 14:04

J'ai juste parcouru vite fait sans vraiment essayer de comprendre (désolé, je n'ai pas le temps de me pencher sur du code juste pour commenter).

Je dirais juste un trucs rapport aux "bonnes pratiques" en programmation point de vue opti :
Évite les foreach à chaque fois que tu peux. Tu peux facilement les remplacer par des "for" comme ceci :

Code : Tout sélectionner

	//Ton exemple
   	foreach (var dep in ContactDeDepart)
        {
            //et compose le circuit à partir de ce contact
            ComposeCircuit(dep);
        }

        // La même fonction mais en for
         for (int i = 0; i < ContactDeDepart.Length; i++)
        {
            ComposeCircuit(ContactDeDepart.dep[i]);
        }
C'est un poil plus long (au passage tape "for" puis deux fois la touche tab, l'autocomplétion de visual studio te fera un bisou), mais l'avantage de cette méthode est qu'elle ne génère pas de Garbage Collection (GC)

Un petit mot sur le GC, c'est une façon qu'utilise C# pour allouer temporairement de la mémoire. Bon c'est un peu merdeux comme explication, mais l'important de comprendre c'est que cet allocation est temporaire et que le c# "surveille" en permanence la quatité totale allouée en GC et quand il y en a trop il la vide.
Et Cette opération de vidage est très gourmande et peut occasionner carément des freezes dans le jeu. Lorsque dans un jeu on a des freezes à interval régulier, il y a des forte chance que ce soit la GC qui chie dans les bottes.
Il me semble que l'on peut choisir les moments ou la GC se vide, mais que c'est un truc pas évident à faire et que sans ça, le C# fait bien comme il veut, quand il veut.

Pour cette raison, perso je n'utilise pour ainsi dire jamais les "foreach".

Si ton code ne génère pas de GC gènant, libre à toi de continuer, mais le jour la GC se rappèlera à ton bon souvenir tu risque d'avoir du bouleau à retransformer ton code. Autant prendre les bonnes habitudes après tu seras peinard.

PS : tu peux voir on en sont les allocation en GC, en faisant tourner ton jeu avec la fenêtre du profiler visible (tu la trouve dans window / Analyse / profiler). Dans la partie du dessous, tu sélectionnes hiérarchie à la place de timeline et tu auras la CG allouée dans la colonne "GC Alloc" pour chacun de tes scripts (en déroulant la ligne "update.scriptrunbehaviour...).
Et en cochant "deep profile" en haut de la fenêtre, tu peux même avoir le détails par fonction dans chaque script.

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

Re: [DB/MY-AL]Parcours de Circuit

Message par EmileF » 17 Oct 2018 15:05

He bien mince alors.

Je préfère foreach parce que c'est plus facile pour ajouter ou enlever des éléments et je le trouve plus convivial et souple que for.

Pour ce qui est de la complétion automatique je connaissais et ce n'est pas ce qui me gênais. Je l'utilise aussi pour foreach

Pour le reste de tes explications, je ne savais pas et j'avoue que je n'ai pas tout compris, en fait, je n'ai rien compris. Je crois avoir simplement compris que ça pouvait créer des problèmes de gestion de mémoire. je vais donc te croire sur parole et à l'avenir j'éviterai les foreach à mon grand regret.

est-ce que on peut pour faciliter certaines opérations utiliser le foreach et convertir ensuite en array, pour éviter le Garbage Collection. Est-ce une pratique acceptable?
Dernière édition par EmileF le 17 Oct 2018 15:19, édité 1 fois.
La différence entre l'intelligence et la stupidité est que l'intelligence est limitée.

Avatar de l’utilisateur
Liven
Messages : 268
Inscription : 30 Nov 2017 01:48

Re: [DB/MY-AL]Parcours de Circuit

Message par Liven » 17 Oct 2018 15:18

emile121153 a écrit :
17 Oct 2018 15:05
est-ce que on peut pour faciliter certaines opérations utiliser le foreach et convertir ensuite en array, pour éviter le Garbage Collection. Est-ce une pratique acceptable?
C'est clair que moi aussi j'ai fait la grimace lorsque j'ai compris que foreach était un faux ami, mais le profiler ne ment pas.

Pour ta question : malheureusement la réponse est non.

Dans l'expression

Code : Tout sélectionner

   	foreach (var dep in ContactDeDepart)
   	{...}
c'est le "var dep" qui pose problème car C# va stocker dans cette mémoire temporaire (GC) tout les "dep" de ton "ContactDeDepart".
Alors qu'avec un "for" le "i" est la seule variable temporaire (même pas stockée en GC il me semble) qui est utilisé le reste c'est de l'acces direct à des tableaux déjà existants.

NB : Stocker des variables dans des tableaux pour certains besoins est une méthode utile, mais autant utiliser un "for" pour remplir ces tableaux

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

Re: [DB/MY-AL]Parcours de Circuit

Message par EmileF » 17 Oct 2018 15:36

Liven a écrit : c'est le "var dep" qui pose problème car C# va stocker dans cette mémoire temporaire (GC) tout les "dep" de ton "ContactDeDepart".
Ha je crois que tu fais une petite erreur là, dep remplace tout simplement l'item de foreach

Code : Tout sélectionner

   	foreach (var dep in ContactDeDepart)
   	{...}  	   	
   	foreach (var item in ContactDeDepart)
   	{...}
mais ça ne change rien au reste, j'ai donc corrigé mon code:

Code : Tout sélectionner

    //la liste des contacts du jeu
    static GameObject[] ContactsDuJeu;
    //la case du départ, celle appelée Commande
    static Tile depart;
    //Le circuit final
    static List<GameObject> circuit = new List<GameObject>();
    //Eventuellement la liste des portails à ouvrir
    internal static List<GameObject> portails;

    /// <summary>
    /// A n'appeler qu'une fois à la fin de création du niveau
    /// </summary>
    internal static void Initialisation()
    {
        ContactsDuJeu = GameObject.FindGameObjectsWithTag("Contact");
    }

    /// <summary>
    /// Ceci est appellé directement par Mimi quand elle est arrivée à destination
    /// sur une cellule Jaune (Commande)
    /// </summary>
    /// <param name="gameObject"></param>
    internal static void ChercheCircuit(Tile tile)
    {
        depart = tile;
        //Quels que soient les contacts attribués à la cellule Commande dans l'éditeur
        //c'est le ContactX qui est pris comme référence pour la position des Contacts
        //au départ.

        //Les contacts du départ
        GameObject[] ContactDeDepart = new GameObject[]
        {
            depart.transform.Find("ContactX").Find("Left").gameObject,
            depart.transform.Find("ContactX").Find("Up").gameObject,
            depart.transform.Find("ContactX").Find("Right").gameObject,
            depart.transform.Find("ContactX").Find("Down").gameObject,
        };

        //décolore les anciens circuits
        ColoreCircuit(Color.gray);

        //Ce sera le circuit final;
        circuit.Clear();
        //La liste des portails à ouvrir
        portails = new List<GameObject>();

        //parcours les contacts du départ
        for (int i = 0; i < ContactDeDepart.Length; i++)
        {
            //et compose le circuit à partir de ce contact
            ComposeCircuit(ContactDeDepart[i]);
        }

        //colore les circuits trouvés
        ColoreCircuit(Color.yellow);
    }

    /// <summary>
    /// Est appelée quand un bouton pour pivoter les contacts a été pressé
    /// </summary>
    internal static void ChercheCircuit()
    {
        //si Mimi est encore dans la cellule Commande
        if (depart != null)
            ChercheCircuit(depart);
    }

    /// <summary>
    /// Appelée quand Mimi sort de la cellule Commande
    /// </summary>
    internal static void VideDepart()
    {
        ColoreCircuit(Color.gray);
        depart = null;
    }

    static void ComposeCircuit(GameObject contact)
    {
        //cherche un contact du jeu dans la même position
        for (int i = 0; i < ContactsDuJeu.Length; i++)
        {
            GameObject item = ContactsDuJeu[i];
            //si un contact du jeu 
            if (item != contact)//si l'item est différent de contact
            {
                //on récupère le composant Ctt
                Ctt Ctt = item.GetComponent<Ctt>();
                if (item.transform.position == contact.transform.position)  // et s'il est à la même position
                {
                    AjouteContact(Ctt);
                }
                if (Ctt.Val > 0 && Ctt.Val == contact.GetComponent<Ctt>().Val)
                    AjouteContact(Ctt);
            }
        }
    }

    static void AjouteContact(Ctt Ctt)
    {
        //S'il n'est pas déjà dans le circuit
        if (!circuit.Contains(Ctt.gameObject))
            //on l'ajoute au circuit
            circuit.Add(Ctt.gameObject);


        //s'il y a un portail on l'ajoute au portails à ouvrir si possible
        if (Ctt.Portail != null)
            portails.Add(Ctt.Portail.gameObject);

        //on recherche une sortie
        for (int i = 0; i < Ctt.Others.Length; i++)
        {
            Ctt sortie = Ctt.Others[i];
            //s'il est activé on continue la recherche
            if (sortie.transform.parent.gameObject.activeSelf &&
                !circuit.Contains(sortie.gameObject))
            {
                //on l'ajoute au circuit
                circuit.Add(sortie.gameObject);
                //et on continue la recherche
                ComposeCircuit(sortie.gameObject);
            }
        }

    }


    public static void ColoreCircuit(Color color)
    {
        //DéColore les circuits existant
        for (int i = 0; i < circuit.Count; i++)
        {
            if (circuit[i].GetComponent<Ctt>().Mesh != null)
                Utiles.MaterialColor(circuit[i].GetComponent<Ctt>().Mesh, color);
        }
    }

mais pour ce code qui fait partie du script

Code : Tout sélectionner

        //on recherche une sortie
        for (int i = 0; i < Ctt.Others.Length; i++)
        {
            Ctt sortie = Ctt.Others[i];
            //s'il est activé on continue la recherche
            if (sortie.transform.parent.gameObject.activeSelf &&
                !circuit.Contains(sortie.gameObject))
            {
                //on l'ajoute au circuit
                circuit.Add(sortie.gameObject);
                //et on continue la recherche
                ComposeCircuit(sortie.gameObject);
            }
        }

il est difficile d'utiliser Array, ne connaissant pas au départ le nombre d'éléments
Pour ce genre de code est-ce que c'est déconseillé d'utiliser un List provisoire et le transformer en Array une fois le List complet ou c'est mauvais?
La différence entre l'intelligence et la stupidité est que l'intelligence est limitée.

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

Re: [DB/MY-AL]Parcours de Circuit

Message par EmileF » 17 Oct 2018 15:48

Il y a aussi cette situation

Code : Tout sélectionner


	foreach (var item in new List<GameObject>(Liste_Objets))
	{
		If (item != null) //par exemple
			Liste_Objets.Remove(item);
	}

Comment supprimer dans un Array un élément sans qu'il y ait de trou dans le tableau?

Ce sont des opérations qui sont bien pratique dans List et je ne connais pas l'équivalent dans Array.
Il faudrait peut-être que je me penche d'avantage sur l'utilisation des Array, j'ai fait au plus facile.
La différence entre l'intelligence et la stupidité est que l'intelligence est limitée.

Avatar de l’utilisateur
Liven
Messages : 268
Inscription : 30 Nov 2017 01:48

Re: [DB/MY-AL]Parcours de Circuit

Message par Liven » 17 Oct 2018 16:38

Les array ont toujours une taille définie qui ne bouge pas une fois créé (pour changer sa taille il faut le supprimer et en créer un autre).
Les listes peuvent changer de taille, il suffit d'ajouter ou de retirer des éléments.
Ce sont deux utilisations différentes, si tu as besoin de quelque chose qui s'adapte en taille, il faut que tu utilise une liste. si ton contenu a toujours la même taille, que n'a pas besoin d'ajouter ou retirer des trucs en court de route, tu utilise les array.

Le seul truc à éviter c'est de ce dire "j’utilise toujours les listes car c'est plus souple" car les listes sont aussi plus gourmandes en ressource donc à chaque fois que possible il faut privilégier les array.


Sinon pour parcourir une liste avec un "for" au lieu d'un "foreach" ça donne ça :

Code : Tout sélectionner

        for (int i = 0; i < ListaParcourrir.Count; i++)
	{
		ListaParcourrir[i] = blablabla... // bon tu fait ta popote quoi
	}
En gros remplacer "Length" par "Count" pour avoir la taille de la liste

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

Re: [DB/MY-AL]Parcours de Circuit

Message par EmileF » 17 Oct 2018 16:56

Bon ok, c'est clair maintenant, d'ailleurs, c'est vrai, c'est pas tout l'un ou tout l'autre. donc, préférer les array et s'il y a de modifications de tableau, utiliser list.

Je connaissait le for pour les list au lieu de foreach, mais ça ne change rien au reste.

Merci.
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]Parcours de Circuit

Message par djulio74 » 17 Oct 2018 17:22

la diffférence aussi entre foreach et for, c'est qu'avec foreach, ça crée une nouvelle variable, qui prend la valeur de l'élément de la liste/array. Donc oui tu peux avoir accès au donnée de cette variable, en lecture. par contre je suis pas sur que tui puisse modifier la valeur dans la source.

Code : Tout sélectionner


int[] variable;

foreach ( int item in variable){
	print(item); // ok, ça correspond a la valeur dans variable. Item => nouvelle variable int
	item = 5; // ça change la valeur de item, pas sur ça change celle de la valeur DANS variable.
}

for ( int i = 0 ; i < variable.Length ; i++){
	variable[i] = 5; // modifie la valeur dans l'array
}

du moins je vois ça comme ça, même pas testé ^^

______________________________________________________________
\_______________________ Impossible is nothing _______________________/

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

Re: [DB/MY-AL]Parcours de Circuit

Message par EmileF » 18 Oct 2018 12:58

Attendez, est-ce que j'ai bien compris ?

C'est Foreach qu'il faut éviter ou List ?

Car on peut utiliser foreach avec les array aussi !!!
La différence entre l'intelligence et la stupidité est que l'intelligence est limitée.

Répondre

Revenir vers « (C#) CSharp »