
Unity est un moteur de jeu avec un seuil d'entrée loin de zéro (en comparaison avec le même Game Maker Studio), et dans cet article, je vais vous dire quels problèmes j'ai rencontrés en commençant à l'étudier et quelles solutions j'ai trouvées. Je décrirai ces moments avec l'exemple de mon jeu de puzzle 2d pour Android (qui j'espère sera bientôt publié sur le Play Market).
Je ne prétends pas être vrai, et je ne vous exhorte pas à répéter après vous-même, si vous connaissez la meilleure façon, je vais juste vous montrer comment le faire vous-même, et peut-être que quelqu'un qui commence tout juste à se familiariser avec Unity créera son propre chef-d'œuvre indépendant avec moins de travail.
Je suis ingénieur en conception de centrales électriques, mais j'ai toujours été intéressé par le codage et je connais certains langages de programmation. Par conséquent, nous convenons que pour créer des jeux sur Unity:
- Vous devez connaître un peu C # ou JavaScript (au moins la syntaxe en forme de C).
Tout ce qui sera écrit ci-dessous n'est pas un tutoriel Unity, dont suffisamment ont été créés sur le réseau sans moi. Vous trouverez ci-dessous les moments difficiles qui peuvent survenir lors de la création de votre premier projet sur Unity.
Il convient de signaler que les scripts fournis omettent la majeure partie de la logique du jeu (représentant des "secrets commerciaux"), mais leurs performances en tant qu'exemples ont été vérifiées.
Problème 1 - Orientation
Verrou d'orientationLa première difficulté qui est apparue en moi est que je n'ai pas accordé l'attention voulue à l'optimisation de l'interface visuelle pour l'orientation de l'écran. La solution est la plus simple - si vous n'avez pas besoin de changer l'orientation de l'écran pour le gameplay, il est préférable de le bloquer. Pas besoin d'une flexibilité excessive, vous écrivez un jeu indépendant, pas un projet de l'autre côté d'un million de dollars. Pourquoi des tonnes de transitions conditionnelles et des changements d'ancrage si le jeu est plus beau dans Portrait (par exemple). Vous pouvez verrouiller l'orientation de l'écran ici:
Édition> Paramètres du projet> Lecteur
Différentes autorisationsIl est également important de tester l'interface visuelle à différentes résolutions dans l'orientation sélectionnée, et lors du test, n'oubliez pas l'existence d'appareils avec des proportions de 4: 3 (enfin, ou 3: 4), afin que nous puissions ajouter en toute sécurité 768x1024 (ou 1024x768).
Un meilleur positionnementIl est préférable d'utiliser Rect Transform pour ajuster le positionnement et l'échelle des objets du jeu.

Deuxième problème - COMMUNICATION
J'ai eu un problème similaire en raison du fait que j'ai fait la première connaissance avec le développeur de jeu en utilisant Game Maker Studio, où le script est une partie à part entière de l'objet de jeu, et il a immédiatement un accès complet à tous les composants de l'objet. Unity a des scripts communs et seules leurs instances sont ajoutées à l'objet. Parlant de manière simpliste et figurée, le script ne sait pas directement sur quel objet il s'exécute actuellement. Par conséquent, lors de l'écriture de scripts, vous devez prendre en compte l'initialisation des interfaces pour travailler avec les composants d'un objet ou avec les composants d'autres objets.
Nous nous entraînons sur les chatsDans mon jeu, il y a un objet GameField, sur la scène, il n'y en a qu'une seule instance, il y a aussi un script du même nom. L'objet est chargé d'afficher la partition du jeu et de reproduire l'intégralité du son du jeu, donc à mon avis, il est plus économique pour la mémoire (en général, le jeu n'a que trois sources audio - une musique de fond, deux autres effets sonores). Le script résout les problèmes de stockage d'un compte de jeu, de choix d'AudioClip pour la lecture du son et d'une certaine logique de jeu.
Arrêtons-nous plus en détail sur le son, car cet exemple montre facilement l'interaction du script avec les composants de l'objet.

Naturellement, l'objet doit avoir le script GameField.cs lui-même et le composant AudioSource, dans mon cas deux entiers (plus tard, il sera clair pourquoi).
Comme mentionné précédemment, le script n'est «pas au courant» que l'objet a un composant AudioSource, donc nous déclarons et initialisons l'interface (pour l'instant, nous considérons qu'il n'y a qu'une seule AudioSource):
private AudioSource Sound; void Start(){ Sound = GetComponent<AudioSource> (); }
La méthode GetComponent <component_type> () renvoie le premier composant du type spécifié à partir de l'objet.
En plus d'AudioSource, vous aurez besoin de plusieurs AudioClip:
[Header ("Audio clips")] [SerializeField] private AudioClip OnStart; [SerializeField] private AudioClip OnEfScore; [SerializeField] private AudioClip OnHighScore; [SerializeField] private AudioClip OnMainTimer; [SerializeField] private AudioClip OnBubbMarker; [SerializeField] private AudioClip OnScoreUp;
Ci-après, les commandes entre crochets sont nécessaires pour Inspector`a, plus de détails
ici .

Maintenant, le script dans Inspector a de nouveaux champs dans lesquels nous glissons les sons nécessaires.
Ensuite, créez une méthode SoundPlay dans le script qui prend en AudioClip:
public void PlaySound(AudioClip Clip = null){ Sound.clip = Clip; Sound.Play (); }
Pour jouer du son dans le jeu, nous appelons au bon moment cette méthode avec le clip.
Il y a un inconvénient majeur à cette approche, un seul son peut être joué à la fois, mais pendant le jeu, il peut être nécessaire de jouer deux sons ou plus, à l'exception de la musique de fond jouée en permanence.
Pour éviter la cacophonie, je recommande d'éviter la possibilité de lecture simultanée de plus de 4-5 sons (de préférence un maximum de 2-3), je veux dire des sons courts de premier ordre (saut, pièce, coup de joueur ...), pour le bruit de fond, il est préférable de créer votre propre source son sur l'objet qui fait ce bruit (si vous avez besoin d'un son 2d-3d) ou un objet responsable de tout le bruit de fond (si le "volume" n'est pas nécessaire).
Dans mon jeu, il n'est pas nécessaire de jouer simultanément plus de deux AudioClips. Pour une lecture garantie des deux sons hypothétiques, j'ai ajouté deux AudioSource à l'objet GameField. Pour déterminer les composants du script, nous utilisons la méthode
GetComponents<_>()
qui renvoie un tableau de tous les composants du type spécifié à partir de l'objet.
Le code ressemblera à ceci:
private AudioSource[] Sound;
La plupart des modifications affecteront la méthode PlaySound. Je vois deux versions de cette méthode: «universelle» (pour n'importe quel nombre d'AudioSource dans un objet) et «maladroite» (pour 2-3 AudioSource, pas la plus élégante mais moins gourmande en ressources).
L'option "maladroite" pour deux AudioSource (je l'ai utilisé)
private void PlaySound(AudioClip Clip = null){ if (!Sound [0].isPlaying) { Sound [0].clip = Clip; Sound [0].Play (); } else { Sound [1].clip = Clip; Sound [1].Play (); } }
Vous pouvez étirer jusqu'à trois AudioSource ou plus, mais le nombre de conditions dévorera toutes les économies de performances.
Option "universelle"
private void PlaySound(AudioClip Clip = null){ foreach (AudioSource _Sound in Sound) { if (!_Sound.isPlaying) { _Sound.clip = Clip; _Sound.Play (); break; } } }
Accès à une composante étrangèreSur le terrain de jeu, il existe plusieurs exemples du préfabriqué Fishka, comme une puce de jeu. Il est construit comme ceci:
- Objet parent avec son SpriteRenderer;
- Objets enfants avec leur SpriteRenderer.
Les objets enfants sont chargés de dessiner le corps de la puce, sa couleur, les éléments mutables supplémentaires. Le parent trace une bordure de marqueur autour de la puce (la puce active doit être mise en surbrillance dans le jeu). Le script se trouve uniquement sur l'objet parent. Ainsi, pour gérer les sprites enfants, le script parent doit spécifier ces sprites. Je l'ai organisé comme ceci - dans le script, j'ai créé des interfaces pour accéder aux enfants SpriteRenderer:
[Header ("Graphic objects")] public SpriteRenderer Marker; [SerializeField] private SpriteRenderer Base; [Space] [SerializeField] private SpriteRenderer Center_Red; [SerializeField] private SpriteRenderer Center_Green; [SerializeField] private SpriteRenderer Center_Blue;
Maintenant, le script dans Inspector a des champs supplémentaires:

Faire glisser et déposer des enfants dans les champs correspondants nous donne accès à eux dans le script.
Exemple d'utilisation:
void OnMouseDown(){
Appel du script de quelqu'un d'autreEn plus de manipuler des composants étrangers, vous pouvez également accéder au script d'un objet tiers, travailler avec ses variables, méthodes et sous-classes publiques.
Je vais donner un exemple sur l'objet GameField déjà bien connu.
Le script GameField a une méthode publique FishkiMarkerDisabled (), qui est nécessaire pour "supprimer" un marqueur de toutes les puces sur le terrain et est utilisé dans le processus de définition d'un marqueur lorsque vous cliquez sur une puce, car il ne peut y avoir qu'un seul actif.
Dans le script Fishka.cs, SpriteRenderer Marker est public, c'est-à-dire qu'il est accessible à partir d'un autre script. Pour ce faire, ajoutez la déclaration et l'initialisation des interfaces pour toutes les instances de la classe Fishka dans le script GameField.cs (lors de la création d'un script, la classe du même nom y est créée) de la même manière que pour plusieurs AudioSource:
private Fishka[] Fishki; void Start(){ Fishki = GameObject.FindObjectsOfType (typeof(Fishka)) as Fishka[]; } public void FishkiMarkerDisabled(){ foreach (Fishka _Fishka in Fishki) { _Fishka .Marker.enabled = false; } }
Dans le script Fishka.cs, ajoutez la déclaration et l'initialisation de l'interface de l'instance de classe GameField, et lorsque nous cliquons sur l'objet, nous appelons la méthode FishkiMarkerDisabled () de cette classe:
private GameField gf; void Start(){ gf = GameObject.FindObjectOfType (typeof(GameField)) as GameField; } void OnMouseDown(){ gf.FishkiMarkerDisabled(); Marker.enabled = !Marker.enabled; }
Ainsi, vous pouvez interagir entre des scripts (ou plutôt des classes) de différents objets.
Troisième problème - GARDEURS
Tenue de compteDès que quelque chose comme un compte apparaît dans le jeu, le problème immédiat est son stockage, à la fois pendant le jeu et en dehors de celui-ci, je veux aussi garder un record afin d'encourager le joueur à le dépasser.
Je ne considérerai pas les options lorsque tout le jeu (menu, jeu, retrait de compte) est construit en une seule scène, car, premièrement, ce n'est pas la meilleure façon de construire le premier projet, et deuxièmement, à mon avis, la scène de chargement initiale devrait être . Par conséquent, nous convenons qu'il y a quatre scènes dans le projet:
- chargeur - une scène dans laquelle l'objet de musique de fond est initialisé (plus sera plus tard), et chargement des paramètres à partir de la sauvegarde;
- menu - une scène avec un menu;
- jeu - scène de jeu;
- score - la scène du score, du record, du classement.
Remarque: L'ordre de chargement des scènes est défini dans Fichier> Paramètres de construction.Les points accumulés pendant le jeu sont stockés dans la variable Score de la classe GameField. Pour avoir accès aux données lors de l'accès à la scène des scores, nous allons créer une classe statique publique ScoreHolder, dans laquelle nous déclarons une variable pour stocker la valeur et une propriété pour obtenir et définir la valeur de cette variable (la méthode
espionne les
apocatastas ):
using UnityEngine; public static class ScoreHolder{ private static int _Score = 0; public static int Score { get{ return _Score; } set{ _Score = value; } } }
Une classe statique publique n'a besoin d'être ajoutée à aucun objet, elle est immédiatement disponible dans n'importe quelle scène à partir de n'importe quel script.
Exemple d'utilisation dans la classe GameField dans les scores de la méthode de transition de scène:
using UnityEngine.SceneManagement; public class GameField : MonoBehaviour { private int Score = 0;
De la même manière, vous pouvez ajouter un compte d'enregistrement au ScoreHolder pendant le jeu, mais il ne sera pas enregistré à la sortie.
Gardien des paramètresPrenons l'exemple de l'enregistrement de la valeur de la variable booléenne SoundEffectsMute, selon l'état dans lequel le jeu a ou non des effets sonores.
La variable elle-même est stockée dans la classe statique publique SettingsHolder:
using UnityEngine; public static class SettingsHolder{ private static bool _SoundEffectsMute = false; public static bool SoundEffectsMute{ get{ return _SoundEffectsMute; } set{ _SoundEffectsMute = value; } } }
La classe est similaire à ScoreHolder, vous pouvez même les combiner en un seul, mais à mon avis, ce sont de mauvaises manières.
Comme vous pouvez le voir dans le script, par défaut _SoundEffectsMute est déclaré faux, donc chaque fois que le jeu démarre, SettingsHolder.SoundEffectsMute retournera faux, que l'utilisateur l'ait modifié ou non auparavant (il est modifié à l'aide du bouton sur la scène du menu).
Enregistrement de variablesLe plus optimal pour une application Android sera d'utiliser la méthode PlayerPrefs.SetInt pour la sauvegarde (pour plus de détails voir la
documentation officielle ). Il existe deux options pour conserver la valeur de SettingsHolder.SoundEffectsMute dans PlayerPrefs, appelons-les «simples» et «élégantes».
La façon «simple» (pour moi comme ça) est dans la méthode OnMouseDown () de la classe du bouton susmentionné. La valeur stockée est chargée dans la même classe mais dans la méthode Start ():
using UnityEngine; public class ButtonSoundMute : MonoBehaviour { void Start(){
La méthode "élégante", à mon avis, n'est pas la plus correcte, car compliquer la maintenance du code, mais il y a quelque chose dedans, et je ne peux pas m'empêcher de le partager. Une caractéristique de cette méthode est que le setter de la propriété SettingsHolder.SoundEffectsMute est appelé à un moment qui ne nécessite pas de hautes performances, et il peut être chargé (oh, horreur) à l'aide de PlayerPrefs (lecture - écriture dans un fichier). Modifiez la classe statique publique SettingsHolder:
using UnityEngine; public static class SettingsHolder { private static bool _SoundEffectsMute = false; public static bool SoundEffectsMute{ get{ return _SoundEffectsMute; } set{ _SoundEffectsMute = value; if (_SoundEffectsMute) PlayerPrefs.SetInt ("SoundEffectsMute", 1); else PlayerPrefs.SetInt ("SoundEffectsMute", 0); } } }
La méthode OnMouseDown de la classe ButtonSoundMute se simplifiera pour:
void OnMouseDown(){ SettingsHolder.SoundEffectsMute = !SettingsHolder.SoundEffectsMute; }
Cela ne vaut pas la peine de charger le getter avec la lecture d'un fichier, car il est impliqué dans un processus critique pour les performances - dans la méthode PlaySound () de la classe GameField:
private void PlaySound(AudioClip Clip = null){ if (!SettingsHolder.SoundEffectsMute) {
De la manière ci-dessus, vous pouvez organiser le stockage dans le jeu de toutes les variables.
Cinquième problème - UN POUR TOUS
Cette musique sera éternelleTôt ou tard, tout le monde fait face à un tel problème, et je n'ai pas fait exception. Comme prévu, la musique de fond commence à jouer même dans la scène du menu, et si elle n'est pas désactivée, elle joue le menu, le jeu et les partitions sur les scènes sans interruption. Mais si l'objet "jouant" de la musique de fond est installé sur la scène de menu, lorsque vous accédez à la scène de jeu, il est détruit et le son disparaît, et si vous placez le même objet sur la scène de jeu, puis après la transition, la musique joue en premier. La solution s'est avérée être l'utilisation de la méthode DontDestroyOnLoad (Object target) placée dans la méthode Start () de la classe dont l'instance de script a l'objet «music». Pour ce faire, créez le script DontDestroyThis.cs:
using UnityEngine; public class DontDestroyThis: MonoBehaviour { void Start(){ DontDestroyOnLoad(this.gameObject); } }

Pour que tout fonctionne, l'objet «musical» doit être root (au même niveau de hiérarchie que la caméra principale).
Pourquoi la musique de fond dans le chargeurLa capture d'écran montre que l'objet «musical» ne se trouve pas sur la scène du menu mais sur la scène du chargeur. Il s'agit d'une mesure causée par le fait que la scène de menu peut être chargée plus d'une fois (après la scène de partitions, la transition vers la scène de menu), et chaque fois qu'elle est chargée, un autre objet «musical» sera créé et l'ancien ne sera pas supprimé. Cela peut être fait comme dans l'exemple de
la documentation officielle , mais j'ai décidé de profiter du fait que la scène du chargeur ne sera chargée qu'une seule fois.
Sur ce point, les problèmes clés que j'ai rencontrés lors du développement de mon premier jeu sur Unity, avant de télécharger sur le Play Market (je n'ai pas encore enregistré de compte développeur), se sont terminés avec succès.
PSSi les informations vous ont été utiles, vous pouvez soutenir l'auteur, et il enregistrera enfin un compte de développeur Android.