[RESOLU] Spawn coin

Questions à propos du scripting. Hors Shader, GUI, Audio et Mobile.
Jonathanbtq
Messages : 6
Inscription : 05 Oct 2020 15:47

[RESOLU] Spawn coin

Message par Jonathanbtq » 28 Oct 2021 19:24

Bonjour, je m'appelle Jonathan, je suis actuellement en train de dev un petit jeu mobile et je suis bloqué sur un script très simple a première vue mais je suis perdu, je m'explique.

Je veux faire spawn des pieces(classique), je recupere cette piece, une autre spawn après 12secondes(ex)
Le problème c'est que lorsque je récup cette pièce pas de problèmes une pièces s'ajoute a mon compteur mais la prochaine pièces qui apparait, me donnre 3 pièces voir plus.

Je ne trouve rien, j'ai essayé un tas de scripts et c'est un des derniers trucs ou je bloque pour enfin finir ce jeu :)

Les Scripts:

pièce:

Code : Tout sélectionner

using UnityEngine;
using UnityEngine.UI;
using System.Collections;

public class Coin : MonoBehaviour
{
    private void OnTriggerEnter2D(Collider2D collision)
    {
        if (collision.CompareTag("Jellyfish"))
        {
            Inventory.instance.AddCoins(1);
            ScoreScript.instance.AddScore();

            Destroy(gameObject);
        }
    }
}

spawner pièce:

Code : Tout sélectionner

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

public class Coinspawner : MonoBehaviour
{
    public GameObject Coin;
    public static bool spawnAllowed;

    float next_spawn_time;


    void Start()
    {
        Invoke("SpawnObject", 0f);
        spawnAllowed = true;
        next_spawn_time = Time.time + 12.0f;
    }


    void Update()
    {
        if (GameObject.Find("Money(Clone)") != null)
        {
            spawnAllowed = false;
        }
        else
        {
            spawnAllowed = true;
            if (spawnAllowed)
            {
                if (Time.time > next_spawn_time)
                {
                    Instantiate(Coin, transform.position, transform.rotation);
                    next_spawn_time += 12.0f;
                }
            }
        }
    }

    public void SpawnObject()
    {
            Instantiate(Coin, transform.position, transform.rotation);
    }
}
Voila, en espérant pouvoir être aider s'il vous plait :super:

Avatar de l’utilisateur
boubouk50
ModoGenereux
ModoGenereux
Messages : 6186
Inscription : 28 Avr 2014 11:57
Localisation : Saint-Didier-en-Bresse (71)

Re: Spawn coin

Message par boubouk50 » 29 Oct 2021 09:14

Salut Jonathan,

Je vais commencer par un revue du code, pour en montrer les faiblesses avant d'aller plus loin.
Je considère que ce script n'est présent qu'une seule fois dans la scène.

Code : Tout sélectionner

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

public class Coinspawner : MonoBehaviour
{
    public GameObject Coin;
    public static bool spawnAllowed; //As-tu besoin que ce booléen soit static? Souvent une mauvaise pratique

    // GameObject spawnedCoin = null;
    float next_spawn_time;


    void Start()
    {
        // Pourquoi un Invoke?? Tu peux tout simplement appeler la fonction Spawnobject ();
        Invoke("SpawnObject", 0f);
        
        // L'Update s'en charge mais c'est tjs une bonne pratique s'initialiser une variable (cependant cela peut être fait à sa création
        spawnAllowed = true;
        
        // C'est donc décidé, un spawn aura tjs lieu au bout de 12 secondes -> Manque de flexibilité
        next_spawn_time = Time.time + 12.0f;
    }

    // C'est ici que tout se joue. Déjà l'Update crée énormément de redondance, il est appelé à chaque frame pour des informations que tu possèdes déjà!
    void Update()
    {
        // Les Find sont à proscrire dans un Update, Unity va chercher dans toute la hiérarchie un objet précis, c'est lourd! (même si les portables sont des foudres de guerre ajd, c'est une faiblesse de logique)
        // Tu aurais pu récupérer le GameObject depuis Instantiate () et le tester plutôt
        // Voir ta fonction SpawnObject pour récupérer spawnedCoin
        // if (spawnedCoin != null) <- Beaucoup moins lourd que rechercher un GameObject puisque tu le connais déjà!
        if (GameObject.Find("Money(Clone)") != null)
        {
            spawnAllowed = false;
        }
        else
        {
            spawnAllowed = true;
            // spawnAllowed est forcément vraie puisque tu viens de le passer à true. Cette condition est inutile
            if (spawnAllowed)
            {
                if (Time.time > next_spawn_time)
                {
                    // Ici tu instancies la pièce à l'endroit de ce script, est ce un bon comportement??
                    Instantiate(Coin, transform.position, transform.rotation);
                    next_spawn_time += 12.0f;
                }
            }
        }
    }

    // Cette fonction devient inutile si tu ne l'utilises pas dans ton code (notamment dans l'Update)
    public void SpawnObject()
    {
    	    // Récupérer le GameObject
    	    // spawnedCoin = Instantiate(Coin, transform.position, transform.rotation);
            Instantiate(Coin, transform.position, transform.rotation);
    }
}

Maintenant la logique:
Ici, tu cherches si une pièce existe.
Si oui, tu ne fais rien.
Si non, tu vérifies si le temps a dépassé ton temps de spawn.
Première faille -> Cela ne prend pas en compte le moment où la pièce est récupérée. Elle est donc créée 12 secondes après la création de la précédente pas après l'avoir recueillie.
Si le temps n'est pas dépassé, tu ne fais rien.
S'il l'est, tu instancies une nouvelle pièce et tu ajoutes 12 à ton next_spawn_time.
Deuxième faille -> Tu dois ajouter 12 secondes au temps en cours. Sinon, si les 12 secondes ont été dépassées, tu auras moins de 12 secondes, jusqu'au point de toujours être en retard et donc créer une pièce directement après l'avoir récupérée. Tu vas donc cumuler les pièces jusqu'à ce que next_spawn_time revienne à flot.

Ici, la logique est bourrine.
Tu crées une pièce, tu vérifies continuellement si la pièce est récoltée, si c'est le cas, je teste encore pendant les secondes restantes puis je crée une nouvelle.
Une logique plus simple voudrait:
Je crée une pièce. J'attends que cette pièce soit récoltée (elle me le dira depuis son script). Une fois récoltée, je compte 12 secondes. Une fois les 12 secondes passées, je crée une nouvelle pièce.

Cela mets en place des concepts que tu ne connais peut être encore pas, tel les événements et les coroutines.
Pour les événements, ils te permettent d"'inverser ta llogique pour ce cas précis.
Au lieu de faire: la pièce existe-t-elle? à chaque frame depuis l'Update (). Tu ne fais rien, et c'est la pièce qui t'envoie l'information quand elle est récoltée. Ainsi ce n'est plus à ton Spawner de le savoir, ça l'allège d'une responsabilité qu'il ne devrait pas avoir de toute manière.
Pour la coroutine, elle te permet de te proscrire de l'Update pour éviter de faire des calculs continus et de pouvoir "contrôler" le temps passé.

Voici ce que cela donnerait avec une approche événementielle:

Code : Tout sélectionner

using UnityEngine;
using UnityEngine.UI;
using System.Collections;

public class Coin : MonoBehaviour
{
    // Déclaration des Events
    public delegate void CoinEvent ();
    public static event CoinEvent onCoinRetrieved ;

    private void OnTriggerEnter2D(Collider2D collision)
    {
        if (collision.CompareTag("Jellyfish"))
        {
            Inventory.instance.AddCoins(1);
            ScoreScript.instance.AddScore();
            
            // Si un objet s'est abonné à l'événement alors lancer l'événement -> c'est l'idée du ?, il teste si l'abonnement à l'Event est nul.
            // Les instances Inventory et ScoreScript pourraient s'abonner à cet événement au lieu d'être des Singletons. L'avantage serait que si une instance est nulle, (détruite ou autre) alors cela ne fera pas planter l'application.
            onCoinRetrieved?Invoke ();

            Destroy(gameObject);
        }
    }
}

Code : Tout sélectionner

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

public class Coinspawner : MonoBehaviour
{
    public GameObject Coin;
    //Je le laisse cette variable statique au cas où tu aurais besoin de cette info ailleurs, mais ce n'est pas une bonne pratique.
    public static bool spawnAllowed = true;
    public float spawnTime = 12f;

    void Start()
    {    
        // Spawn la première pièce
        SpawnObject ();
    }
    
    void OnEnable ()
    {
    	// S'abonner à l'événement de récupération de pièce lorsque ce component est actif
    	// Ici, lorsque l'événement onCoinRetrieved sera levé, DelayedSpawn sera appelé
        Coin.onCoinRetrieved += DelayedSpawn;
    }
    
    void OnDisable ()
    {
    	// Se désabonner à l'événement de récupération de pièce lorsqu'il se désactive
        Coin.onCoinRetrieved -= DelayedSpawn;
    }

    // Cette fonction sera appelée par l'événement lorsqu'une pièce sera récupérée 
    private void DelayedSpawn ()
    {
        // Tu viens de récupérer la pièce, donc tu autorises la création d'une nouvelle (spawnAllowed est inutile si pas utilisé ailleurs qu'ici
        spawnAllowed = true;
            
        // Appeler la fonction SpawnObject dans spawnTime secondes
    	Invoke ("SpawnObject", spawnTime);
    }

    public void SpawnObject()
    {
            Instantiate(Coin, transform.position, transform.rotation);
            
            // Tu viens d'instancier la pièce, donc tu n'autorises plus la création d'une nouvelle
            spawnAllowed = false;
    }
}
Je n'ai pas introduit la coroutine pour ne pas trop t'en mettre plein la tête d'un coup, mais c'est ce que fait Invoke de façon masquée. Il gère le temps (spawnTime secondes) avant de faire quelque chose.

J'aurai pu me proscrire de l'événement. Lors de l'instanciation de la pièce, j'aurais pu récupérer son gameObject puis son component Coin pour lui donner la référence de ce script. Ainsi, la pièce aurait eu connaissance de ce script et aurait pu appeler DelayedSpawn elle même (il aurait fallu rendre cette fonction publique du coup).
Mais une introduction aux événements me paraît plus intéressante pour toi. Histoire de comprendre la logique derrière.
Une chose à retenir, si dans ta logique tu attends quelque chose, ou boucle sur un test, alors tu peux normalement inverser cette logique pour que ce que tu attends te donne le signal plutôt que de le tester.

Ce code a été fait à main levée, il n'a pas été testé. Si jamais il y a des erreurs, n'hésite pas à chercher d'abord, puis à demander ici.
"Ce n'est pas en améliorant la bougie, que l'on a inventé l'ampoule, c'est en marchant longtemps."
Nétiquette du forum
Savoir faire une recherche
Apprendre la programmation

Jonathanbtq
Messages : 6
Inscription : 05 Oct 2020 15:47

Re: Spawn coin

Message par Jonathanbtq » 29 Oct 2021 18:20

Bonjour, merci beaucoup pour cette réponse
J'ai pu apprendre pas mal :-D

Le script est top, j'ai tenté et ca marche nickel, j'en revien pas qu'une chose si simple que faire spawn une pièce et réapparaitre pouvais me faire bug comme ca haha

Merci encore et bonne journée :super:

Je dois fermé le ticket j'imagine? je vais essayer

Verrouillé

Revenir vers « Scripting »