web-dev-qa-db-fra.com

Responsable du jeu Unity. Le script ne fonctionne qu'une fois

Je fais un gestionnaire de jeu simple. J'ai un script qui sera accessible à partir de toutes les scènes du jeu. Et je dois vérifier les valeurs de ses variables après avoir chargé une nouvelle scène. Mais mon code ne s'exécute qu'une fois après le démarrage de la simulation alors qu'un objet avec ce script existe dans toutes les scènes. Qu'est-ce qui ne va pas? Pourquoi ça ne marche pas après le chargement d'une nouvelle scène?

43
Dima Kozyr

Dans chaque projet Unity, vous devez avoir une scène de précharge.

Il est assez déroutant que Unity n'ait pas de scène de préchargement "intégrée".

Ils ajouteront ce concept à l'avenir.

Pour répéter, vous devez littéralement avoir une scène de préchargement.

C'est si simple.

C’est le plus grand malentendu pour les nouveaux programmeurs qui tentent d’utiliser Unity!


Heureusement, il est extrêmement facile d'avoir une scène de précharge.

Étape 1.

Créez une scène nommée "précharge". Il doit s'agir de la scène 0 0 dans Build Manager.

enter image description here

Étape 2.

Dans la scène "préchargement", créez un GameObject vide appelé, par exemple, "__app".

Simplement, mettez DontDestroyOnLoad sur '__app'.

Remarque:

C'est le seul endroit dans tout le projet vous utilisez DontDestroyOnLoad.

C'est si simple.

enter image description here

Dans l'exemple: les développeurs ont créé un script DDOL d'une ligne.

Placez ce script sur l'objet "__app".

Vous ne devez plus jamais penser à DDOL.

Étape 3

Votre application aura (plusieurs) "comportements généraux". Donc, des choses comme la connectivité à la base de données, les effets sonores, le scoring, etc.

Vous devez et ne pouvez que mettre vos comportements généraux sur "_app".

C'est vraiment aussi simple que cela.

Les comportements généraux sont alors - bien sûr - disponibles partout dans le projet, à tout moment et dans toutes les scènes .

Comment pourriez-vous le faire autrement?

Dans l'exemple d'image ci-dessus, notez "Iap" ("achat intégré") et les autres.

Tous vos "comportements généralement nécessaires" - effets sonores, scores, etc. - sont exactement là sur cet objet.

Important...

Cela signifie que - bien sûr, naturellement -

... vos comportements généraux auront, bien sûr, des inspecteurs ordinaires comme tout le reste dans Unity.

Vous pouvez utiliser toutes les fonctionnalités habituelles de Unity, que vous utilisez sur tous les autres objets du jeu.

Vous pouvez bien sûr utiliser les variables Inspector habituelles (glisser pour vous connecter), les paramètres, etc.

(En effet: supposons que vous ayez été embauché pour travailler sur un projet existant. La première chose que vous ferez est de jeter un coup d’œil sur la scène de préchargement. Vous verrez tous les "comportements généraux" du projet au même endroit. Effets sonores , scoring, AI, etc. Vous verrez instantanément tous les paramètres de ces objets sous forme de variables d’inspecteur ... volume de la parole, identifiant du magasin, etc.)

Voici un exemple de comportement général "Effets sonores":

enter image description here

On dirait qu'il y a aussi un comportement général de "voix off" et un comportement général de "musique".

Répéter. En ce qui concerne vos "comportements généraux". (Effets sonores, scores, réseaux sociaux, etc., etc.) Celles-ci NE PEUVENT ALLER QUE sur un objet de jeu dans la scène de préchargement.

Ce n'est pas optionnel: il n'y a pas d'alternative!

Vous avez terminé.

C'est vraiment aussi simple que cela.

Il n'y a littéralement rien à faire!

Beaucoup d'ingénieurs venant d'autres environnements s'y retrouvent, car il semble que "ça ne peut pas être aussi facile".

Je répète simplement que tout le problème est que (bizarrement) Unity a tout simplement oublié d’intégrer une scène de précharge. Il vous suffit donc de cliquer pour ajouter une scène de préchargement - et n'oubliez pas d'ajouter DDOL sur la scène de préchargement.

Donc, pendant le développement:

Commencez toujours votre jeu à partir de la scène de préchargement.

C'est si simple.

(Un point important: votre application comportera certainement des scènes "précoces". Par exemple, un "écran de démarrage" ou un "menu". Notez que vous ne pouvez pas = utilisez "écran de démarrage" ou "menu" comme scène de préchargement. Vous devez avoir littéralement "une scène de préchargement". La scène de préchargement va alors charger votre scène de démarrage, votre scène de menu ou ce que vous souhaitez.)


La question centrale: "trouver" celles d'autres scripts:

Donc, vous avez une scène de précharge.

Tous vos "comportements généraux" sont simplement sur la scène du préchargement.

Vous avez ensuite le problème de trouver, tout simplement, "SoundEffects" ou "GameManager".

Vous devez pouvoir les trouver facilement, à partir de n’importe quel script situé sur n’importe quel objet du jeu dans l’une de vos scènes.

Heureusement, c’est très facile, c’est une ligne de code.

C'est un non-problème:

Sound sound = Object.FindObjectOfType<Sound>();
Game game = Object.FindObjectOfType<Game>();

Faites cela dans Awake, pour tout script qui en a besoin.

C'est vraiment aussi simple que cela. C'est tout ce qu'on peut en dire.

Sound sound = Object.FindObjectOfType<Sound>();

Une énorme confusion résulte des centaines de absolument faux exemples de code vus en ligne. En outre, les nouveaux ingénieurs Unity "ne peuvent pas croire", c’est aussi simple!

C'est vraiment aussi simple que ça - honnêtement!

C’est totalement étrange que Unity ait oublié d’ajouter une "scène de préchargement" intégrée - un endroit pour attacher vos systèmes tels que SoundEffects, GameManager, etc. C’est juste une de ces choses étranges à propos de Unity. La première chose que vous faites dans tout projet Unity est donc de cliquer une fois pour créer une scène de préchargement.

C'est ça!


Un détail...

Notez que si vous voulez vraiment taper encore moins (!) Lignes de code, c'est extrêmement facile - vous pouvez utilisez simplement un global pour chacune de ces choses!

Ceci est expliqué en détail ici (vous pouvez utiliser mon script Grid.cs) et dans la réponse @Frits ci-dessous.

(Que vous utilisiez une variable globale ou simplement une variable locale dans chaque composant qui en a besoin est une question de style de code. Dans des situations très inhabituelles, l'approche "globale" peut être performante (comme avec n'importe quelle variable globale).)


DylanB demande: " Pendant le développement il est assez ennuyant que vous deviez cliquer sur la scène de préchargement avant de cliquer. "Jouer". Cela peut-il être automatisé? "

Bien sûr, chaque équipe a une façon différente de le faire. Voici un exemple trivial:

// this should run absolutely first; use script-execution-order to do so.
// (of course, normally never use the script-execution-order feature,
// this is an unusual case, just for development.)
...
public class DevPreload:MonoBehaviour
 {
 void Awake()
  {
  GameObject check = GameObject.Find("__app");
  if (check==null)
   { UnityEngine.SceneManagement.SceneManager.LoadScene("_preload"); }
  }
 }

Mais n'oubliez pas: "Que pouvez-vous faire d'autre?" Les jeux doivent commencer à partir d'une scène de préchargement. Que pouvez-vous faire d'autre, à part aller à la scène de préchargement, commencer le jeu? Vous pouvez aussi bien demander: "C'est ennuyant de lancer Unity pour exécuter Unity - comment éviter de lancer Unity?" Bien entendu, les jeux doivent tout simplement commencer par une scène de précharge - comment pourrait-il en être autrement? Alors, bien sûr, vous devez "cliquer sur la scène de préchargement avant de cliquer sur Lire" lorsque vous travaillez dans Unity - comment pourrait-il en être autrement?

86
Fattie

@Fattie: Merci d'avoir élaboré tout cela, c'est génial! Il y a un point cependant que les gens essaient de vous contacter, et je vais juste essayer aussi:

Nous ne voulons pas que chaque instanciation de tout dans nos jeux mobiles fasse un "FindObjectOfType" pour chaque "classe globale"!

Au lieu de cela, vous pouvez simplement lui demander d'utiliser immédiatement une Instanciation d'un Singleton statique /, sans le rechercher!

Et c’est aussi simple que cela: écrivez ceci dans la classe à laquelle vous souhaitez accéder, où XXXXX est le nom de la classe, par exemple "Son".

public static XXXXX Instance { get; private set; }
void Awake()
{
if (Instance == null) { Instance = this; } else { Debug.Log("Warning: multiple " + this + " in scene!"); }
}

Maintenant au lieu de votre exemple

Sound sound = Object.FindObjectOfType<Sound>();

Il suffit simplement de l'utiliser, sans regarder, et sans variables supplémentaires, simplement comme ceci, directement de n'importe où:

Sound.Instance.someWickedFunction();

Alternativement (techniquement identique), utilisez simplement une classe globale, généralement appelée Grid, pour "tenir" chacune de celles-ci. Howto . Alors,

Grid.sound.someWickedFunction();
Grid.networking.blah();
Grid.ai.blah();
20
Frits Lyneborg

Voici comment vous pouvez démarrer la scène de votre choix et vous assurer de réintégrer votre scène _preload à chaque fois que vous appuyez sur le bouton de lecture dans l'éditeur d'Unity. Un nouvel attribut est disponible depuis Unity 2017 RuntimeInitializeOnLoadMethod, pour en savoir plus ici .

En gros, vous avez une classe plane c # simple et une méthode statique avec RuntimeInitializeOnLoadMethod dessus. Maintenant, chaque fois que vous démarrez le jeu, cette méthode chargera la scène de préchargement pour vous.

using UnityEngine;
using UnityEngine.SceneManagement;

public class LoadingSceneIntegration {

#if UNITY_EDITOR 
    public static int otherScene = -2;

    [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
    static void InitLoadingScene()
    {
        Debug.Log("InitLoadingScene()");
        int sceneIndex = SceneManager.GetActiveScene().buildIndex;
        if (sceneIndex == 0) return;

        Debug.Log("Loading _preload scene");
        otherScene = sceneIndex;
        //make sure your _preload scene is the first in scene build list
        SceneManager.LoadScene(0); 
    }
#endif
}

Ensuite, dans votre scène _preload, vous avez un autre script qui chargera la scène souhaitée (à partir de l'endroit où vous avez commencé):

...
#if UNITY_EDITOR 
    private void Awake()
    {

        if (LoadingSceneIntegration.otherScene > 0)
        {
            Debug.Log("Returning again to the scene: " + LoadingSceneIntegration.otherScene);
            SceneManager.LoadScene(LoadingSceneIntegration.otherScene);
        }
    }
#endif
...
6
misher