如何让你的脚本成为控制物体的一部分?
首先我们要理解,Unity中的一个物体的状态,是由它的各种组件(Component)控制的。例如一个Cube,我们看右边的视察面板:它的组件有:Transform、Mesh Filter&Mesh Renderer、Box Collider。
Transform,官方定义为“Position, rotation and scale of an object”,也就是一个物体的位置、大小和旋转。Unity中,所有的物体都有Transform属性(包括空物体),而且不可移除;
Mesh Filter和Mesh Renderer,用于渲染一个物体的外观。物体的颜色就是它的子属性之一;
Box Collider,碰撞体,用于判定两个物体的碰撞。选中物体,把Mesh Renderer勾掉,我们就可以看到一个由绿色线围成的轮廓,这就是这个立方体的碰撞体。通常和RigidBody刚体组件共同构成一个适用物理系统的物体;
这些都是Unity封装好的组件。类似的,我们也可以将脚本也作为一个组件,挂在一个物体上,这样就应该能像其他组件一样控制它。创建一个物体,命名为Player。然后创建一个C#脚本PlayerController,并绑在Player上。
在Start()中写:
Debug.Log(name);
Debug.Log(transform.position);
控制台输出了Player和它的位置,成功了。如果你有一定的编程基础,你可能已经看出脚本能控制Player这个物体的“真相”了。
为什么我们能直接访问“name”、“transform.position”这些变量?
这是因为实际上,代码进行了省略。name,其实是this.gameObject.name。transform.position,其实是this.gameObject.transform.position。你也可以省略gameObject,写成this.name。是不是和平常写的其他程序一样了?
this.gameObject,就是这个脚本所附着的物体的实例。实际上,它的原型是Component.gameObject,而this.gameObject中的this,代表的就是这个脚本自身。这与我们是将脚本作为一种组件添加给物体的思路是一致的。
而为什么将脚本直接挂在某个物体上是最直接的方式,就是因为能缺省地使用这个物体所有this.gameObject中的属性。
但这不是唯一的方法。事实上,只要能“找到”,我们就能控制任何一个物体。看下面这个例子。将PlayerController挂在随便一个其他的物体上,例如Main Camera。然后我们将脚本稍微改造一下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerController : MonoBehaviour
{
public GameObject player;
// Start is called before the first frame update
void Start()
{
Debug.Log(player.name);
}
}
然后我们看Main Camera的视察面板。在我们的脚本下面多了一栏Player,显示None(Game Object)。这就是我们的public GameObject player变量。我们把Player拖到框中,让变量引用到Player物体的实例。
运行游戏,控制台也输出了Player。虽然不能使用this,我们可以像这样创建一个GameObject变量来存储物体,并控制它们。
脚本很大的一个优点就是动态、灵活。像上面这样的引用,是一种很“死”的方式。那么我们如何在游戏运行过程中动态地找到想要的物体呢?Unity为我们提供了一系列的函数。一个简单的例子:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerController : MonoBehaviour
{
private GameObject _player;
private void Start()
{
_player = GameObject.Find("Player");
if (_player != null)
{
Debug.Log("Found");
Debug.Log(_player.name);
}
}
}
Tips:注意变量命名规范。博主的习惯是private变量使用下划线开头。当然,命名规范属于一个没有定论的东西,但根本原则是:在一个项目中前后保持一致。
如果你是初学者,可以参考一些知名团队的规范:
微软官方文档:https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/naming-guidelines Google Style Guide:https://github.com/google/styleguide/blob/gh-pages/csharp-style.md
控制台打出了“Player”。在这个程序中,我们是在游戏启动后,执行Start()函数时通过GameObject.Find(String name)函数根据名称找到了一个物体。相似的,还有其他函数可以通过Tag、Type等特征找到特定的物体。需要注意的是,当你的场景中物体很多时,这种查找可能会相当耗费资源。 应该使用最合理的查找方式,尽可能避免全局大范围频繁的查找。
总结起来,我们要明白,一个脚本所能控制的物体,最基本的是它依附的物体,但并不局限于此,我们可以自由控制。这也带来一个问题:同一种效果,我们把脚本挂在哪里有多种选择。这时,我们还是应该选择逻辑上最合理的方法。例如PlayerController脚本,就应该挂在Player上而不是挂在Main Camera上再通过一个public变量获取Player实例。
还有一种很常见的情形。比如说我们要创建一个管理整个游戏的脚本GameManager,那么把它挂在任何一个具体的物体上都不太合适。这时我们一般会创建一个空物体,专门用于挂载这个脚本。
明白了脚本控制物体的方法,在下一节,我们尝试实现用键盘操控一个物体进行上下左右的简单移动。