在游戏开发中,拼UI界面和写UI逻辑是少不了,甚至是绝大多数的事。
写UI逻辑时候,我们如果经常要引用到一个Image组件,名字例如为img1,首次接触游戏开发得你可能会这样写,声明一个变量,通过调用transform.Find("节点路径1/img1").GetComponent<Image>()来缓存组件引用。如果有其他组件ScrollRect scrollRect2,GameObject panel3也同上操作。
public class BaseView : MonoBehaviour
{
Image img1;
ScrollRect scrollRect2;
...
GameObject panel3;
private void Awake()
{
img1= transform.Find("节点路径1/img1").GetComponent<Image>();
scrollRect2 = transform.Find("节点路径2/scrollRect2").GetComponent<ScrollRect>();
...
panel3 = transform.Find("节点路径3/panel3").gameObject;
}
}
但是这样写的话有个弊端,如果后期优化的时候,拼UI的同学变动了节点层级路径,那么代码里的节点路径也得相应的变动,并且节点路径本身也极其容易写错。
于是,你可能会想到通过递归查找节点名字来获取节点。这样,只要节点名字不变,节点层级的变动在代码层是无感的。
private void Awake()
{
img1 = FindDeep(transform, "img1").GetComponent<Image>();
scrollRect2 = FindDeep(transform, "scrollRect2").GetComponent<ScrollRect>();
...
panel3 = FindDeep(transform, "panel3").gameObject;
}
Transform FindDeep(Transform t, string childName)
{
if (t.name.Equals(childName))
return t;
for (int i = 0; i < t.childCount; ++i)
{
var child = FindDeep(t.GetChild(i), childName);
if (child) return child;
}
return null;
}
到这里,其实已经能应付绝大多数情况了。
但是作者君懒啊,实现不想每引用一个组件都写一遍缓存组件的代码。在作者君看来程序员就是要来消灭重复的。于是在掉了几根头发以后,作者君想到利用反射获取需要缓存的引用和其组件类型,接着同样利用反射设置该引用的值。
public class BaseView : MonoBehaviour
{
public Image img1;
public ScrollRect scrollRect2;
...
public GameObject panel3;
private void Awake()
{
var fields = GetType().GetFields(BindingFlags.Instance | BindingFlags.Public); // 获取public变量
foreach (var field in fields)
{
var name = field.Name;
Transform obj = Utils.FindDeep(transform, name); // 递归查找节点
if (obj != null)
{
if (typeof(GameObject).Equals(field.FieldType))
{
field.SetValue(this, obj.gameObject);
}
else
{
field.SetValue(this, obj.GetComponent(field.FieldType)); // 获取组件类型,有时候你会觉得Unity很贴心
}
}
}
}
}
从而在继承了BaseView的界面脚本里,所有public组件成员引用都可以被脚本自动缓存。只需18行代码,永久省去所有缓存组件的代码,一次编写,终生享受!
本文介绍了一种游戏开发中UI组件自动引用的方法,通过反射和递归查找,避免了因UI层级变化导致的代码修改,实现了组件引用的动态绑定,极大地提高了开发效率。

3496

被折叠的 条评论
为什么被折叠?



