深入了解GameObject
GameObject的属性
Name下方的选项组,对应GameObject.tag,多用在控制碰撞和查找物体上。在创建标签时可以使用系统自带的标签,也可以在下拉菜单中点击New Tag来创造自定义标签。
点击Add Tag…后,Inspector会跳转到Tags&Layers界面,在该界面下创建自定义标签后,需要重新点选GameObject再将其tag设置为新创建的Tag。
在脚本中我们可以使用Tag来查找GameObject,使用Tag来查找比使用Name来查找消耗的资源更少一些:
GameObject GameObject.FindGameObjectWithTag(string tag);
GameObject[] GameObject.FindGameObjectsWithTag(string tag);
CompareTag方法可以用于判断物体的Tag:
bool GameObject.CompareTag(string tag);
这个函数比简单的使用字符串判断句this.tag == tag要高效的多。
CompareTag最常用于碰撞的判定,如下面的例子:
void OnCollisionEnter(collider other)
{
if(other.CompareTag("bullet"))
{
GameOver();
}
}
Tag右侧的选项组,对应脚本中的GameObject.layer。它的作用涉及到了摄像机的渲染和物理碰撞的检测。
同样的我们可以自定义Layer。Unity最多支持32层Layer,因为Unity经常需要使用Int32作为掩码来输入Layer信息。比如说我需要使用射线检测系统来跟踪鼠标指向的目标,但只需要检测Unit而不需要检测其它物体,我们就可以将Unit设置为单独的一个Layer,比如User Layer8 = Unit,然后使用下面的代码来实现:
readonly LayerMask mask = 1<<8;
void Update()
{
if(Input.GetMouseButtonDown(0))
{
RaycastHit hit;
if(Physics.Raycast(Camera.main.ScreenPointToRay(Input.mousePosition),out hit, mask.value));
{
Debug.Log("Hit on a Unit!");
DoSomething();
}
}
}
上面的代码中,Raycast等射线检测的知识将等到物理章节中解释。作为参数的mask作为一个掩码,从最低位到最高位分别表示Layers列表中的第0个元素到第31个元素,每一位如果是1则表示选中该层Layer,为0表示未选中,而Raycast函数中接收mask传入的掩码,只对第8层进行检测,从而实现目标效果。
层与层的碰撞可以在主界面菜单栏的"Edit"->“Project Settings”->“Physics"中修改,2D游戏则是"Edit”->“Project Settings”->“Physics 2D”。在这个窗口页面的最下方有一个碰撞检测表格,在这个表格中勾除某些格就可以使对应的两个层间不再发生碰撞,如图:
在上图的情况中,标签为New Layer的物体不再和其他标签为New Layer或Default的物体发生碰撞。
可以通过锁定某一层来防止不小心移动该层的物体。具体的做法是在Unity主界面的工具栏中点击Layers下拉菜单并改变对应层最右边的锁定选项。在这个菜单中的眼睛图标代表了一个层的可见性,当该选项被勾除则这一层不会被渲染。后面的锁是锁定性,被勾选时该层物体不能被选中:
在改变一个父物体的Layer属性时,会弹出如图所示的窗口,询问是否要将改变应用到所有子物体上。选择Yes则子物体的Layer也会一起改变,选择No则只改变父物体的Layer。
生成与销毁GameObject
GameObject是不能使用new来实例化的,必须使用Instantiate函数:
GameObject Instantiate(GameObject obj);
GameObject Instantiate(GameObject obj, Transform father);
GameObject Instantiate(GameObject obj, Vector3 position, Quaternion rotation);
GameObject Instantiate(GameObject obj, Vector3 position, Quaternion rotation, Transform father);
class InstantiateTest : MonoBehavior
{
[SerilizedField]
private GameObject bullet;
void Start()
{
GameObject newBullet = Instantiate(bullet, transform.position, transform.rotation);
newBullet.GetComponent<Bullet>().Shoot();
}
}
- 使用Destroy销毁GameObject
void Destroy(GameObject obj);
void Destroy(GameObject obj, float delay);
class ExplosionEffectManager : MonoBehavior
{
[SerilizedField]
private GameObjcet explosionEffect;
public void ShowEffect(Vector3 center)
{
GameObjcet newEffect = Instantiate(explosionEffect, center + Random.insideUnitSphere, transform.rotation);
Destroy(newEffect, 5f);
}
}
- 使用物品池来管理物体
每次调用Instantiate都会申请新的内存空间,而每次Destroy都会将这些内存遗弃等待GC处理。对于经常生成又经常被摧毁的物品,如子弹、特效等,快速而大量的Instantiate和Destroy会导致大量的GC消耗。这些物体最好使用一个物品池来管理,物品池可以是栈也可以是队列或其它的什么结构,其根本思路在于通过改变activeSelf而非直接使用Destroy来实现类似销毁的效果,以此减轻GC负担。
下面的例子是用一个栈封装的子弹池,其它脚本在创建或销毁子弹时不再使用Instantiate和Destroy,转而使用BulletPool.Born和BulletPool.Dies:
public class BulletPool
{
public static Stack<GameObject> pool = new Stack<GameObjcet>();
public static GameObject Born(GameObject bullet, Vector3 position)
{
if(pool.Count == 0)
{
GameObject obj = Instantiate(bullet, position, new Quaternion(0,0,0,0));
obj.Initialize();
return obj;
}
else
{
GameObject obj = pool.Pop();
obj.SetActive(true);
obj.Initialize();
return obj;
}
}
public static void Dies(GameObject bullet)
{
pool.Push(bullet);
bullet.SetActive(false);
}
}