[转]50 Tips for Working with Unity (Best Practices)

About these tips

These tips are not all applicable to every project.

They are based on my experience with projects with small teams from 3 to 20 people. There’s is a price for structure, re-usability, clarity, and so on — team size and project size determine whether that price should be paid.

Many tips are a matter of taste (there may be rivalling but equally good techniques for any tip listed here).

Some tips may fly in the face of conventional Unity development. For instance, using prefabs for specialisation instead of instances is very non-Unity-like, and the price is quite high (many times more prefabs than without it). Yet I have seen these tips pay off, even if they seem crazy.

 

Process

1. Avoid branching assets.(避免资源的分歧,加以相应的前缀加以区分)

    There should always only ever be one version of any asset. If you absolutely have to branch a prefab, scene, or mesh, follow a process that makes it very clear which is the right version. The “wrong” branch should have a funky name, for example, use a double underscore prefix: __MainScene_Backup. Branching prefabs requires a specific process to make it safe (see under the section Prefabs).

    对于项目暂无使用的资源分支加上特别的前缀加以区分,对于prefabs存在的分歧需要一个特殊的处理来使得它可安全使用。

2. Each team member should have a second copy of the project checked out for testing (每

个小组成员都应有一份项目的副本用于测试)

    if you are using version control. After changes, this second copy, the clean copy, should be updated and tested. No-one should make any changes to their clean copies. This is especially useful to catch missing assets.

3. Consider using external level tools for level editing.(考虑使用外部关卡工具设计关卡--TuDee)

    Unity is not the perfect level editor. For example, we have used TuDeeto build levels for a 3D tile-based game, where we could benefit from the tile-friendly tools (snapping to grid, and multiple-of-90-degrees rotation, 2D view, quick tile selection). Instantiating prefabs from an XML file is straightforward. See Guerrilla Tool Developmentfor more ideas.

4. Consider saving levels in XML instead of in scenes.(考虑使用XML保存场景,替换Unity自带的 scene功能)

This is a wonderful technique:

It makes it unnecessary to re-setup each scene.

It makes loading much faster (if most objects are shared between scenes). (读取更快,如果两个场景之间存在许多共享的物体)

It makes it easier to merge scenes (even with Unity’s new text-based scenes there is so much data in there that merging is often impractical in any case). (很容易地实现场景融合) It makes it easier to keep track of data across levels. (便于在各场景中追踪数据)

You can still use Unity as a level editor (although you need not). You will need to write some code to serialize and deserialize your data, and load a level both in the editor and at runtime, and save levels from the editor. You may also need to mimic Unity’s ID system for maintaining references between objects.

5. Consider writing generic custom inspector code. (考虑开发一个自定义的Inspector)

To write custom inspectors is fairly straightforward, but Unity’s system has many drawbacks:

It does not support taking advantage of inheritance.

It does not let you define inspector components on a field-type level, only a class-type level. For instance, if every game object has a field of type SomeCoolType, which you want rendered differently in the inspector, you have to write inspectors for all your classes.

You can address these issues by essentially re-implementing the inspector system. Using a few tricks of reflection, this is not as hard as it seems, details are provided at the end of the article.

Scene Organisation

6. Use named empty game objects as scene folders. (用空的游戏对象作为场景的文件夹)

    Carefully organise your scenes to make it easy to find objects.

7. Put maintenance prefabs and folders (empty game objects) at 0 0 0. (确保prefabs 和 空对象在0,0,0处)

    If a transform is not specifically used to position an object, it should be at the origin. That way, there is less danger of running into problems with local and world space, and code is generally simpler.

8. Minimise using offsets for GUI components. (最小限度使用GUI组件的offset)

    Offsets should always be used to layout components in their parent component only; they should not rely on the positioning of their grandparents. Offsets should not cancel each other out to display correctly. It is basically to prevent this kind of thing:

Parent container arbitrarily placed at (100, -50). Child, meant to be positioned at (10, 10), then placed at (90, 60) [relative to parent].

This error is common when the container is invisible, or does not have a visual representation at all.

9. Put your world floor at y = 0. (放置世界的地板于 y=0 处,这有助于实现一些2D空间的游戏逻辑)

    This makes it easier to put objects on the floor, and treat the world as a 2D space (when appropriate) for game logic, AI, and physics.

10. Make the game runnable from every scene.

This drastically reduces testing time. To make all scenes runnable you need to do two things:

First, provide a way to mock up any data that is required from previously loaded scenes if it is not available.

Second, spawn objects that must persist between scene loads with the following idiom:

myObject = FindMyObjectInScene();

 

if (myObjet == null)

{    myObject = SpawnMyObject();

}

Art

11. Put character and standing object pivots at the base, not in the centre. (确保角色和站立物

体轴心点在脚底,而不是在中心)

    This makes it easy to put characters and objects on the floor precisely. It also makes it easier to work with 3D as if it is 2D for game logic, AI, and even physics when appropriate.

12. Make all meshes face in the same direction (positive or negative z axis).(确保你所有的网格

模型的朝向都是一致的,正负z轴皆可)

    This applies to meshes such as characters and other objects that have a concept of facing direction. Many algorithms are simplified if everything have the same facing direction.

13. Get the scale right from the beginning.(导入模型时,设置好合适的缩放因子)

    Make art so that they can all be imported at a scale factor of 1, and that their transforms can be scaled 1, 1, 1. Use a reference object (a Unity cube) to make scale comparisons easy. Choose a world to Unity units ratio suitable for your game, and stick to it.

14. Make a two-poly plane to use for GUI components and manually created particles. Make the plane face the positive z-axis for easy billboarding and easy GUI building.

15. Make and use test art(使用一些作为测试用的美工素材)

Squares labelled for skyboxes. (贴上标签的skybox)

A grid.

Various flat colours for shader testing: white, black, 50% grey, red, green, blue, magenta,

yellow, cyan. (做shader Testing时使用各种各样的单调色进行测试)

Gradients for shader testing: black to white, red to green, red to blue, green to blue. (渐变

测试建议使用:黑to白,红to绿,红to蓝,绿to蓝)

Black and white checkerboard. (黑白相间的棋盘格) Smooth and rugged normal maps.

A lighting rig (as prefab) for quickly setting up test scenes. (制作一套照明设备)

Prefabs

16. Use prefabs for everything.(所有物体都应使用Prefabs,除了那些作为目录的空对象)

    The only game objects in your scene that should not be prefabs should be folders. Even unique objects that are used only once should be prefabs. This makes it easier to make changes that don’t require the scene to change. (An additional benefit is that it makes building sprite atlases reliable when using EZGUI).

17. Use separate prefabs for specialisation; do not specialise instances. (对Prefabs做特化处

理,而不要对实例做特化处理)

If you have two enemy types, and they only differ by their properties, make separate prefabs for the properties, and link them in. This makes it possible to

make changes to each type in one place make changes without having to change the scene.

    If you have too many enemy types, specialisation should still not be made in instances in the editor. One alternative is to do it procedurally, or using a central file / prefab for all enemies. A

single drop down could be used to differentiate enemies, or an algorithm based on enemy position or player progress.(另一种方法是用基于算法来生成不同的敌人类型)

18. Link prefabs to prefabs; do not link instances to instances.(建立预置物与预置物之间的联

系,而不是建立实例对象之间的联系)

     Links to prefabs are maintained when dropping a prefab into a scene; links to instances are not. Linking to prefabs whenever possible reduces scene setup, and reduce the need to change scenes.

19. As far as possible, establish links between instances automatically.

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

Don’t put meshes at the roots of prefabs if you want to add other scripts.(如果你想加入其它脚本,不要把模型网格放到预置物的根部,这样可以随时改变模型而不需修改其他东西)

    When you make the prefab from a mesh, first parent the mesh to an empty game object, and make that the root. Put scripts on the root, not on the mesh node. That way it is much easier to replace the mesh with another mesh without loosing any values that you set up in the inspector.

Use linked prefabs as an alternative to nested prefabs.

    Unity does not support nested prefabs, and existing third-party solutions can be dangerous when working in a team because the relationship between nested prefabs is not obvious.

20. Use safe processes to branch prefabs.(用安全的处理方式更改Prefabs) The explanation use the Player prefab as an example.

Make a risky change to the Player prefab is as follows:(单人负责的任务不必考虑,以下做法多人合作时存在风险)

  1. Duplicate the Player prefab.
  2. Rename the duplicate to __Player_Backup.
  3. Make changes to the Player prefab.
  4. If everything works, delete __Player_Backup.

Do not name the duplicate Player_New, and make changes to it!

Some situations are more complicated. For example, a certain change may involve two people, and following the above process may break the working scene for everyone until person two finished. If it is quick enough, still follow the process above. For changes that take longer, the

following process can be followed:(以下做法可以规避多人合作时,带来的不可以预料的风险)

  1. Person 1:
    1. Duplicate the Player prefab.
    2. Rename it to __Player_WithNewFeature or __Player_ForPerson2.
    3. Make changes on the duplicate, and commit / give to Person 2.
  2. Person 2:
    1. Make changes to new prefab.
    2. Duplicate Player prefab, and call it __Player_Backup.
    3. Drag an instance of __Player_WithNewFeature into the scene.
    4. Drag the instance onto the original Player prefab.
    5. If everything works, delete __Player_Backup and __Player_WithNewFeature.

Extensions and MonoBehaviourBase(对MonoBehaviour类进行扩展)

21. Extend your own base mono behaviour, and derive all your components from it.(控制你自己的mono behaviour类,让所有组件都继承它,而不去使用系统默认的)

This allows you to implement some general functionality, such as type safe Invoke, and more complicated Invokes (such as random, etc.).

22. Define safe methods for Invoke, StartCoroutine and Instantiate.(定义一个类型安全的 Invoke方法,以delegate为参数,而不是以String作为参数)

Define a delegate Task, and use it to define methods that don’t rely on string names. For example:

public void Invoke(Task task, float time)

{

   Invoke(task.Method.Name, time);

}

23. Use extensions to work with components that share an interface. (用意不太明确,大概是

方便获取场景上所有对象带有“ I 组件”的方法吧)

It is sometimes convenient to get components that implement a certain interface, or find objects with such components.

The implementations below uses typeof instead of the generic versions of these functions. The generic versions don’t work with interfaces, but typeof does. The methods below wraps this neatly in generic methods.

//Defined in the common base class for all mono behaviours public I GetInterfaceComponent<I>() where I : class

{

   return GetComponent(typeof(I)) as I;

}  

public static List<I> FindObjectsOfInterface<I>() where I : class {

   MonoBehaviour[] monoBehaviours = FindObjectsOfType<MonoBehaviour>();

   List<I> list = new List<I>();

 

   foreach(MonoBehaviour behaviour in monoBehaviours)

   {

      I component = behaviour.GetComponent(typeof(I)) as I;

 

      if(component != null)

      {

         list.Add(component);

      }

   }  

   return list;

}

24. Use extensions to make syntax more convenient.(定义静态方法来简化复杂的语法,即工具类)

For example:

public static class CSTransform

{

   public static void SetX(this Transform transform, float x)

   {

      Vector3 newPosition =

         new Vector3(x, transform.position.y, transform.position.z);

       transform.position = newPosition;    }    ... }

25. Use a defensive GetComponent alternative. (定义一个带防御性的GetComponent方法,这样

可以提醒我们指定的GameObject上是否存在相应的组件)

Sometimes forcing component dependencies (through RequiredComponent) can be a pain. For example, it makes it difficult to change components in the inspector (even if they have the same base type). As an alternative, the following extension of GameObject can be used when a component is required to print out an error message when it is not found.

public static T GetSafeComponent<T>(this GameObject obj) where T : MonoBehaviour {

   T component = obj.GetComponent<T>();

 

   if(component == null)

   {

      Debug.LogError("Expected to find component of type "

         + typeof(T) + " but found none", obj);

   }  

   return component;

}

Idioms(风格)

26. Avoid using different idioms to do the same thing.(避免用不同的风格完成相同的任务)

In many cases there are more than one idiomatic way to do things. In such cases, choose one to use throughout the project. Here is why:

Some idioms don’t work well together. Using one idiom well forces design in one direction that is not suitable for another idiom.

Using the same idiom throughout makes it easier for team members to understand what is going on. It makes structure and code easier to understand. It makes mistakes harder to make.

Examples of idiom groups:

Coroutines vs. state machines. (协程 vs. 状态机)

Nested prefabs vs. linked prefabs vs. God prefabs.

Data separation strategies. (数据分离策略)

Ways of using sprites for states in 2D games.

Prefab structure. (Prefab结构)

Spawning strategies. (生成策略)

Ways to locate objects: by type vs. name vs. tag vs. layer vs. reference (“links”). (如何定位对象:type | name | tag | layer | reference)

Ways to group objects: by type vs. name vs. tag vs. layer vs. arrays of references (“links”).

(如何打组对象:type | name | tag | layer | reference)

Finding groups of objects versus self registration. (通过Find方法获取对象数组 | 通过注册方式得到对象数组)

Controlling execution order (Using Unity’s execution order setup versus yield logic versus Awake / Start and Update / Late Update reliance versus manual methods versus any-order architecture). (控制执行顺序)

 Selecting objects / positions / targets with the mouse in-game: selection manager versus local self-management. (如何管理游戏中object | position | target 的选取,selectionManager or 自身管理)

 Keeping data between scene changes: through PlayerPrefs, or objects that are not

Destroyed when a new scene is loaded. (如何实现场景切换的数据持久化,PlayerPrefs | XML | 全局对象)

 Ways of combining (blending, adding and layering) animation. (动画的结合方式)

Time

27. Maintain your own time class to make pausing easier. (建立自己的时间类,有助于更好地实

现暂停,比如:GUI动画 与 游戏内部动画)

Wrap Time.DeltaTime and Time.TimeSinceLevelLoad to account for pausing and time scale. It requires discipline to use it, but will make things a lot easier, especially when running things of different clocks (such as interface animations and game animations).

Spawning Objects

28. Don’t let spawned objects clutter your hierarchy when the game runs. (当游戏运行时不

要让自动生成的对象扰乱你的场景层次结构)

Set their parents to a scene object to make it easier to find stuff when the game is running. You could use a empty game object, or even a singleton with no behaviour to make it easier to access from code. Call this object DynamicObjects.

Class Design

29. Use singletons for convenience. (定义了单例基类,所有类继承它都可以实现单例模式)

The following class will make any class that inherits from it a singleton automatically:

public class Singleton<T> : MonoBehaviour where T : MonoBehaviour

{

   protected static T instance;

 

   /**

      Returns the instance of this singleton.

   */    public static T Instance

   {       get       {          if(instance == null)

         {             instance = (T) FindObjectOfType(typeof(T));

 

            if (instance == null)

            {

               Debug.LogError("An instance of " + typeof(T) +

                  " is needed in the scene, but there is none.");

            }

         }           return instance;

      }

   }

}

Singletons are useful for managers, such as ParticleManager or AudioManager or GUIManager.

(建议所有的管理者类都以单例模式定义,比如:GUIManager、AudioManager)

 Avoid using singletons for unique instances of prefabs that are not managers (such as the Player). Not adhering to this principle complicates inheritance hierarchies, and makes certain types of changes harder. Rather keep references to these in your GameManager (or other suitable God class  ) (对于不是管理者类避免使用单例模式)

 Define static properties and methods for public variables and methods that are used often from outside the class. This allows you to write GameManager.Player instead of GameManager.Instance.player. (对于经常要被其他类访问的公共方法和变量,可以定义为静态方法和属性)

30. For components, never make variables public that should not be tweaked in the

inspector. (对于组件来说,对不做修改的变量不要设置为public,如果逼不得已就加上前缀加以警告,预防无意的修改)

Otherwise it will be tweaked by a designer, especially if it is not clear what it does. In some rare cases it is unavoidable. In that case use a two or even four underscores to prefix the variable name to scare away tweakers: public float __aVariable;

31. Separate interface from game logic. (从游戏逻辑中分离出界面)

This is essentially the MVC pattern. (MVC模式,不懂百度下)

Any input controller should only give commands to the appropriate components to let them know the controller has been invoked. For example in controller logic, the controller could decide which commands to give based on the player state. But this is bad (for example, it will lead to duplicate logic if more controllers are added). Instead, the Player object should be notified of the intent of moving forward, and then based on the current state (slowed or stunned, for example) set the speed and update the player facing direction. Controllers should only do things that relate to their own state (the controller does not change state if the player changes state; therefore, the controller should not know of the player state at all). Another example is the changing of weapons. The right way to do it is with a method on Player SwitchWeapon(Weapon newWeapon), which the GUI can call. The GUI should not manipulate transforms and parents and all that stuff.

在控制器逻辑中,控制器通常基于player状态给出一些指令,但是这样做是不好的(如果添加多个控制器的话,这样会导致复制相同的逻辑代码)。取而代之的是,玩家对象被通知要向前移动,然后基于当前状态(减速或震惊)来设置速度和更新玩家面向的方向。控制器只根据它们自己的状态来做事。另外一个例子就是更换屋企,正确的做法是在玩家对象上加入SwitchWeapon(Weapon newWeapon)。

Any interface component should only maintain data and do processing related to it’s own state. For example, do display a map, the GUI could compute what to display based on the

player’s movements. However, this is game state data, and does not belong in the GUI. The GUI should merely display game state data, which should be maintained elsewhere. The map data should be maintained elsewhere (in the GameManager, for example).

所有的界面组件都只依据自己的状态维持数据和处理相应事物。比如:显示一幅地图,GUI需要基于玩家的移动计算出显示什么。然而,这些游戏状态数据是不属于GUI的,GUI的工作只是显示游戏数据,数据的维护工作一个放到别处。一般来说这些地图数据的维护工作在 GameManager 类似的God Class中。

Gameplay objects should know virtually nothing of the GUI. The one exception is the pause behaviour, which is may be controlled globally through Time.timeScale (which is not a good idea as well… see ). Gameplay objects should know if the game is paused. But that is all. Therefore, no links to GUI components from gameplay objects.

GamePlay对象应知道几乎没有GUI的存在。一个例外就是暂停操作,这个必须让游戏对象知道。

In general, if you delete all the GUI classes, the game should still compile.

You should also be able to re-implement the GUI and input without needing to write any new game logic.

总的来说,如果你删除了所有的GUI类,游戏依旧可以编译,那你就可以重实现GUI和输入操作,而不需要再编写新的游戏逻辑控制代码了。

32. Separate state and bookkeeping. (分离状态与记录)

Bookkeeping variables are used for speed or convenience, and can be recovered from the state. By separating these, you make it easier to

save the game state, and debug the game state.

One way to do it is to define a SaveData class for each game logic class. The

[Serializable] PlayerSaveData

{

   public float health; //public for serialisation, not exposed in inspector

}

 

Player

{

   //... bookkeeping variables

 

   //Don’t expose state in inspector. State is not tweakable.

   private PlayerSaveData playerSaveData; }

33. Separate specialisation configuration.(分离特化的配置信息,比如:相同的外形的敌人,不同的体型和速度,应该把这些数据分离出来,定义成不同EnemyTemplate)

Consider two enemies with identical meshes, but different tweakables (for instance different strengths and different speeds). There are different ways to separate data. The one here is what I prefer, especially when objects are spawned, or the game is saved. (Tweakables are not state data, but configuration data, so it need not be saved. When objects are loaded or spawned, the tweakables are automatically loaded in separately)

Define a template class for each game logic class. For instance, for Enemy, we also define

EnemyTemplate. All the differentiating tweakables are stored in EnemyTemplate In the game logic class, define a variable of the template type.

Make an Enemy prefab, and two template prefabs WeakEnemyTemplate and

StrongEnemyTemplate.

When loading or spawning objects, set the template variable to the right template.

This method can become quite sophisticated (and sometimes, needlessly complicated, so beware!).

For example, to better make use of generic polymorphism, we may define our classes like this:

(游戏实体类设计)

public class BaseTemplate {

   ...

}  

public class ActorTemplate : BaseTemplate {    ...

}  

public class Entity<EntityTemplateType> where EntityTemplateType : BaseTemplate {

   EntityTemplateType template;    ...

}  public class Actor : Entity <ActorTemplate> {    ...

}

34. Don’t use strings for anything other than displayed text.

 In particular, do not use strings for identifying objects or prefabs etc. One unfortunate exception is animations, which generally are accessed with their string names.

35. Avoid using public index-coupled arrays.(避免使用公共索引值的数组)

 For instance, do not define an array of weapons, an array of bullets, and an array of particles , so that your code looks like this:

public void SelectWeapon(int index)

{

   currentWeaponIndex = index;

   Player.SwitchWeapon(weapons[currentWeapon]);

}  

public void Shoot()

{

   Fire(bullets[currentWeapon]);

   FireParticles(particles[currentWeapon]);   }

The problem for this is not so much in the code, but rather setting it up in the inspector without making mistakes.

Rather, define a class that encapsulates the three variables, and make an array of that:(将相关联

的部件压缩成一个类来使用)

[Serializable] public class Weapon

{    public GameObject prefab;    public ParticleSystem particles;    public Bullet bullet;

}

The code looks neater, but most importantly, it is harder to make mistakes in setting up the data in the inspector.

36. Avoid using arrays for structure other than sequences. (避免使用数组结构,尝试定义合适的类来取代使用数组结构)

For example, a player may have three types of attacks. Each uses the current weapon, but generates different bullets and different behaviour.

You may be tempted to dump the three bullets in an array, and then use this kind of logic:

public void FireAttack()

{

   /// behaviour

   Fire(bullets[0]);

}  

public void IceAttack()

{

   /// behaviour

   Fire(bullets[1]);

}  

public void WindAttack()

{

   /// behaviour

   Fire(bullets[2]);

}

Enums can make things look better in code…

public void WindAttack()

{

   /// behaviour

   Fire(bullets[WeaponType.Wind]);

}

…but not in the inspector.

It’s better to use separate variables so that the names help show which content to put in. Use a class to make it neat.

[Serializable] public class Bullets

{

   public Bullet FireBullet;    public Bullet IceBullet;    public Bullet WindBullet;

}

This assumes there is no other Fire, Ice and Wind data.

37. Group data in serializable classes to make things neater in the inspector. (组织数据放到可

序列化的类中,可以使得在Inspector面板中更有条理)

Some entities may have dozens of tweakables. It can become a nightmare to find the right variable in the inspector. To make things easier, follow these steps:

Define separate classes for groups of variables. Make them public and serializable.

In the primary class, define public variables of each type defined as above.

Do not initialize these variables in Awake or Start; since they are serializable, Unity will take

care of that. (不要在Awake或Start中初始化这些变量)

 You can specify defaults as before by assigning values in the definition;

This will group variables in collapsible units in the inspector, which is easier to manage.

[Serializable] public class MovementProperties //Not a MonoBehaviour!

{

   public float movementSpeed;

   public float turnSpeed = 1; //default provided

}  public class HealthProperties //Not a MonoBehaviour!

{    public float maxHealth;    public float regenerationRate;

}  

public class Player : MonoBehaviour

{    public MovementProperties movementProeprties;    public HealthPorperties healthProeprties; }

Text

38. If you have a lot of story text, put it in a file.

Don’t put it in fields for editing in the inspector. Make it easy to change without having to open the Unity editor, and especially without having to save the scene.

39. If you plan to localise, separate all your strings to one location. (如果你计划进行本地化处

理,抽离所有你用到的字符串到一个位置上)

简单做法定义一个Text类收集所有的字符串字段,然后分别定义各种语言子类,初始化时确定初始化那种语言子类。

更复杂的方法是(面对大量文本内容和多种类的语言)读取一个棋盘格对照表,提供一些逻辑方法来选择指定的string。

There are many ways to do this. One way is to define a Text class with a public string field for each string, with defaults set to English, for example. Other languages subclass this and reinitialize the fields with the language equivalents.

More sophisticated techniques (appropriate when the body of text is large and / or the number of languages is high) will read in a spread sheet and provide logic for selecting the right string based on the chosen language.

Testing and Debugging

40. Implement a graphical logger to debug physics, animation, and AI.

    This can make debugging considerably faster. See here.

41. Implement a HTML logger.

    In some cases, logging can still be useful. Having logs that are easier to parse (are colour coded, has multiple views, records screenshots) can make log-debugging much more pleasant. See here.

42. Implement your own FPS counter. (实现一个自己的FPS计数器,因为没人知道Unity自带的FPS 是否正确)

    Yup. No one knows what Unity’s FPS counter really measures, but it is not frame rate.

Implement your own so that the number can correspond with intuition and visual inspection.

43. Implement shortcuts for taking screen shots.

    Many bugs are visual, and are much easier to report when you can take a picture. The ideal system should maintain a counter in PlayerPrefs so that successive screenshots are not overwritten. The screenshots should be saved outside the project folder to avoid people from accidentally committing them to the repository.

44. Implement shortcuts for printing the player’s world position. (实现一个快捷键来输出 player的世界位置)

    This makes it easy to report the position of bugs that occur in specific places in the world, which in turns makes it easier to debug.

45. Implement debug options for making testing easier. (实现一些debug用的选项有助于更容易地调试)

Some examples:

Unlock all items.

Disable enemies. (禁用敌人)

Disable GUI. (禁用GUI)

Make player invincible. (玩家无敌)

Disable all gameplay. (禁用所有与游戏玩法相关的对象)

46. For teams that are small enough, make a prefab for each team member with debug options.

Put a user identifier in a file that is not committed, and is read when the game is run. This why:

Team members do not commit their debug options by accident and affect everyone.

Changing debug options don’t change the scene.

47. Maintain a scene with all gameplay elements.

    For instance, a scene with all enemies, all objects you can interact with, etc. This makes it easy to test functionality without having to play too long.

48. Define constants for debug shortcut keys, and keep them in one place.

    Debug keys are not normally (or conveniently) processed in a single location like the rest of the game input. To avoid shortcut key collisions, define constants in a central place. An alternative is to process all keys in one place regardless of whether it is a debug function or not.

(The downside is that this class may need extra references to objects just for this).

Documentation

49. Document your setup.

Most documentation should be in the code, but certain things should be documented outside code. Making designers sift through code for setup is time-wasting. Documented setups improved efficiency (if the documents are current).

Document the following:

Layer uses (for collision, culling, and raycasting – essentially, what should be in what layer).

Tag uses.

GUI depths for layers (what should display over what).

Scene setup.

Idiom preferences.

Prefab structure.

Animation layers.

Naming Standard and Folder Structure(命名标准和目录结构)

50. Follow a documented naming convention and folder structure.

Consistent naming and folder structure makes it easier to find things, and to figure out what things are.

You will most probably want to create your own naming convention and folder structure. Here is one as an example.

Naming General Principles(命名规则)

  1. Call a thing what it is. A bird should be called Bird.
  2. Choose names that can be pronounced and remembered. If you make a Mayan game, do not name your level QuetzalcoatisReturn.
  3. Be consistent. When you choose a name, stick to it.
  4. Use Pascal case, like this: ComplicatedVerySpecificObject. Do not use spaces, underscores, or hyphens, with one exception (see Naming Different Aspects of the Same Thing).
  5. Do not use version numbers, or words to indicate their progress (WIP, final).
  6. Do not use abbreviations: DVamp@W should be DarkVampire@Walk.
  7. Use the terminology in the design document: if the document calls the die animation Die, then use DarkVampire@Die, not DarkVampire@Death.
  8. Keep the most specific descriptor on the left: DarkVampire, not VampireDark; PauseButton, not ButtonPaused. It is, for instance, easier to find the pause button in the inspector if not all buttons start with the word Button. [Many people prefer it the other way around, because that makes grouping more obvious visually. Names are not for grouping though, folders are. Names are to distinguish objects of the same type so that they can be located reliably and fast.]
  9. Some names form a sequence. Use numbers in these names, for example, PathNode0, PathNode1. Always start with 0, not 1.
  10. Do not use numbers for things that don’t form a sequence. For example, Bird0, Bird1,

Bird2 should be Flamingo, Eagle, Swallow.

  1. Prefix temporary objects with a double underscore __Player_Backup.

Naming Different Aspects of the Same Thing(命名不同方面的同一事物)

Use underscores between the core name, and the thing that describes the “aspect”. For instance:

GUI buttons states EnterButton_Active, EnterButton_Inactive

Textures DarkVampire_Diffuse, DarkVampire_Normalmap

Skybox JungleSky_Top, JungleSky_North

LOD Groups DarkVampire_LOD0, DarkVampire_LOD1

Do not use this convention just to distinguish between different types of items, for instance Rock_Small, Rock_Large should be SmallRock, LargeRock.

Structure

The organisation of your scenes, project folder, and script folder should follow a similar pattern.

Folder Structure

Materials

GUI

Effects

Meshes

   Actors

      DarkVampire       LightVampire       ...

   Structures       Buildings       ...

   Props

      Plants       ...    ...

Plugins

Prefabs

   Actors    Items    ...

Resources

   Actors    Items    ...

Scenes

   GUI

   Levels

   TestScenes

Scripts

Textures

GUI

Effects ... Scene Structure

Cameras

Dynamic Objects

Gameplay

   Actors    Items    ... GUI

   HUD

   PauseMenu    ...

Management

Lights

World

   Ground

   Props

   Structure    ... Scripts Folder Structure

ThirdParty    ...

MyGenericScripts

   Debug

   Extensions

   Framework

   Graphics    IO

   Math    ...

MyGameScripts

   Debug

   Gameplay

      Actors       Items       ...

   Framework

   Graphics

   GUI    ...

How to Re-implement Inspector Drawing(如何重新实现Inspector 的绘制)

1. Define a base class for all your editors

BaseEditor<T> : Editor where T : MonoBehaviour

{    override public void OnInspectorGUI()

   {

      T data = (T) target;

 

      GUIContent label = new GUIContent();       label.text = "Properties"; //

 

      DrawDefaultInspectors(label, data);

 

      if(GUI.changed)

      {        

         EditorUtility.SetDirty(target);

      }

   }

}

2. Use reflection and recursion to do draw components(利用反射与递归)

public static void DrawDefaultInspectors<T>(GUIContent label, T target)    where T : new()

{

   EditorGUILayout.Separator();

   Type type = typeof(T);     

   FieldInfo[] fields = type.GetFields();

   EditorGUI.indentLevel++;

 

   foreach(FieldInfo field in fields)

   {

      if(field.IsPublic)

      {          if(field.FieldType == typeof(int))

         {

            field.SetValue(target, EditorGUILayout.IntField(             MakeLabel(field), (int) field.GetValue(target)));

         }            else if(field.FieldType == typeof(float))

         {             field.SetValue(target, EditorGUILayout.FloatField(             MakeLabel(field), (float) field.GetValue(target)));

         }

 

         ///etc. for other primitive types

 

         else if(field.FieldType.IsClass)

         {

            Type[] parmTypes = new Type[]{ field.FieldType};

 

            string methodName = "DrawDefaultInspectors";

 

            MethodInfo drawMethod =

               typeof(CSEditorGUILayout).GetMethod(methodName);

 

            if(drawMethod == null)

            {

               Debug.LogError("No method found: " + methodName);

            }

 

            bool foldOut = true;

 

            drawMethod.MakeGenericMethod(parmTypes).Invoke(null,                new object[]

               {

                  MakeLabel(field),                   field.GetValue(target)

               });          }               else

         {

            Debug.LogError(

               "DrawDefaultInspectors does not support fields of type " +                field.FieldType);

         }

      }        

   }

 

   EditorGUI.indentLevel--;

}

The above method uses the following helper:

private static GUIContent MakeLabel(FieldInfo field)

{

   GUIContent guiContent = new GUIContent();         guiContent.text = field.Name.SplitCamelCase();         object[] descriptions =

      field.GetCustomAttributes(typeof(DescriptionAttribute), true);

 

   if(descriptions.Length > 0)

   {

      //just use the first one.       guiContent.tooltip =

         (descriptions[0] as DescriptionAttribute).Description;

   }  

   return guiContent;

}

Note that it uses an annotation in your class code to generate a tooltip in the inspector.

3. Define new Custom Editors

Unfortunately, you will still need to define a class for each MonoBehaviour. Fortunately, these definitions can be empty; all the actual work is done by the base class.

[CustomEditor(typeof(MyClass))]

public class MyClassEditor : BaseEditor<MyClass>

{}

In theory this step can be automated, but I have not tried it.

转载于:https://www.cnblogs.com/Firepad-magic/p/6442852.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值