有用的Unity社区贴

MonoDevelop调试

Debug.Log (or print)是一般的调试手段,用来查看变量的状态,但万一输出的多了,它可是都写入硬盘的,随着时间它会逐步影响程序的性能,造成之后的程序调试会不准确,所以正规的调试还是推荐使用MonoDevelop Debugger,类似于VS的断点调试。

有用的Unity社区贴_第1张图片

图像左上角就是调试开始的按钮,在中间红色框中左键确定断点后,先点击开始MonoDevelop的调试,在到Unity中点击执行,便会自动跳回到MonoDevelop中的断点位置中,我们也可以在下方的Debug界面查看到变量的状态。

有用的Unity社区贴_第2张图片

点击上图红色箭头按钮可以进行单步调试,下方可以自行添加需要查看的变量

右键断点,我们还可以选择BreakPoint Properties,里面可以设置断点的属性,例如到这个断点就打印一条消息,判断值大小等等,如下图

有用的Unity社区贴_第3张图片

Unity中良好的编程实践

对很多Unity开发者来说,这可能不算什么,但是这确实是非常重要的话题,保持这些习惯,能使你编程更加简单。

单一职责原则

简单来说,就是一个类只负责一个任务。
每个类都有自己的小小任务,你可以通过结合这些小任务来解决大目标。如果这听起来我像在描述一个组件,这是因为两者配合起来特别完美。

举个例子,我们有一个Player类。Player处理输入,物理特性,武器,血量,财产。

Player有太多需要负责,我们应该打破它成多个组件,每个组件做一件事。
- PlayerInput - 负责处理输入
- PlayerPhysics - 负责驱动物理模拟
- WeaponManager - 负责管理玩家武器
- Health - 负责玩家健康度
- PlayerInventory - 负责玩家的财产

哈,这样就好多了。现在,这些类相互依赖,但这还会导致一些问题,让我们看看后面如何解决。

依赖倒置原则

如果一个类依赖其他类,使依赖成为抽象依赖。

依赖倒置是当一个类调用其他类,我们应该用一些抽象来替代,这样能较好地隔离其他类。最好的就是接口。所以A类调用B类的方法,实际调用的是ISomeInterface接口的方法,方法由B类实现。

另一种是B类继承的抽象基类,A类作为B类的基类而不是A类直接调用B类。

当然最不可取的依赖就是A类直接依赖于B类,这是我们需要避免的。

所以对于我们之前的例子,这些组件可能有相当紧密的耦合。PlayerInput可能依赖于PlayerPhysics的存在,WeaponManager可能依赖于Inventory的存在,等等。

让我们抽象他们:
- IActorPhysics - 接收输入和物理模拟效果的类的接口
- IDamageable - 接收伤害的类的接口
- IInventory - 储存和检索物品的类的接口

现在我们的PlayerPhysics实现IActorPhysics,我们的Health实现IDamageable,我们的PlayInventory实现IInventory。

所以PlayerInput只依赖于一些IActorPhysics接口,WeaponManager依赖于一些IInventory接口,可能一些处理对玩家的伤害依赖于一些IDamageable接口。

依赖倒置加强了独立责任。PlayerInput不应该关心玩家如何移动,也不应该关心什么移动了玩家。

在一些情况下,SendMessage也能完成同意的目标。

模块化

在很多Unity中看过这样的代码:

switch( behavior )
{
    case 1: // some behavior
    case 2: // another behavior
    case 3: // yet another behavior
    case 4: // the last behavior
}

它确实正常运行,但我们能做的更好。这段代码实际上是可以模块化的。每个行为都是一个模块。“behavior”应该是一个实例(假设我们的模块都是一个委托,即使可能只是一个类或者结构体),我们的代码可以是这样:

behavior();

然后你可以像这样分配现有的模块:

MyClass.behavior = someBehavior;

someBehavior可能是一个函数,或者我们的模块是一个类,一个类实例或者其他。

这是非常有用的,特别是应用在状态机和菜单系统中。当增加或减少新的状态或者菜单时,我们就没必要每次扩展我们的switch语句添加我们想要的东西。

Unity编辑器扩展 - 菜单项

Unity编辑器允许添加自定义菜单,而看起来像是内建的菜单。对于添加需要经常通过编辑器界面频繁直接访问的通用功能来说,这是非常有用的。接下来就会描述如何一个新的菜单添加到Unity编辑器中,尝试提供一个真实生活中的例子。

添加菜单项

为了在顶部工具栏添加新的菜单,你应该创建一个editor脚本(脚本放在Editor文件夹中的任何位置)。菜单项在脚本中作为静态方法创建。

比如,添加一个新的“Tools”菜单,提供一些选项,以下是添加一个新的工具菜单,附带一个选项(清除所有PlayerPrefs数据):

using UnityEngine;
using UnityEditor;

public class MenuItems
{
    [MenuItem("Tools/Clear PlayerPrefs")]
    private static void NewMenuOption()
    {
        PlayerPrefs.DeleteAll();
    }
}

这创建了一个新的叫做Tools的编辑器菜单,菜单中的选项叫做Clear PlayerPrefs:
添加Tools菜单

当然也可以在已存在的菜单下创建菜单项,也能为更好的结构和组织创建多级菜单:

using UnityEngine;
using UnityEditor;

public class MenuItemsExample
{
    // Add a new menu item under an existing menu

    [MenuItem("Window/New Option")]
    private static void NewMenuOption()
    {
    }

    // Add a menu item with multiple levels of nesting

    [MenuItem("Tools/SubMenu/Option")]
    private static void NewNestedOption()
    {
    }
}

结果如下:
有用的Unity社区贴_第4张图片

热键

为了使代码老手和键盘党工作更快,新的菜单项可以分配热键 - 快捷组合键将自动启动。

这里有一些支持的健(也可以组合):
- % - CTRL on Window/CMD on OSX
- # - Shift
- & - Alt
- LEFT/RIGHT/UP/DOWN - Arrow keys
- F1 … F2 - F keys
- HOME, END, PGUP, PGDN

字符键不是键序列的一部分,添加的话需要前面加一个下划线(如_g就是热键“G”)

热键组合放在菜单项之后,并以空格隔开,例:

// Add a new menu item with hotkey CTRL-SHIFT-A

[MenuItem("Tools/New Option %#a")]
private static void NewMenuOption()
{
}

// Add a new menu item with hotkey CTRL-G

[MenuItem("Tools/Item %g")]
private static void NewNestedOption()
{
}

// Add a new menu item with hotkey G
[MenuItem("Tools/Item2 _g")]
private static void NewOptionWithHotkey()
{
}

菜单项与热线会以组合的形式展现,比如上图的代码会产生这样的菜单:
有用的Unity社区贴_第5张图片

注意:系统没有验证重复的热键!定义多个相同热键的菜单项,只有一个菜单项会响应。

特殊的路径

如所见,菜单属性控制顶级菜单。
Unity也有一些特殊的路径表现菜单内容(能直接通过右键访问)

  • Assets - 目录在Assets菜单下是有效的,在项目视图中右键也可以看到
  • Assets/Create - 当在项目视图中点击“Create”,目录将会展示出来
  • CONTEXT/ComponentName - 右键组件的inspector界面,目录也是可以看到

以下是一些例子:

// Add a new menu item that is accessed by right-clicking on an asset in the project view

[MenuItem("Assets/Load Additive Scene")]
private static void LoadAdditiveScene()
{
    var selected = Selection.activeObject;
    EditorApplication.OpenSceneAdditive(AssetDatabase.GetAssetPath(selected));
}

// Adding a new menu item under Assets/Create

[MenuItem("Assets/Create/Add Configuration")]
private static void AddConfig()
{
    // Create and add a new ScriptableObject for storing configuration
}

// Add a new menu item that is accessed by right-clicking inside the RigidBody component

[MenuItem("CONTEXT/Rigidbody/New Option")]
private static void NewOpenForRigidBody()
{
}

代码结果如下:
有用的Unity社区贴_第6张图片

有用的Unity社区贴_第7张图片

有用的Unity社区贴_第8张图片

验证

一些菜单项只在一些特定的场景下有意义,有些时候不应该有效。通过添加validation方法,根据使用场景有效/无效菜单项。

validation方法是静态方法,标记菜单项属性,传递true验证参数。

validation方法应该有相同的菜单路径,应该返回一个boolean值确定菜单想是否激活。

比如:

[MenuItem("Assets/ProcessTexture")]
private static void DoSomethingWithTexture()
{
}

// Note that we pass the same path, and also pass "true" to the second argument.
[MenuItem("Assets/ProcessTexture", true)]
private static bool NewMenuOptionValidation()
{
    // This returns true when the selected object is a Texture2D (the menu item will be disabled otherwise).
    return Selection.activeObject.GetType() == typeof(Texture2D);
}

当右键任何东西,不是texture类型,菜单项就不会被激活。

利用优先级排列顺序

优先级是一个数字,能分配到菜单项,通过MenuIten参数传递,来控制菜单项在根菜单的顺序。

菜单项也根据分配的数字自动分组(增量为50):

[MenuItem("NewMenu/Option1", false, 1)]
private static void NewMenuOption()
{
}

[MenuItem("NewMenu/Option2", false, 2)]
private static void NewMenuOption2()
{
}

[MenuItem("NewMenu/Option3", false, 3)]
private static void NewMenuOption3()
{
}

[MenuItem("NewMenu/Option4", false, 51)]
private static void NewMenuOption4()
{
}

[MenuItem("NewMenu/Option5", false, 52)]
private static void NewMenuOption5()
{
}

这代码的结果根据分配的优先级有两组菜单项:
有用的Unity社区贴_第9张图片

相关类

以下是一些额外关于添加新菜单项的类的列表

当在一个inspector中添加一个新的菜单项(使用“CONTEXT/Component“,就像之前描述的),有时有必要得到引用给实际的组件。(比如修改它的数据)。

这能通过添加MenuCommand参数给静态方法来定义一个新菜单项:

[MenuItem("CONTEXT/RigidBody/New Option")]
private static void NewMenuOption(MenuCommand menuCommand)
{
    // The RigidBody component can be extracted from the menu command using the context field.
    var rigid = menuCommand.context as RigidBody;
}

如所见代码例子,当触发菜单项,这组件能通过context范围进行访问。

ContextMenu

这参数允许定义context菜单项。它的效果跟通过MenuItem参数“CONTEXT/…”定义菜单项一模一样。

使用这参数的区别是,你只是定义本组件的菜单,而使用MenuItem方法,你可以定义其他组件的菜单。

比如:

public class NameBehaviour : MonoBehaviour
{
    public string Name;

    [ContextMenu("Reset Name")]
    private static void ResetName()
    {
        Name = string.Empty;
    }
}

ContextMenuItem

这参数用于MonoBehaviour组件,允许添加context菜单。

这个参数添加在属性前面,而不是方法,它接收两个参数:菜单项名字和当菜单项选择后要调用的方法名。

例子:

public class NameBehaviour : MonoBehaviour
{
    [ContextMenuItem("Randomize Name", "Randomize")]
    public string Name;

    private void Randomize()
    {
        Name = "Some Random Name";
    }
}

这个代码结果就是当右键这个组件的属性时会产生菜单

你可能感兴趣的:(unity)