📜 ⬆️ ⬇️

Tips and tricks for working with Unity3D



I published the first article "50 tips on working with Unity" 4 years ago. Although most of it is still relevant, much has changed for the following reasons:


This article is a version of the original article, revised taking into account all of the above.

Before turning to the tips, I’ll leave a small note first (the same as in the first article). These tips do not apply to all Unity projects:


The Unity website also has recommendations for working on projects (however, most of them are aimed at increasing project productivity) (all of them are in English) :


The working process


1. From the very beginning, determine the scale and create everything of the same scale. If you do not do this, you may have to redo the assets later (for example, the animation does not always scale correctly). For 3D games, it's probably best to take 1 Unity unit equal to 1 meter. For 2D games that do not use lighting and physics, usually 1 Unity unit, equal to 1 pixel (in the “working” resolution) is suitable. For UI (and 2D games), select the working resolution (we use HD or 2xHD) and create all assets for the scale in this resolution.

2. Make each scene run. This will allow you not to switch between scenes to start the game and thus speed up the testing process. This can be tricky if you use objects transferred between scene loads (persistent) that are required in all scenes. One of the ways to achieve this is to make the transferred objects a singleton that will load itself if they are not in the scene. Singletons are discussed in more detail in another council.

3. Apply source code controls and learn how to use it effectively.


4. Always separate test scenes from code. Make commits of temporary assets and scripts to the repository and delete them from the project when you are done with them.

5. Update the tools (especially Unity) at the same time. Unity is already much better at keeping connections when opening a project from versions other than the current one, but connections are still lost sometimes if team members work in different versions.

6. Import third-party assets into a clean project and import a new package for your use from there. When importing directly into a project, assets sometimes cause problems:


To make assets more secure, use the following instructions:

1. Create a new project and import an asset.
2. Run the examples and make sure they work.
3. Arrange the asset into a more appropriate folder structure. (Usually I do not customize the asset for my own folder structure. But I check that all files are in the same folder and that there are no files in important places that can overwrite existing files of my project.)
4. Run the examples and make sure they are still working. (Sometimes it happened that the asset “broke down” when I moved its components, but usually this problem does not arise.)
5. Now remove the components that you do not need (such as examples).
6. Make sure that the asset is still compiled and the prefabs still have all their connections. If there is still something not running, test it.
7. Now select all assets and export the package.
8. Import it into your project.

7. Automate the build process. This is useful even in small projects, but in particular it is useful when:


For information on how to do this, read Unity Builds Scripting: Basic and advanced possibilities.

8. Document your settings. Most of the documentation should be in the code, but something needs to be documented outside of it. Making developers rummaging through code looking for settings means spending their time. Documented settings improve efficiency (if the relevance of the documents is maintained). Document the following:


General code tips


9. Place all your code in the namespace. This avoids the conflict of the code of your own libraries and third-party code. But don't rely on namespaces when trying to avoid code conflicts with important classes. Even if you use other namespaces, do not take “Object”, “Action” or “Event” as class names.

10. Use assertions. Assertions are useful for testing invariants in code and help get rid of logical bugs. Assertions are available through the Unity.Assertions.Assert class. They check the condition and write a message to the console if it is incorrect. If you do not know what the statements may be useful for, see The Benefits of programming with assertions (aka assert statements) .

11. Do not use strings for anything other than displaying text. In particular, do not use strings to identify objects or prefabs. There are exceptions (there are still some elements in Unity that can only be accessed through a name). In such cases, define such strings as constants in files, such as AnimationNames or AudioModuleNames. If such classes become unmanageable, use nested classes to introduce something like AnimationNames.Player.Run.

12. Do not use Invoke and SendMessage. These MonoBehaviour methods call other methods by name. Methods called by name are difficult to track in code (you cannot find “Usages”, and SendMessage has a wide scope, which is even more difficult to track).

You can easily write your own Invoke version using Coroutine and C # actions:

public static Coroutine Invoke(this MonoBehaviour monoBehaviour, Action action, float time) { return monoBehaviour.StartCoroutine(InvokeImpl(action, time)); } private static IEnumerator InvokeImpl(Action action, float time) { yield return new WaitForSeconds(time); action(); } 

Then you can use it in MonoBehaviour like this:

 this.Invoke(ShootEnemy); // ShootEnemy -    (void)   . 

( Addition: Someone suggested using the class ExecuteEvent as an alternative, part of the Unity event system . So far I know not so much about it, but it looks like it should be studied in more detail.)

13. Do not allow spawned objects to confuse the hierarchy when playing a game. Set as a parent for them an object in the scene to make it easier to find objects when playing the game. You can use an empty (empty) game object or even a singleton (see later in this article) without behavior (behavior) to make it easier to access it in code. Name this object DynamicObjects.

14. Be accurate when using null as valid values, and avoid them where possible.

Null values ​​are useful when searching for invalid code. However, if you acquire the habit of ignoring null, the incorrect code will be successfully executed and you will not notice any errors for a long time. Moreover, it can appear deep inside the code, since each layer ignores null variables. I try not to use null at all as a valid value.

I prefer the following idiom: do not check for null and allow the code to fall out when a problem occurs. Sometimes in reusable methods, I check the variable for null and throw an exception instead of passing it to other methods in which it can lead to an error.

In some cases, a null value may be valid, and therefore processed in another way. In such cases, you need to add a comment indicating the reason that the value may be null.

Normal script is often used for values ​​that are configured in the inspector. The user can specify a value, but if he does not, the default value will be used. The best way to do this is to use the Optional ‹T class, which wraps the T values. (This is a bit like Nullable‹ T). You can use a special property renderer to render the checkbox and only show the value field when the checkbox is selected. (Unfortunately, it is impossible to use the generic class directly, it is necessary to extend the classes for certain values ​​of T.)

 [Serializable] public class Optional { public bool useCustomValue; public T value; } 

In your code, you can use it like this:

 health = healthMax.useCustomValue ? healthMax.Value : DefaultHealthMax; 

Addition: many people tell me that it is better to use a struct (does not create garbage and cannot be null). However, this means that you cannot use it as a base class for non-generic classes so that you can use it for fields that can be used in the inspector.

15. If you use Coroutines, learn to use them effectively. Korutin can be a convenient way to solve many problems. However, they are difficult to debug, and with their help you can easily turn code into chaos, in which no one, even you, can figure it out.

You must understand:


 //   IEnumerator RunInParallel() { yield return StartCoroutine(Coroutine1()); yield return StartCoroutine(Coroutine2()); } public void RunInSequence() { StartCoroutine(Coroutine1()); StartCoroutine(Coroutine1()); } Coroutine WaitASecond() { return new WaitForSeconds(1); } 

16. Use extension methods to work with components that have a common interface. ( Addition: It seems that GetComponent and other methods now also work for interfaces, so this advice is redundant) It is sometimes convenient to get components that implement a specific interface or find objects with such components.

The implementation below uses typeof instead of generic versions of these functions. Generic versions do not work with interfaces, but typeof works. The method below wraps it in generic methods.

 public static TInterface GetInterfaceComponent(this Component thisComponent) where TInterface : class { return thisComponent.GetComponent(typeof(TInterface)) as TInterface; } 

17. Use extension methods to make the syntax more convenient. For example:

 public static class TransformExtensions { public static void SetX(this Transform transform, float x) { Vector3 newPosition = new Vector3(x, transform.position.y, transform.position.z); transform.position = newPosition; } ... } 

18. Use a softer alternative to GetComponent. Sometimes the forced addition of dependencies via RequireComponent can be unpleasant, it is not always possible or acceptable, especially when you call GetComponent for someone else’s class. Alternatively, the following GameObject extension can be used when the object should display an error message if it is not found.

 public static T GetRequiredComponent(this GameObject obj) where T : MonoBehaviour { T component = obj.GetComponent(); if(component == null) { Debug.LogError("   " + typeof(T) + ",   ", obj); } return component; } 

19. Avoid using different idioms to perform the same actions. In many cases, there are various idiomatic ways of doing things. In such cases, select one idiom and use it for the entire project. And that's why:


Examples of idiom groups:


20. Create and maintain your own time class to make work with pauses more convenient. Wrap Time.DeltaTime and Time.TimeSinceLevelLoad to control pauses and time scales. The use of a class requires discipline, but it makes everything much easier, especially when performed with different time counters (for example, interface animation and game animations).

Addition: Unity supports unscaledTime and unscaledDeltaTime, which make your own time class redundant in many situations. But it can still be useful if the scaling of global time affects components that you did not write in undesirable ways.

21. User classes that require updates should not have access to global static time. Instead, they should receive the time delta as a parameter of the Update method. This allows you to use these classes when implementing the pause system described above, or when you want to speed up or slow down the behavior of a custom class.

22. Use the general structure to make WWW calls. In games with a large amount of communication with the server, there are usually dozens of WWW calls. Regardless of whether you use the raw WWW Unity class or a plugin, it will be convenient to write a thin layer on top that will work as a boilerplate.

Usually I define the Call method (separately for Get and Post), CallImpl and MakeHandler corutes. In essence, the Call method creates, using the MakeHandler method, a “super hander” from the parser, on-success and on-failure handler. He also calls CallImpl's coroutine, which forms the URL, makes the call, waits for it to complete, and then calls the "superprocessor."

This is how it looks like:

 public void Call<T>(string call, Func<string, T> parser, Action<T> onSuccess, Action<string> onFailure) { var handler = MakeHandler(parser, onSuccess, onFailure); StartCoroutine(CallImpl(call, handler)); } public IEnumerator CallImpl<T>(string call, Action<T> handler) { var www = new WWW(call); yield return www; handler(www); } public Action<WWW> MakeHandler<T>(Func<string, T> parser, Action<T> onSuccess, Action<string> onFailure) { return (WWW www) => { if(NoError(www)) { var parsedResult = parser(www.text); onSuccess(parsedResult); } else { onFailure(" "); } } } 

There are several advantages to this approach.


23. If you have a lot of text, put it in a file. Do not put it in the edit fields in the inspector. Make it so that you can change it quickly without opening the Unity editor, and especially without having to save the scene.

24. If you are planning to localize, separate all lines into one place. There are several ways to do this. One of them is to define a Text class with a public string field for each line; by default, for example, English will be set. Other languages ​​will be child classes and reinitialize fields with language equivalents.

A more complicated way (it is suitable for a large amount of text or a high number of languages) is to read a spreadsheet and create logic to select the desired line based on the selected language.

Class design


25. Decide how the inspected fields will be used and make it a standard. There are two ways: make the fields public, or make them private and mark as [SerializeField]. The latter is “more correct”, but less convenient (and this method is not very popularized by Unity itself). Whatever you choose, make it a standard so that the developers on your team know how to interpret the public field.


26. Never make public component variables unless they need to be configured in the inspector. Otherwise they will be changed by the designer, especially if it is not clear what they are doing. In some rare cases, this can not be avoided (for example, if some script editor must use a variable). In this case, you need to use the HideInInspector attribute to hide it in the inspector.

27. Use property drawers to make fields more user friendly. Property drawers can be used to set up controls in the inspector. This will allow you to create the controls that best fit the data view and insert security (for example, limiting the values ​​of variables). Use the Header attribute to organize the fields, and the Tooltip attribute to provide additional documentation to designers.

28. Prefer property drawers, rather than custom editors (custom editors) . Property drawers are implemented by field types, which means they take much less time to implement. They are also more convenient to reuse - after the implementation for the type, they can be used for the same type in any class. Custom editors are implemented in MonoBehaviour, so they are more difficult to reuse and they require more work.

29. By default, “seal” MonoBehaviours (use the sealed modifier). In general, MonoBehaviours Unity is not very convenient for inheritance:


In cases where inheritance is necessary , do not use Unity message methods if this can be avoided. If you still use them, do not make them virtual. If necessary, you can define an empty virtual function called from the message method, which the child class can override (override) to perform additional actions.

 public class MyBaseClass { public sealed void Update() { CustomUpdate(); ... // update   } //  ,      update //     update. virtual public void CustomUpdate(){}; } public class Child : MyBaseClass { override public void CustomUpdate() { // -  } } 

This will prevent your class from accidentally overriding your code, but it still allows you to use Unity messages. I do not like this order, because it becomes problematic. In the example above, the child class may need to perform operations immediately after the class has executed its own update.

30. Separate the interface from the game logic. Interface components as a whole should not know anything about the game in which they are used. Send them the data you want to display, and sign up for events that are checked when the user interacts with the UI components. Interface components do not have to execute game logic. They can filter the input data, checking that it is correct, but the basic rules should not be implemented in them. In many puzzle games, field elements are extensions of the interface, and should not contain rules. (For example, a chess piece should not count the moves allowed for it.)

The information entered must also be separated from the logic acting on this information. Use the input controller to tell the actor about the need for movement, the actor decides when to move.

Here is a stripped-down example of a UI component that allows the user to select a weapon from a given list. The only thing these classes know about the game is the Weapon class (and only because the Weapon class is a useful source of data that this container should display). The game also knows nothing about the container; it only needs to register the OnWeaponSelect event.

 public WeaponSelector : MonoBehaviour { public event Action OnWeaponSelect {add; remove; } //GameManager     public void OnInit(List weapons) { foreach(var weapon in weapons) { var button = ... //        buttonOnInit(weapon, () => OnSelect(weapon)); //    , //        } } public void OnSelect(Weapon weapon) { if(OnWepaonSelect != null) OnWeponSelect(weapon); } } public class WeaponButton : MonoBehaviour { private Action<> onClick; public void OnInit(Weapon weapon, Action onClick) { ... //     this.onClick = onClick; } public void OnClick() //    OnClick  UI Button { Assert.IsTrue(onClick != null); //   onClick(); } } 

31. Separate configuration, status, and supporting information.


By dividing these types of variables, you will understand what can be changed, what needs to be saved, what needs to be sent / received over the network. Here is a simple example of such a separation.

 public class Player { [Serializable] public class PlayerConfigurationData { public float maxHealth; } [Serializable] public class PlayerStateData { public float health; } public PlayerConfigurationData configuration; private PlayerState stateData; //  private float previousHealth; public float Health { public get { return stateData.health; } private set { stateData.health = value; } } } 

32. Do not use index-related public arrays. For example, do not define an array of weapons, an array of bullets and an array of particles as follows:

 public void SelectWeapon(int index) { currentWeaponIndex = index; Player.SwitchWeapon(weapons[currentWeapon]); } public void Shoot() { Fire(bullets[currentWeapon]); FireParticles(particles[currentWeapon]); } 

The problem here is more likely not in the code, but in the complexity of error-free tuning in the inspector.

Better define a class that encapsulates all three variables, and create an array from it:

 [Serializable] public class Weapon { public GameObject prefab; public ParticleSystem particles; public Bullet bullet; } 

This code looks nicer, but more importantly, it’s harder to make mistakes when setting up data in the inspector.

33. Avoid using arrays for structures that are not sequences. For example, a player has three types of attacks. Each uses the current weapon, but generates different bullets and different behavior.

You can try thrusting three bullets into an array, and then use this type of logic:

 public void FireAttack() { ///  Fire(bullets[0]); } public void IceAttack() { ///  Fire(bullets[1]); } public void WindAttack() { ///  Fire(bullets[2]); } 

Enums may look prettier in code ...

 public void WindAttack() { /// behaviour Fire(bullets[WeaponType.Wind]); } 

... but not in the inspector.

It is better to use separate variables so that the names help to understand what content is written there. Create a class so that everything is comfortable.

 [Serializable] public class Bullets { public Bullet fireBullet; public Bullet iceBullet; public Bullet windBullet; } 

This implies that there is no other Fire, Ice or Wind data.

34. Group data into serialized classes so that everything looks more comfortable in the inspector. Some items may have dozens of settings. Finding the right variable can be a nightmare. To simplify your life, follow these instructions:


This will create groups of variables that are collapsed in the inspector that are easier to manage.

 [Serializable] public class MovementProperties // MonoBehaviour! { public float movementSpeed; public float turnSpeed = 1; //    } public class HealthProperties // MonoBehaviour! { public float maxHealth; public float regenerationRate; } public class Player : MonoBehaviour { public MovementProperties movementProeprties; public HealthPorperties healthProeprties; } 

35. Make non-MonoBehaviour classes Serializable, even if they are not used for public fields. This will allow you to view the class fields in the inspector when it is in Debug mode. This also works for nested classes (private or public).

36. Try not to change the data that is configured in the inspector in the code. The variable that is configured in the inspector is a configuration variable, and it should be treated as a constant when the application runs, and not as a state variable. If you follow this rule, it will be easier for you to write methods that reset the state of the component to its original state, and you will more clearly understand what the variable does.

 public class Actor : MonoBehaviour { public float initialHealth = 100; private float currentHealth; public void Start() { ResetState(); } private void Respawn() { ResetState(); } private void ResetState() { currentHealth = initialHealth; } } 

Patterns


Patterns are ways to solve common problems with standard methods. Robert Nystrom 's book “Game Programming Patterns” (you can read it for free online) is a valuable resource for understanding how patterns are applicable to solving problems in game development. In Unity itself, there are many such patterns: Instantiate is an example of a prototype pattern; MonoBehaviour is a version of the “template” pattern (template), the UI and animation use the “observer” pattern, and the new animation engine uses state machines.

These tips relate to the use of patterns specifically in Unity.

37. Use for convenience singletons (loner pattern). The following class will automatically make a singleton any class that inherits it:

 public class Singleton<T> : MonoBehaviour where T : MonoBehaviour { protected static T instance; //   . public static T Instance { get { if(instance == null) { instance = (T) FindObjectOfType(typeof(T)); if (instance == null) { Debug.LogError("    " + typeof(T) + ",   ."); } } return instance; } } } 

Singletons are useful for managers, for example for ParticleManager , AudioManager or GUIManager .

(Many programmers are opposed to classes vaguely called XManager, because it indicates that a bad name was chosen for a class or there are too many tasks unrelated to each other. In general, I agree with them. However, there are only a few managers in games , and they fulfill the same tasks in games, so these classes are actually idioms.)


As explained in other tips, singletones are useful for creating default spawn points and objects passed between scene loads and storing global data.

38. Use state machines to create different behaviors in different states or to execute code when changing states. A lightweight state machine has many states, and for each state you can specify the actions that are performed while entering or staying in the state, as well as the update action. This allows code to be made cleaner and less error prone. A good sign that a state machine is useful to you: the code of the Update method contains an if or switch construct that changes its behavior, or variables such as hasShownGameOverMessage.

 public void Update() { if(health <= 0) { if(!hasShownGameOverMessage) { ShowGameOverMessage(); hasShownGameOverMessage = true; //    false } } else { HandleInput(); } } 

With more states, this type of code can become confusing, the state machine will make it much clearer.

39. Use fields of type UnityEvent to create the observer pattern in the inspector. The UnityEvent class allows you to bind methods that receive up to four parameters in the inspector using the same UI interface as the events in Buttons. This is especially useful when working with input.

40. Use the observer pattern to determine when the field value changes. The problem of code execution only when changing a variable often arises in games. We have created a standard solution to this problem with the help of a generic class that allows registering variable change events. Below is an example of health. Here is how it is created:

 /* */ health = new ObservedValue(100); health.OnValueChanged += () => { if(health.Value <= 0) Die(); }; 

Now you can change it anywhere, without checking its value in every place, for example, like this:

 if(hit) health.Value -= 10; 

When health drops below 0, the Die method is called. For detailed discussions and implementation, see this post .

41. Use the Actor pattern for prefabs. (This is a “non-standard” pattern. The main idea is taken from the presentation of Kieran Lord.)

Actor (actor) is the main component of the prefab. Usually this is the component that provides the “personality” of the prefab, and the one with which higher level code will most often interact. Actor often uses other components — helpers — for the same object (and sometimes for children) to do its work.

When creating a “button” object through the Unity menu, a game object is created with the Sprite and Button components (and a child with the Text component). In this case, the actor will be the Button. The main camera also usually has several components (GUI Layer, Flare Layer, Audio Listener) attached to the Camera component. Camera is an actor here.

Other components may be required for the actor to work properly. You can make a prefab more reliable and useful with the following attributes of the actor component:


 [RequiredComponent(typeof(HelperComponent))] [DisallowMultipleComponent] [SelectionBase] public class Actor : MonoBehaviour { ...// } 

42. Use random and patterned data flow generators. (This is a non-standard pattern, but we consider it extremely useful.)

The generator is similar to a random number generator: it is an object with the Next method called to get a new element of a certain type. In the process of designing, generators can be modified to create a wide range of patterns and various types of randomness. They are useful because they allow you to keep the logic of generating a new element separate from the section of code in which an element is needed, which makes the code much cleaner.

Here are some examples:

 var generator = Generator .RamdomUniformInt(500) .Select(x => 2*x); //    0  998 var generator = Generator .RandomUniformInt(1000) .Where(n => n % 2 == 0); //    var generator = Generator .Iterate(0, 0, (m, n) => m + n); //  var generator = Generator .RandomUniformInt(2) .Select(n => 2*n - 1) .Aggregate((m, n) => m + n); //    1  -1 var generator = Generator .Iterate(0, Generator.RandomUniformInt(4), (m, n) => m + n - 1) .Where(n >= 0); // ,   

We have already used generators to spawn obstacles, change colors of the background, procedural music, generate a sequence of letters to create words, as in word games, and for many more. Using the following design, generators can be successfully used to control corutins repeated at varying intervals:

 while (true) { //-  yield return new WaitForSeconds(timeIntervalGenerator.Next()); } 

Read this post to learn more about generators.

Prefabs and scripted objects


43. Use prefabs for everything. The only game objects in the scene that are not prefabs (or parts of prefabs) should be folders. Even unique objects that are used only once must be prefabs. This makes it easy to make changes that do not require a scene change.

44. Bind prefabs to prefabs; do not bind instances to instances. Links to prefabs are preserved when dragging a prefab into the scene, links to instances are not. Attaching to prefabs, where possible, reduces the cost of setting up the scene and reduces the need to change scenes.

Wherever possible, establish links between instances automatically. If you need to link instances, establish links programmatically. For example, the Player prefab can register itself with the GameManager when it starts, or the GameManager can find the Player prefab when it starts.

45. Do not do grids with prefab roots if you want to add other scripts. When creating a prefab from the grid, first make the grid an empty game object, and let it be the root. Bind the scripts to the root, not to the grid. This will make it easier for you to replace the grid with another grid, without losing the values ​​configured in the inspector.

46. ​​Use scripted objects, but not prefabs for the transmitted configuration data.

If you do this:


47. Use scripted objects for these levels. Level data is often stored in XML or JSON, but using scripted objects instead of them has several advantages:


48. Use scripted objects to configure behavior in the inspector. Scripted objects are usually associated with configuration data, but they also allow you to use “methods” as data.

Consider a scenario in which you have a type of Enemy, and each enemy has some kind of superpowers SuperPowers. You can make them ordinary classes and get them listed in the Enemy class, but without a custom editor you cannot configure a list of different super powers (each with its own properties) in the inspector. But if you make these super powers assets (implement them as ScriptableObjects), then you will succeed!

Here's how it works:

 public class Enemy : MonoBehaviour { public SuperPower superPowers; public UseRandomPower() { superPowers.RandomItem().UsePower(this); } } public class BasePower : ScriptableObject { virtual void UsePower(Enemy self) { } } [CreateAssetMenu("BlowFire", "Blow Fire") public class BlowFire : SuperPower { public strength; override public void UsePower(Enemy self) { ///   blow fire } } 

When using this pattern, one should not forget about the following limitations:


49. Use scripted objects to specialize prefabs. If the configuration of two objects differs only in certain properties, then usually two instances are inserted into the scene and set up these properties in the instances. It is usually better to make a separate property class, which can differ between two types, a separate class of the object being scripted.

This provides more flexibility:


Here is a simple example of such a setting.

 [CreateAssetMenu("HealthProperties.asset", "Health Properties")] public class HealthProperties : ScriptableObject { public float maxHealth; public float resotrationRate; } public class Actor : MonoBehaviour { public HealthProperties healthProperties; } 

With a large number of specializations, you can define a specialization as a normal class, and use their list in a scripted object associated with the appropriate place in which you can apply it (for example, in GameManager ). To ensure its safety, speed and convenience, a little more “glue” is required; Below is an example of the lowest possible use.

 public enum ActorType { Vampire, Wherewolf } [Serializable] public class HealthProperties { public ActorType type; public float maxHealth; public float resotrationRate; } [CreateAssetMenu("ActorSpecialization.asset", "Actor Specialization")] public class ActorSpecialization : ScriptableObject { public List healthProperties; public this[ActorType] { get { return healthProperties.First(p => p.type == type); } // ! } } public class GameManager : Singleton { public ActorSpecialization actorSpecialization; ... } public class Actor : MonoBehaviour { public ActorType type; public float health; //  public Regenerate() { health += GameManager.Instance.actorSpecialization[type].resotrationRate; } } 

50. Use the CreateAssetMenu attribute to automatically add the creation of a ScriptableObject in the Asset / Create menu.

Debugging


51. Unity.


52. IDE. ., , Debugging Unity games in Visual Studio .

53. , . , , . , . , , . Monitor Components , .

54. . , . Editor Console Pro , .

55. Unity, . ., , Unity Test Tools Unit testing at the speed of light with Unity Test Tools .

56. Unity «» . Unity . «» , .

57. , . , , . PlayerPrefs, . , (commit) .

58. . , , . . , . , « » AI- (, , ).

59. , . Examples:


, ; .

60. , . , , . , . , , . ( , ).

61. . , , UV .

Performance


62. .


63. . ; . , .

64. .


65. . Unity : , , . , Asset Store.

66. . , , . , .

67. . Exceptions:



68. . .

, . .


  1. . Bird.
  2. , . , QuetzalcoatisReturn ().
  3. . , . - buttonHolder buttonContainer .
  4. Pascal case, : ComplicatedVerySpecificObject. , , (. « »).
  5. (WIP, final).
  6. : DVamp@W DarkVampire@Walk.
  7. -: Die, DarkVampire@Die, DarkVampire@Death.
  8. : DarkVampire, VampireDark; PauseButton, ButtonPaused. , , Button. [ , . , , . , .]
  9. . , , PathNode0, PathNode1. 0, 1.
  10. , . , Bird0, Bird1, Bird2 Flamingo, Eagle, Swallow.


, «» . For example:


, , Rock_Small, Rock_Large SmallRock, LargeRock.

Structure


, . , .

Folder structure


MyGame
Helper
Design
Scratchpad
Materials
Meshes
Actors
DarkVampire
LightVampire
...
Structures
Buildings
...
Props
Plants
...
...
Resources
Actors
Items
...
Prefabs
Actors
Items
...
Scenes
Menus
Levels
Scripts
Tests
Textures
Ui
Effects
...
Ui
MyLibray
...
Plugins
SomeOtherAsset1
SomeOtherAsset2
...


Main
Debug
Managers
Cameras
Lights
Ui
Canvas
HUD
PauseMenu
...
World
Ground
Props
Structures
...
Gameplay
Actors
Items
...
Dynamic Objects


Debug
Gameplay
Actors
Items
...
Framework
Graphics
Ui
...

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


All Articles