[RESOLU] Changement de valeur héritée avant instanciation

Questions à propos du scripting. Hors Shader, GUI, Audio et Mobile.
Kazuma17
Messages : 33
Inscription : 29 Août 2017 15:56

[RESOLU] Changement de valeur héritée avant instanciation

Message par Kazuma17 » 13 Avr 2020 11:41

Bonjour ! :-D

Pour la version courte : j'ai des scripts enfants (selon le type d'ennemi) qui héritent tous du même script parent (Enemy.cs). Ce script parent établit les variables communes à tous ses enfants (en accès protected) et ces derniers vont chacun réattribuer les valeurs de ces variables, donc lors de leur instanciation, avec ce modèle là :

Code : Tout sélectionner

public class Enemy_Simple : Enemy
{
    protected override void Start()
    {
        base.Start();
        InitializeDefaultValue();
        
        void InitializeDefaultValue()
    {
        _speed = 1.0f;
        _fireRate = 10f;
        _health = 1;
        _damage = 20;
        _score = 10;   
    }
    }
Le problème, c'est que je suis en train de concevoir un editeur de spawn des ennemis, et j'aurais besoin de connaitre la nouvelle valeur que l'enfant donne avant même de l'instancier. Sauf que du coup, je me retrouve toujours avec la valeur du parent puisqu'elle n'est pas overridée sans instanciation.

Je posterai le code si besoin mais je voulais d'abord poser la question de manière concise, ne sachant pas s'il s'agit d'un problème de conception ou si j'ai raté quelque chose.

Merci ! :super:

Avatar de l’utilisateur
Max
Messages : 8771
Inscription : 30 Juil 2011 13:57
Contact :

Re: Changement de valeur héritée avant instanciation

Message par Max » 13 Avr 2020 16:03

Hello,
Kazuma17 a écrit :
13 Avr 2020 11:41
j'aurais besoin de connaitre la nouvelle valeur que l'enfant donne avant même de l'instancier.
Tu peux développer un peu plus, car j'avoue que je ne comprend pas trop :-/
Image
Pas d'aide par MP, le forum est là pour ça.
En cas de doute sur les bonnes pratiques à adopter sur le forum, consulter la Charte et sa FAQ

Kazuma17
Messages : 33
Inscription : 29 Août 2017 15:56

Re: Changement de valeur héritée avant instanciation

Message par Kazuma17 » 14 Avr 2020 12:22

Ok désolé c'est pas évident à expliquer simplement, je vais poster le code et rentrer un peu plus dans le détail.

En fait j'ai un script Enemy.cs qui donne les mécaniques de base des ennemis. Chaque type d'ennemi possède un script qui hérite d'Enemy.cs et qui override les fonctions de tirs, de déplacements, etc. ainsi que les valeurs de l'ennemi type speed, damage, health, etc. ça donne ça :

Code : Tout sélectionner

 public class Enemy : MonoBehaviour
     {
         protected float _speed = 4.0f;
         protected float _fireRate = 2.0f;
         protected int _health = 1;
         protected int _damage = 5;
         protected int _score = 10;
         protected bool _isBig;
     
         protected Player _player;
     
         protected virtual void Start()
         {
             InitializeEnemy();
         }
     
         void InitializeEnemy()
         {
             _player = GameObject.Find("Player").GetComponent<Player>();
     
             if (_player == null)
             {
                 Debug.LogError("Player is NULL");
             }
         }
     
     public bool isBig()
         {
             return _isBig;
         }
Et pour chaque type d'ennemi on a donc un script de ce genre là :

Code : Tout sélectionner

public class Big_Enemy : Enemy
 {
     protected override void Start()
     {
         base.Start();
         InitializeDefaultValue();
     }
 
     void InitializeDefaultValue()
     {
         _speed = 1.0f;
         _fireRate = 10f;
         _health = 1;
         _damage = 20;
         _score = 10;   
         _isBig = true;
     }
 }
Parmi tous mes types d'ennemis, il y a un booléen _isBig qui détermine la place que va prendre l'ennemi à son spawn. C'est important pour mon SpawnManager, car je suis en train de créer un éditeur de spawn qui permettra avant le lancement du jeu de choisir les types d'ennemis qui vont spawner, la quantité etc. dans une limite de 30 à la fois. Donc un ennemi dont _isBig est false va prendre 1 place, un ennemi dont _isBig est true va prendre 2 places. Ainsi on ne pourra spawner que 15 gros ennemis au lieu de 30 ennemi simples. Mais on peut aussi spawner des gros ennemis en même temps que des petits, donc j'ai une fonction qui calcule la taille prise par les ennemis lorsqu'on les choisit en fonction de cette variable _isBig. Tout le reste est expliqué dans les commentaires du code ci-dessous.

Code : Tout sélectionner

public class SpawnManager : MonoBehaviour
 {
     [SerializeField]
         private Enemy[] _enemiesPrefab;
         [SerializeField]
         private Transform[] _spawnPositions;
     
      void Start()
         {
         //Exemple ci-dessous qui retourne false au lieu de true, car ce big ennemy 
         //n'étant pas instancié au moment de l'appel de la fonction (puisque le 
         //choix des ennemis se fait avant le niveau), sa variable _isBig conserve la
         // valeur du parent ! Puisque _isBig est overridé dans le script de cet ennemi
         // qui hérite de Enemy.cs. Bien sûr je ne saurais pas à l'avance le type de
         // script de mon ennemi mais c'est simplement pour démontrer que même
         // en le sachant je n'arrive pas à avoir la bonne valeur de _isBig.
     
             Big_Enemy bigEnemyScript = _enemiesPrefab[1].GetComponent<Big_Enemy>();
             Debug.Log(bigEnemyScript.isBig());
     
     
     //Ici c'est l'application réel. J'ai un ennemi à l'index[0] qui est un ennemi simple,
     // soit _isBig = false, il prend 1 place. Contrairement à l'ennemi à l'index[1], 
     // dont _isBig = true, il prend deux places. Je passe en paramètre de ma fonction
     // de spawn que je veux 25x le [0], et 3x le [1]. Le calcul de la taille qu'ils vont
     // prendre est donc 25 x 1 + 3 x 2 = 31. Le soucis c'est que comme _isBig
     // sera toujours false, j'obtient 25 x 1 + 3 x 1 = 28. Comme ma limite d'ennemis 
     //est de 30, ma fonction me retourne que tout est OK, alors qu'en vérité avec 31 ça 
     //ne passerait pas. 
             
      Debug.Log(CheckTotalEnemies(0, 25, 1, 3));
         }
     
     private bool CheckTotalEnemies(int firstEnemyType, int firstEnemyNumber, int secondEnemyType = -1, int secondEnemyNumber = 0, int thirdEnemyType = -1, int thirdEnemyNumber = 0)
         {
             int total = 0;
 
 //en fonction de _isBig, je multiplie par 2 ou non pour déterminer la taille totale du groupe.
             total = _enemiesPrefab[firstEnemyType].isBig() ? total += firstEnemyNumber * 2 : total += firstEnemyNumber;
     
          //Ajouter un deuxième ou un troisième type d'ennemi est optionnel
             if (secondEnemyType != -1)
             {
                 total = _enemiesPrefab[secondEnemyType].isBig() ? total += secondEnemyNumber * 2 : total += secondEnemyNumber;
     
                 if (thirdEnemyType != -1)
                     total = _enemiesPrefab[thirdEnemyType].isBig() ? total += thirdEnemyNumber * 2 : total += thirdEnemyNumber;
             }
            
            //Je vérifie enfin si le total n'excède pas la taille totale qui est de 30
             return total <= 30 ? true : false;
         }
 }
Voilà, donc soit j'ai un problème de conception concernant l'héritage, soit je rate un truc qui me permet de savoir à l'avance la valeur de cette variable overridée par le script enfant...

Merci en tout cas de ton intérêt à la question je sais que ça fait beaucoup d'infos... :roll:

Avatar de l’utilisateur
Max
Messages : 8771
Inscription : 30 Juil 2011 13:57
Contact :

Re: Changement de valeur héritée avant instanciation

Message par Max » 14 Avr 2020 17:59

Bonsoir,

Oui, je comprends mieux. Je dirais qu'en fait ton soucis vient du fait que ton script SpawnManager est exécuté avant les scripts de tes ennemis (un classique).
Pour remédier à cela, tu as plusieurs solutions. La première est de passer tes init dans Awake au niveau de tes scripts d'ennemis (et non dans Start). La seconde possibilité est de gérer l'ordre d’exécution de tes scripts (voir la partie Project Setting).
.
Image
Pas d'aide par MP, le forum est là pour ça.
En cas de doute sur les bonnes pratiques à adopter sur le forum, consulter la Charte et sa FAQ

Kazuma17
Messages : 33
Inscription : 29 Août 2017 15:56

Re: Changement de valeur héritée avant instanciation

Message par Kazuma17 » 15 Avr 2020 14:54

Hello, :-D

Merci pour les pistes, cependant comme tu le relèves mon script SpawnManager est exécuté avant celui des mes ennemis mais c'est volontaire : en fait on aura une scène "Editeur de niveau" où on pourra choisir les ennemis qui apparaîtront dans la scène suivante, et selon ce qu'on a choisit le SpawnManager va les instancier. De ce fait l'instanciation des ennemis se fera toujours APRES la vérification de la taille, car je n'ai pas le choix de faire cette vérification avant le lancement du niveau pour dire "Attention, là ça va pas il faut réduire le nombre d'ennemi", sans que le niveau soit encore lancé.

Je me dis que je devrais déjà laisser mon script SpawnManager uniquement pour l'instanciation au début du niveau de jeu, et décaler ma fonction de vérification et de choix dans un script EditorManager qui gère aussi l'UI de l'éditeur, puis ensuite envoyer toutes les infos au SpawnManager qui va faire les spawns.

Mais le problème reste le même, je vois pas comment avoir une variable commune à tous mes types d'ennemis que je peux récupérer avant de les instancier dans la mesure où c'est une variable héritée...
Sinon je pense contourner le problème en me créant un array avec le même nombre d'index que j'ai de type d'ennemis, puis le remplir à la main pour que ça corresponde genre _ennemiesPrefab[0] possède _isBig = false, je crée un array où isBig[0] = false... C'est pas hyper modulable mais ça fera le taf pour un coût en performance intéressant.

Je vais tenter un truc dans ce sens-là :super:

Avatar de l’utilisateur
jmhoubre
Messages : 856
Inscription : 05 Oct 2019 22:05

Re: Changement de valeur héritée avant instanciation

Message par jmhoubre » 16 Avr 2020 13:41

Bonjour,
j'avoue ne pas comprendre ton problème. Voilà ce que j'ai cru comprendre :
1 - le joueur, dans la scène "Choix" choisit les ennemis à affronter (type et nombre par type)
2 - le joueur lance le combat
3 - le jeu instancie les ennemis choisis dans la scène "Combat"
Et tu as une place limitée de 30 "unités" d'ennemis, sachant que certains ennemis prennent 1 ou 2 places.

Question : pourquoi ne pas compter dans la scène "Choix" la place prise et n'autoriser à passer en "Combat" que lorsque la place prise est valide ? Vu qu'on choisit les ennemis, on doit bien afficher quelque chose de l'ennemi au joueur (une image, un texte, ...), donc le jeu doit pouvoir compter les places prises ?

En fait, la réponse à cette question est surement dans l'éditeur de niveau.

Avatar de l’utilisateur
DevAmat
Messages : 435
Inscription : 23 Nov 2016 11:50

Re: Changement de valeur héritée avant instanciation

Message par DevAmat » 18 Avr 2020 16:21

Si je peux me permettre, pour moi c'est une erreur de conception. Je m'explique, l'héritage permet de donner une base "générique", mais cela permet aussi de donner de la modularité.
Pourquoi ne pas mettre ta variable qui décide de la taille en "Public", et la modifier directement via l'inspecteur? Tu auras dans tous les cas un Prefab pour chaque type d'ennemie facile à modifier, et pas de problème d'initialisation de variable.
Car là, si tu décides de faire une modification tu es obligé de modifier le script et ça ce n'est pas pratique.

ps: c'est valable pour toutes tes variables de ta classe "Enemy"

Kazuma17
Messages : 33
Inscription : 29 Août 2017 15:56

Re: Changement de valeur héritée avant instanciation

Message par Kazuma17 » 10 Mai 2020 12:24

Effectivement c'était déjà un problème de conception dans la façon de faire mais aussi dans le code. Premièrement j'ai crée un level Editeur qui m'a permis plus simplement de créer toutes mes variables AVANT de lancer le niveau. Deuxièmement j'ai passé toutes les variables de Enemy.cs, donc la classe parent, en public plutôt qu'en protected comme le souligne DevAmat :super: . De là j'ai pu donné indépendamment à chaque classe enfant ses propres valeurs depuis l'inspector d'Unity, et j'ai donc supprimé la fonction InitalizeValue() de chaque classe enfant qui n'était plus utile. Maintenant dans mon editeur si je fais un GetComponent<Enemy>() sur un enemy qui a un script Enemy_Simple.cs, donc enfant de Enemy.cs, il me donne directement les bonnes valeurs du script enfant puisqu'il n'y a plus d'override au Start() mais les valeurs sont directement données dans l'inspector.

Tout fonctionne impec, merci ! ;-)

Répondre

Revenir vers « Scripting »