
Unity is a game engine, with far from zero entry threshold (comparing with the same Game Maker Studio), and in this article I will tell you what problems I encountered starting to study it, and what solutions I found. I will describe these moments on the example of my 2d puzzle game for Android (which, I hope, will be released soon in the Play Market).
I do not pretend to be true, and I do not call to repeat after myself, if you know the best way, I will only show you how I do it myself, and maybe someone who is just starting to get acquainted with Unity will create his indie masterpiece with less effort.
')
I am an engineer by designing power plants, but coding has always interested me, and I am familiar with some programming languages. Therefore, we agree that to create games on Unity:
- You need to know a little C # or JavaScript (at least C-shaped syntax).
Everything that will be written further, this is not a tutorial on Unity, of which there are enough in the network without me. Below will be collected the difficult moments that may occur when creating your first project on Unity.
It should be warned that in the provided scripts a large part of the game logic (representing “commercial secret”) is omitted, but their performance has been tested as examples.
Problem one - ORIENTATION
Orientation lockThe first difficulty I had was that I did not pay enough attention to optimizing the visual interface for screen orientation. The simplest solution is that if the screen orientation change is not needed for the gameplay, then it is better to block it. No need for excessive flexibility, you are writing an indie game, not a project on the other side of a million dollars. Why tons of conditional transitions and change of anchors, if the game looks better in Portrait (for example). You can lock screen orientation here:
Edit> Project Settings> Player
Different permissionsIt is also important to test the visual interface at different resolutions in the selected orientation, and when testing, do not forget about the existence of devices with 4: 3 aspect ratios (well, or 3: 4), so feel free to add 768x1024 (or 1024x768).
Best positioningTo adjust the positioning and scale of game objects it is better to use the Rect Transform.

Problem Two - COMMUNICATION
I had a similar problem due to the fact that I made my first acquaintance with game-devise through Game Maker Studio, where the script is a full-fledged part of the game object, and it immediately has full access to all components of the object. With Unity, the scripts are shared, and only their instances are added to the object. Speaking simplistically, the script does not know directly which object is currently being executed. Therefore, when writing scripts, it is necessary to take into account the initialization of interfaces for working with object components or with components of other objects.
We train on catsIn my game there is a GameField object, on the stage there is only one copy of it, there is also a script of the same name. The object is responsible for displaying the game score and for playing the entire game sound, so in my opinion it is more economical for memory (there are only three Audio Source in the game - one Background Music, two other Sound Effects). The script solves the issues of storing a game account, choosing an AudioClip to play sound, and for some game logic.
We will dwell on the sound in more detail, since with this example it is easy to show the interaction of the script with the components of the object.

Naturally, the object should have the GameField.cs script itself and the AudioSource component, in my case as many as two (later it will be clear why).
As it was said earlier, the “not aware” script that the object has an AudioSource component, therefore we declare and initialize the interface (for the time being we consider that AudioSource is only one):
private AudioSource Sound; void Start(){ Sound = GetComponent<AudioSource> (); }
The method GetComponent <type_type> () will return the first component of the specified type from the object.
In addition to AudioSource, you will need several 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;
Here and further, the commands in square brackets are needed for the Inspector`a, more
here .

Now the script in the Inspector `e has new fields, in which we drag the necessary sounds.
Next, create a SoundPlay method in the script that accepts AudioClip:
public void PlaySound(AudioClip Clip = null){ Sound.clip = Clip; Sound.Play (); }
To play the sound in the game, we call this method at the right time with an indication of the clip.
There is one significant disadvantage of this approach; only one sound can be played at a time, but during the game you may need to play two or more sounds, with the exception of background music that plays constantly.
To avoid cacophony, I recommend avoiding the possibility of simultaneous playback of more than 4-5 sounds (preferably a maximum of 2-3), I mean playing short sounds of the first plan (jump, coin, player’s shot ...), for background noise it’s better to create your own source sound on the object that emits this noise (if you need a 2d-3d sound) or one object responsible for all the background noise (if "volume" is not needed).
In my game, there is no need to simultaneously play more than two AudioClips. For guaranteed reproduction of both hypothetical sounds, I added two AudioSource to the GameField object. To determine the components in the script, use the method
GetComponents<_>()
which returns an array of all components of a specified type from an object.
The code will look like this:
private AudioSource[] Sound;
Most changes will affect the PlaySound method. I see two variants of this method: “universal” (for any number of AudioSource in the object) and “clumsy” (for 2-3 AudioSource, not the most elegant but less resource-intensive).
"Clumsy" option for two AudioSource (I used it)
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 (); } }
You can stretch into three or more AudioSource, but the number of conditions will devour all the performance savings.
"Universal" option
private void PlaySound(AudioClip Clip = null){ foreach (AudioSource _Sound in Sound) { if (!_Sound.isPlaying) { _Sound.clip = Clip; _Sound.Play (); break; } } }
Appeal to someone else's componentThere are several instances of the Fishka prefab on the playing field, like a game chip. It is built like this:
- Parent with its SpriteRenderer;
- Child objects with their SpriteRenderer.
Child objects are responsible for drawing the body of the chip, its color, additional variable elements. The parent object draws a marker edging around the chip (according to the game, you need to select the active chip). The script is only on the parent object. Thus, to manage child sprites, you need to specify these sprites for the parent script. I organized it this way - in the script I created interfaces for accessing the child 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;
Now the script in the Inspector`e has additional fields:

Dragging the child elements into the corresponding fields we get access to them in the script.
Example of use:
void OnMouseDown(){
Appeal to someone else's scriptIn addition to manipulating other people's components, you can also access the script of a third-party object, work with its Public variables, methods, subclasses.
I will give an example on the already known GameField object.
The GameField script has a public method, FishkiMarkerDisabled (), which is needed to “remove” a marker from all chips on the field and is used in the process of setting a marker when clicking on a chip, since only one can be active.
In the Fishka.cs script, SpriteRenderer Marker is public, that is, it can be accessed from another script. To do this, in the GameField.cs script, add the declaration and initialization of interfaces for all instances of the Fishka class (when the script is created, a class of the same name is created in it) like it was done for several 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; } }
In the Fishka.cs script, we add the declaration and initialization of the interface of the GameField class instance and when clicking on an object we will call the FishkiMarkerDisabled () method of this class:
private GameField gf; void Start(){ gf = GameObject.FindObjectOfType (typeof(GameField)) as GameField; } void OnMouseDown(){ gf.FishkiMarkerDisabled(); Marker.enabled = !Marker.enabled; }
Thus, it is possible to interact between scripts (more correctly, classes) of different objects.
Problem Three - KEEPERS
Account keeperAs soon as something like an account appears in the game, immediately the problem of its storage arises, both during and outside the game, you also want to save the record in order to encourage the player to surpass it.
I will not consider options when the whole game (menu, game, withdrawal) is built in one scene, because, firstly, this is not the best way to build the first project, secondly, in my opinion, the initial boot scene should be . Therefore, we agree that the project has four scenes:
- loader - a scene in which the background music object is initialized (more detailed later), and loading settings from the save;
- menu - a scene with a menu;
- game - game scene;
- score - the scene of the withdrawal of the score, record, leaderboard.
Note: Scene loading order is set in File> Build SettingsPoints scored during the game are stored in the Score variable of the GameField class. To access the data on the transition to the scores stage, create a public static class ScoreHolder, in which we will declare a variable to store the value and a property to get and set the value of this variable (the method
found in
apocatastas ):
using UnityEngine; public static class ScoreHolder{ private static int _Score = 0; public static int Score { get{ return _Score; } set{ _Score = value; } } }
The public static class does not need to be added to any object; it is immediately available in any scene from any script.
An example of using the GameField class in the transition method to the scores stage:
using UnityEngine.SceneManagement; public class GameField : MonoBehaviour { private int Score = 0;
In the same way, you can add a storage to the ScoreHolder during the game of a record score, but it will not be saved upon exit.
Settings KeeperConsider the example of saving the value of a Boolean variable SoundEffectsMute, depending on the state of which the game has sound effects or not.
The variable itself is stored in a public static class SettingsHolder:
using UnityEngine; public static class SettingsHolder{ private static bool _SoundEffectsMute = false; public static bool SoundEffectsMute{ get{ return _SoundEffectsMute; } set{ _SoundEffectsMute = value; } } }
The class is similar to ScoreHolder, you could even combine them into one, but in my opinion this is a moveton.
As can be seen from the script, by default _SoundEffectsMute is declared false, so every time the game starts, the SettingsHolder.SoundEffectsMute will return false regardless of whether the user has previously changed it or not (using the button on the menu menu to change it).
Saving VariablesThe most optimal for the Android application will be using the PlayerPrefs.SetInt method for saving (for more, see the
official documentation ). There are two options to keep the SettingsHolder.SoundEffectsMute value in PlayerPrefs, let's call them “simple” and “elegant”.
The “simple” method (for me) is in the OnMouseDown () method of the class of the above button. The stored value is loaded in the same class but in the Start () method:
using UnityEngine; public class ButtonSoundMute : MonoBehaviour { void Start(){
"Elegant" way, in my opinion, is not the most correct, because complicate the maintenance of the code, but there is something in it, and I can not share it. A feature of this method is that the SettingsHolder.SoundEffectsMute property setter is called at the moment that does not require high performance, and you can load it (oh, horror) using PlayerPrefs (read by writing to a file). Change public static class 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); } } }
The OnMouseDown method of the ButtonSoundMute class is simplified to:
void OnMouseDown(){ SettingsHolder.SoundEffectsMute = !SettingsHolder.SoundEffectsMute; }
Load getter reading from the file is not worth it, because it is involved in the performance-critical process - in the PlaySound () method of the GameField class:
private void PlaySound(AudioClip Clip = null){ if (!SettingsHolder.SoundEffectsMute) {
As described above, you can organize in-game storage of any variable.
Problem Five - ONE FOR ALL
This music will be eternalEveryone faces this problem sooner or later, and I was no exception. As planned, the background music begins to play in the scene menu, and if it is not turned off, then the menu, game and scores play on the scenes without interruption. But if the object “playing” background music is set on the menu stage, when you go to the game scene, it is destroyed and the sound disappears, and if you place the same object on the game scene, then after the transition the music plays first. The solution turned out to be the use of the DontDestroyOnLoad (Object target) method of the class placed in the Start () method, which has a “musical” object a script instance. To do this, create a DontDestroyThis.cs script:
using UnityEngine; public class DontDestroyThis: MonoBehaviour { void Start(){ DontDestroyOnLoad(this.gameObject); } }

For everything to work, the “musical” object must be root (at the same hierarchy level as the main camera).
Why background music in loaderThe screenshot shows that the “musical” object is not located on the menu menu but on the loader stage. This is a measure caused by the fact that the menu scene can be loaded more than once (after the scores scene, the transition to the menu scene), and each time it is loaded, another “musical” object will be created, and the old one will not be deleted. It can be done as in the example of
official documentation , I decided to use the fact that the scene loader is guaranteed to be loaded only once.
On this, the key problems that I encountered when developing my first game on Unity, before being unloaded in the Play Market (I have not yet registered a developer account), have successfully ended.
PSIf the information was useful, you can support the author, and he will finally register an Android account for the developer.