Code structure in Unity3d - personal opinion and a couple of tricks

    image
    I would like to share my personal impressions of the development of mobile games based on Unity3d. Initially, I thought to fit in one post all the small “Tip & Trick” that I encountered when working with Unity3d recently. But there were too many of them. So in this post there will be only those that relate directly to code writing.

    The main topic of the post is the separation of classes into “layers”, linking them through events and a little bit about how to establish the interaction of objects on the stage.
    Who cares - welcome to kat!


    Application structure


    image
    Initially, I did not think of any structure - I knew Unity too poorly, I did not understand what it was possible to separate from what, what to combine and how to connect it. However, during the development process in the application, the following loosely interconnected layers stand out by themselves:
    1. Interface - this includes all sorts of menus and controls.
    2. Game objects - everything that is on the stages of the levels and is directly related to the geo-game.
    3. The state of the game is those classes that are responsible for the process of passing the game. What level did the player pass? In what time? How many points did you score? What level does he need to download now? Do I need to unlock any content? Etc.
    4. Interaction with Google services - classes that are responsible for posting the result and unlocking achievements.
    5. Advertising - classes responsible for displaying advertising.
    6. Social services - posting on Facebook, VKontakte, etc.

    This list is a personal example in the case of a couple of specific applications. In other applications, it can be very different. But the general idea of ​​it will be clear.

    When these layers somehow formed in my head, the question arose - how to establish interaction between them? Those. I understood that the class that is responsible for loading the next level should not be tied to the corresponding button in the menu. But how to do that?
    The problem was resolved through static classes and events.

    The general idea is that the objects in each layer do not interact with the objects in the other layer. They only cause static events on their layer, and everyone who needs it already subscribes to them.
    Why are events static?

    The fact is that in order to subscribe to the events of a particular object in the scene you need to have a link to it. And this adds a bunch of unnecessary connections and the idea of ​​separating “layers” is lost.
    In some cases, the solution turned out to be even simpler - it was enough to add a singleton to communicate with the layer, and the interaction was greatly simplified.
    Next, I will try to describe with examples how this works in each layer.

    Interface


    image
    Unity tutorials are full of such examples:
    public class ExampleClass : MonoBehaviour {
        public GameObject projectile;
        public float fireRate = 0.5F;
        private float nextFire = 0.0F;
        void Update() {
            if (Input.GetButton("Fire1") && Time.time > nextFire) {
                nextFire = Time.time + fireRate;
                Instantiate(projectile, transform.position, transform.rotation) as GameObject;
            }
        }
    }
    

    This, by the way, is from the official documentation.
    What is wrong here?

    During development and debugging in Unity, using buttons is very convenient. But when switching to a mobile device, they are replaced by interface elements. If you fasten on the buttons, you will then have to edit the class of the game object. In addition, what should I do if several objects should respond to a button press?
    It turned out to be more convenient to create a separate class, for example, UserInterface, declare the OnFireButtonClick global event in it, and subscribe to it wherever you need.

    The main idea on the interface is this: not to do Input processing in game objects. Make separate interface objects that will accept all commands from the user, and subscribe to events in game objects. It is better to generate events in a singleton, since specific interface objects can change, and this should not affect game objects.
    Brrr ... Somehow it turned out to be confusing, but I hope I brought the general idea.

    By the way, if you subscribe to events, do not forget to add a "reply":
    void OnDestroy()
        {
            <ваше_событие> -= <ваш_обработчик>;
        }
    


    And it will be a funny situation when the scene is closed, and the object lives and processes events. And the garbage collector will not work correctly. You can’t unsubscribe only if the object generating the event is in the same scene as the handler and will be deleted with it.

    There is an incomprehensible situation - I assumed that closing the scene means destroying all its objects and automatically “unsubscribing” from everything. It turned out that this is not so. It is more likely that the application simply leaves the block responsible for the scene. It is assumed that objects are no longer referenced and will be removed by the garbage collector. I do not understand if this is a bug or a feature. If anyone understands the essence of this situation - share, pliz, knowledge. Really curious.

    Game objects


    image
    This is all that runs, jumps, spins, shoots and interacts with each other on our stage.
    Unity offers several approaches for their communication.

    1. Link them through public properties. It looks something like this:
    public class PlatformMoveController : MonoBehaviour {
    	public GameObject Player;
    	...		
    	 void OnCollisionStay(Collision col) 
    	{
    		if (col.gameObject.name == Player.name)
    			onTheGround = true;
    	}
    	...
    }
    

    The downside is that we need to connect objects together. And if we dynamically generate them?

    Here a second approach comes to our aid:

    2. Find an object on the scene by name or tag. For instance:
    private GameObject _car;
    ...
    void Start () 
    	{
    	_car = GameObject.Find ("Car");
    	if (_car == null)
    		throw new UnityException ("Cannot find 'Car' object!");
    	_car.OnCrash += DoSomething();
    	}
    …
    

    The minus of this method is visible in the example itself - the desired object in the scene may not appear. Well, if he is not there at all, and we can do nothing, but what if he was added there, but with a different name?

    Here again we can get around a static event. If each Car instance throws its collision through a static class event (CarManager), we don’t have to look for an object or bind to it.

    You get something like:
    class Car
    {
    	void Crash()
    	{
    	...
    	CarManager.CrashCar();
    	}
    }
    public static class CarManager
    {
    	public static void CrashCar()
    	{
    	if (OnCarCrash != null)
    		OnCarCrash();
    	}
    }
    


    Handler:
    void Start () 
    {
    	CarManager.OnCarCrash += DoSomething();
    }
    void OnDestroy()
    {
     	CarManager.OnCarCrash -= DoSomething();
    }
    


    This approach has its drawbacks and pitfalls, so you need to use it carefully and do not forget to unsubscribe from events. But sometimes it helps a lot.

    Game state


    The state of the game is the generalized name of the classes responsible for fixing the passage of levels, loading the next, points scored, etc.
    The main thing to consider is that you do not need to do Application.LoadLevel (<scene name>); sooner or later from the game objects, you will need to enter additional checks, calculations, intermediate screens, etc. It is better to put all this in a separate class. You can also make pause and restore commands for the game, etc.

    for instance
    public static class GameController
    {
    	public static event EmptyVoidEventType OnPause;
    	public static event EmptyVoidEventType OnResume;
    	public static event EmptyVoidEventType OnReplay;
    	public static void PauseGame()
    	{
    		if (OnPause != null)
    			OnPause ();
    	}
    	public static void ResumeGame()
    	{
    		Time.timeScale = 1;
    		if (OnResume != null)
    			OnResume ();
    	}
    	public static void ReplayGame()
    	{
    		LevelManager.LoadLastLevel();
    		if (OnReplay != null)
    			OnReplay ();
    	}
    	public static void FinishLevel ()
    	{
    	...
    	}
    	public static void LoadLastLevel ()
    	{
    	...
    	}
    	public static void GoToMenu()
    	{
    	...
    	}
    	...
    }
    


    Those. the scene no longer has to worry about what to do when the level is completed and what the name was from the previous scene if the player suddenly wants to replay it. We just call the manager class and that’s it.
    Similarly, if we need to react to the suspension of the game in the scene, for example, put Time.timeScale = 0; just subscribe to the corresponding event.
    All these little things greatly simplify the development.

    Interaction with Google Services


    image
    Google has become generous with the wonderful services that can be called from a mobile toy. Now you do not need to write your own leaderboards, achievements, and much more. In addition, these services are developing and promise to become some kind of super-duper-wunderwaffle.
    Given all this, it seems logical not to smear their call with a thin layer throughout the application, but to concentrate in one or two classes, connecting with the rest of the application through events. This will allow developing the “social” component of the game without greatly affecting the gameplay.

    A little trick how to quickly and easily check whether there is a connection with Google
    public static bool HasConnection()
        {
            try
            {
                using (var client = new WebClient())
                using (var stream = new WebClient().OpenRead("http://www.google.com"))
                {
                    return true;
                }
            }
            catch
            {
                return false;
            }
        }
    

    I'm not sure that this is the best option, but it looks the most simple and unambiguous.


    Advertising


    image
    Each advertising provider provides its own display tools. There is no desire to understand the game classes with all this zoo. I made a simple statically simple AdsController class for myself, with one public method: ShowBanner () and call it where I want to show ads. All work with AdMob is hidden inside and if I decide to switch to something else, I don’t have to climb the code and catch calls. A trifle, but nice.
    You can also get involved through events, but in my case the singleton came up more, because I’m unlikely to change the interface for calling the ad unit, but game events can change.

    A little trick to save a player’s nerve cells
    public static class AdsController
    {	
        static DateTime lastAdCloseTime;
        static TimeSpan AdsInterval = new TimeSpan(0,3,0);
    ...
    // Это событие - обработчик успешно закрытия показанного баннера
        static void AdClosed(object sender, System.EventArgs e)
        {
            lastAdCloseTime = DateTime.Now;        
        }
        public static void ShowBanner()
        {       
            if (DateTime.Now - lastAdCloseTime > AdsInterval)
                <показать_рекламу>
        }
    	...
    }
    


    Those. We’ll limit the frequency of successful impressions. Actual for displaying banners. For example, I show them at the end of a level or death. But the first levels are completed in a dozen seconds. In order not to irritate the player, he added such a restriction.
    The main thing is to consider successful impressions. And then with glitches with the connection, the player will never see ads and will be disappointed that he did not thank the developer for his attention.


    Social Services


    This “layer” in its role is very similar to an advertising one, but its essence is even simpler: put all the commands for social networks in a heap, put out one static class with methods: PostToFacebook, PostToVk, etc. All that you can hide is to hide inside the classes. Pass the minimum through the parameters. I have it reduced to one parameter - the number of points scored. In principle, the use of this class comes down to simply invoking the desired method when the user presses the Share button.
    In this form, it can be dragged from project to project with virtually no changes.

    Conclusion


    I hope that this stream of thoughts was useful to someone. I know that I didn’t say anything special, and that people working with Unity have long and intelligently known all this and another 100 more suitable ways to do the same. I hope that I have helped beginners, and experts will share their experience in the comments.

    If there is interest, I can tell you about the bumps that I typed when importing 3d objects and building the project structure.

    Background of the post for the curious
    About a year ago, I wrote to Habr about how the development of mobile games became my hobby: GameDev as a hobby . After this post, I still learned a lot and a lot of what “burned”. After each “unexpected” experience, there was a desire to sit down and write about it, but there was either laziness, or I wanted to better check the idea, and then share it.
    I installed a “didline” for myself - update the toy to the second version and sit down to write a post. But since another project went in parallel and was strained at work, this process dragged on for six months. Therefore, there were many thoughts, and the post was pretty messy.

    Also popular now: