📜 ⬆️ ⬇️

The structure of the code in Unity3d - personal opinion and a couple of tricks

image
I would like to share my personal impressions about 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 lately. But they turned out too much. So in this post there will be only those that relate directly to writing code.

The main theme of the post is the division of classes into "layers", linking them through events and a little bit about how to organize the interaction of objects on the stage.
Who cares - welcome under the cat!


Application structure


image
Initially, I did not think through any structure - I knew Unity too badly, I didn’t understand what could be separated from what, what to combine and how to connect it. However, in the development process in the application, the following weakly interconnected layers somehow emerged:
  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 geymplay.
  3. The state of the game - these are the classes that are responsible for the process of passing the game. What level is the player passed? 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 the achivok.
  5. Advertising - classes that are responsible for displaying advertising.
  6. Social services - posting on Facebook, VKontakte, etc.

This list is a personal example in the case of a pair 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 solved through static classes and events.

The general idea is that the objects of each layer do not interact with the objects of the other layer. They only cause static events on their layer, and they are already subscribed to all who need it.
Why are events static?

The fact is that 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 extra connections and the idea of ​​separating the “layers” is lost.
In some cases, the solution was 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 use examples to describe 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, from 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 with interface elements. If you tied to the buttons - then you have to edit the class of the game object. In addition, what to do if several objects should respond to a button click?
It turned out to be more convenient to create a separate class, for example, UserInterface, to declare the OnFireButtonClick global event in it, and subscribe to it wherever necessary.

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

By the way, if you subscribe to events, do not forget to add a "unsubscribe":
 void OnDestroy() { <_> -= <_>; } 


And then it will be a funny situation when the scene is closed, and the object lives and processes the event. And the garbage collector will not work correctly. You can only unsubscribe if the object causing 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 the destruction of all its objects and the automatic “reply” from everything. It turned out that it is not. It looks more like the application just exits the block responsible for the scene. It is assumed that the objects are no longer referenced and will be deleted by the garbage collector. I did not understand, this is a bug or a feature. If anyone understands the essence of this situation - share, plizz, 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 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 link the objects together. And if we dynamically generate them?

This is where the second approach comes to help:

2. Find an object in the scene by name or tag. For example:
 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 seen in the example itself - the desired object in the scene may not be. Well, if he's not there at all, and we can do nothing, but what if it was added there, but with a different name?

Here again we can bypass through a static event. If each Car Instance will forward its collision through a static class event (CarManager), we will not have to search for an object or bind to it.

It will turn out 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 that they need to use it carefully and do not forget to unsubscribe from the events. But sometimes he helps a lot.

Game state


The state of the game is the generic name of the classes responsible for fixing the passage of the levels, loading the next, scoring points, etc.
The main thing that you need to take into account here is that you don’t need to do Application.LoadLevel (<stage_name>); from game objects - sooner or later, you will need to enter additional checks, calculations, intermediate screens, etc. It is better to bring all this into a separate class. There you can also take commands to pause and restore the game, etc.

for example
 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. in the scene, you no longer have to worry about what you need to do at the end of the level and what is the name of the previous scene if the player suddenly wants to replay it. Just call the class manager and that's it.
Similarly, if we need to respond to a pause in the scene, for example, set Time.timeScale = 0; just subscribe to the appropriate event.
All these little things greatly simplify development.

Interaction with Google services


image
Google has become generous with great services that can be called from a mobile toy. Now you do not need to write your own leaderboards, achivki and much more. In addition, these services are developing and promise to become some kind of super-duper wunder.
Considering 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, associating it with the rest of the application through events. This will allow the development of the “social” component of the game not greatly influencing the geymplay.

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; } } 

Not sure that this is the best option, but it looks the most simple and straightforward.


Advertising


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

A little trick to save the 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 introduce a limit on the frequency of successful hits. Actual for displaying banners. For example, I show them at the end of a level or death. But the first levels are traversed in ten seconds. In order not to irritate the player, added such a restriction.
The main thing is to take into account successful shows. And then with glitches with the connection, the player will never see the advertisement and will be upset that he did not thank the developer for his attention.


Social services


This “layer” in its role is very similar to the advertising one, but its essence is even simpler - all teams for social networks should be heaped in, expose one static class with methods: PostToFacebook, PostToVk, etc. All that can be hidden is hidden inside classes. Pass parameters through a minimum. I have it come down to one parameter - the number of points scored. In principle, the use of this class is reduced simply to calling the desired method when the user presses the Share button.
In this form, it can be dragged from project to project almost unchanged.

Conclusion


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

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

Prehistory of the post for the curious
About a year ago I wrote on Habr about how the development of mobile games became my hobby: GameDev as a hobby . After this post, I learned a lot more and a lot of what I “burned”. After each “unexpected” experience, there was a desire to sit down and write about him, but there was either laziness, or I wanted to get a better idea and then share it.
I set up "didlay" for myself - update the toy to the second version and sit down to write the post. But since another project went in parallel and at work was strained, this process dragged on for half a year. Therefore, there were many thoughts, and the post turned out to be quite confused.

Source: https://habr.com/ru/post/238581/


All Articles