本节我们主要学习GameObject类,该类用于表示任何存在于场景中的游戏对象。这个类继承自Unity Object类(不是C#语言的Object类),我们可以理解这个Unity Object类是所有Unity的基类。这个Unity Object基类比较简单,我们很少使用这个基类的内容。我们还是重点介绍一些GameObject 类吧。GameObject 类有几个比较重要的类实例变量经常使用
1. activeSelf 获取自身(激活状态)。
2. activeInHierarchy 获取自身(激活状态,与父游戏对象有关)。
3. name 游戏对象的名称(其实是来自基类Object中)
4. scene 游戏对象所属的场景Scene类。
5. layer 游戏对象所属层(层的概念我们后面章节会详细介绍)。
6. tag 游戏对象的标签(标签的概念我们后面章节会详细介绍)。
7. transform 游戏对象的世界变换,所有游戏对象都拥有该组件。
以上的变量都可以通过GameObject的实例对象进行调用,不能直接通过类名调用。它代表了游戏对象的基本属性,因此有时候也称之为属性变量。
接下来我们继续介绍GameObject类的一些常用方法,如下所示:
1. setActive方法,参数为true或false,也就是激活或停用当前游戏对象,这个相当于我们在Inspector检视视图中游戏对象名称左边的那个勾选框。
2. AddComponent 给游戏对象添加指定类型的组件,可以是Unity内部标准组件,也可以是我们自定义的C#脚本。组件必须附加到游戏对象上面才能使用。
3. GetComponent返回指定类型的一个组件,如果没有就返回null。
4. GetComponents返回指定类型的所有组件,要么返回组件数组,要么返回null。
5. GetComponentInChildren从子游戏对象上面获取组件,游戏对象可以形成父子关系。
6. GetComponentInParent从父游戏对象上面获取组件,游戏对象可以形成父子关系。
7. CompareTag当前游戏对象是否使用了指定标签,标签就是一个字符串而已。
我们发现GameObject类的大部分方法都是用来查询并获取组件的。以上的方法都是普通方法,需要获取GameObject类实例后才能调用。接下来我们介绍GameObject类的静态方法。关于类的实例成员(变量和方法)和静态成员(变量和方法)的区别,大家一定要注意区分了。静态成员是直接通过类名进行调用的,而实例成员则是类的实例化对象调用的。
1. Find按照名称查询游戏对象,没有查到返回null。
2. FindWithTag按照标签(字符串)查询游戏对象,没有查到返回null。
3. FindGameObjectsWithTag 按照标签(字符串)查询所有游戏对象,返回数组或者null。
由此可见GameObject类的静态方法大部分是用来查询并获取游戏对象的,这样的设计也可以理解。毕竟如果需要实例化一个游戏对象后才能查询其他游戏对象,这样的操作就太繁琐且不人性化了,就不如使用静态方法直接查询并获取游戏对象。最终我需要记住的就是,GameObject类的静态方法是用来获取其他游戏对象的,而普通方法是用来获取游戏对象自身组件的,而实例变量则是获取游戏对象自身的一些基本属性(名称,标签,层等等)。
接下来,我们继续介绍一下MonoBehaviour类,它是一个基类,所有 Unity 组件脚本都继承该类(当然我们也可以定义普通的C#类在组件脚本中使用)。我们上面章节中讲解了它的一些内部方法(Start,Update等等),这些内部方法是Unity自动调用的,他们跟游戏的生命周期密切相关。接下来,我们说一说MonoBehaviour类的其他内容,首先是它的类实例变量
1. enabled当前脚本是否可用。
2. isActiveAndEnabled当前脚本是否激活可用,与附加游戏对象状态相关。
3. gameObject 当前脚本附加的当前游戏对象
4. name当前游戏对象名称
5. tag当前游戏对象的标签
6. transform当前游戏对象的transform组件
我们发现,它的大部分属性就是GameObject类中的属性。这样做的目的,就是方便我们在脚本中直接获取自身游戏对象的常用属性。避免我们先获取自身游戏对象实例,然后再通过该实例获取游戏对象属性。接下来就是MonoBehaviour类的一些常用方法,如下所示:
1. CompareTag当前游戏对象是否使用了指定标签
2. GetComponent按照类型获取一个组件
3. GetComponentInChildren从子游戏对象中获取一个组件
4. GetComponentInParent从父游戏对象中获取一个组件
5. GetComponents 按照类型获取所有组件
6. GetComponentsInChildren 从子游戏对象中获取所有组件
7. GetComponentsInParent从父游戏对象中获取所有组件
这个就比较明显了,在MonoBehaviour类中方法就是所属游戏对象中的方法,这样的设计可能是让我们更加方便的在脚本中对当前的游戏对象进行操作。当然,在MonoBehaviour类中还有一些事件类方法,他们的使用是有条件的。例如我们之前讲过的碰撞体(Collider)组件使用,还有就是一些用户响应事件(鼠标点击,键盘按下等等),这些内容我们会在后面的章节中详细介绍。
接下来在“ScriptDemo”工程中创建一个新的场景,点击菜单栏“File”->“New Scene”
我们选择“Basic(Built-in)”场景,然后点击右下角的“Create”按钮,就会创建一个Unity默认模板的场景,里面包含一个摄像机,平行光和一个天空盒。创建完毕后,我们继续点击菜单栏“File”->“Save”来保存当前新创建的场景,取名为“SampleScene2”,如下所示
我们选中“Scenes”目录,将我们的场景文件保存到“Scenes”目录下
保存成功后,我们就可以在Project工程面板中同步看到场景文件了。
每一个场景都会以个文件(.unity)形式保存。现在的大部分RPG游戏基本上都是多场景的,即便是同一个世界地图,也都是按照不同的区域分成各个小地图,那么这些小地图都可以理解为一个个的场景。这样做的好处在于可以将每一个地图场景独立开发,而不影响其他地图场景。接下来,我们在新场景中创建两个Cube游戏对象,分别取名为cube1和cube2。
接下来,我创建一个脚本文件“CubeTest1.cs”,并将该脚本附加到Cube1上面。为了更好的组织和管理脚本,我们在“Assets”目录下创建一个“Scripts”目录,并将我们刚刚创建的“CubeTest1.cs”脚本保存到这个目录下。创建目录非常简单,只需要在Project面板空白处右击选择“Create”->“Folder”,然后将目录命名为“Scripts”即可。
创建脚本完毕后,即可将其拖拽到Cube1上面,也就是它的Inspector检视面板中
然后双击脚本,打开VS补全我们的脚本代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CubeTest1 : MonoBehaviour
{
// Mesh Renderder组件
private MeshRenderer meshRenderer;
// Start is called before the first frame update
void Start()
{
// 当前游戏对象名称
Debug.Log(name);
Debug.Log(gameObject.name);
// 获取Mesh Renderder组件
meshRenderer = GetComponent<MeshRenderer>();
}
// Update is called once per frame
void Update()
{
if (Input.GetKeyDown(KeyCode.A))
{
meshRenderer.enabled = false;
Debug.Log("Cube1 消失");
}
}
}
我们逐一来解释这些代码。首先,我们定义了一个类实例变量meshRenderer,它的类型是MeshRenderer,也就是渲染组件,在上面的截图中,我们能够看到这个组件,这个组件的用途就是将游戏对象cube1渲染出来。我们在Start方法中通过GetComponent方法来获取,该方法是从当前游戏对象(cube1)上面获取组件实例,而不是从当前场景中获取该组件(组件是挂载到游戏对象上面的)。然后在Start方法中,我们还访问了当前脚本MonoBehaviour的name属性以及gameObject游戏对象的name属性,其实这两个就是一回事(都是打印游戏对象的名称)。然后紧接着在Update方法中,我们检测字母A按键是否按下,如果被按下的话,我们就禁用MeshRenderer组件(渲染组件失效),那么游戏对象cube1将会从场景中消失。我们Play当前工程,然后仅给出控制台日志截图。
备注,如果游戏对象的组件在检视面板中被禁用的话,无法通过GetCompont找到。
接下来,我们继续创建“CubeTest2.cs”脚本,内容如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CubeTest2 : MonoBehaviour
{
// 定义公开的字符类实例变量
public string hello = "hello,unity! ";
}
在这个脚本中,我们只定义了一个普通的类变量。注意,如果Start和Update方法没有使用的话,最好将其删除掉,不要留着空方法在当前脚本类中。接下来,我们将这个“CubeTest2.cs”脚本也附加到游戏对象cube1(不是cube2)上面,同时调整“CubeTest1.cs”脚本内容。
接下来,我们还需要修改“CubeTest1.cs”的脚本代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CubeTest1 : MonoBehaviour
{
// CubeTest2脚本组件
private CubeTest2 cubeTest2;
// Start is called before the first frame update
void Start()
{
// 获取CubeTest2脚本组件
cubeTest2 = GetComponent<CubeTest2>();
Debug.Log(cubeTest2.hello);
}
}
CubeTest1脚本内容我们只展示修改的部分,其他部分不再展示了(其他代码没有改动)。
其实我们就是想从一个脚本中访问另一个脚本,脚本也是组件。因此,我们直接使用GetComponent方法来获取组件脚本,尖括号里面对应的就是想要获取脚本的类名称。然后我们打印CubeTest2脚本中的hello变量的内容。我们重新Play当前工程,查看日志输出。
接下来,我们继续调整“CubeTest1.cs”脚本内容,来获游戏对象cube2来试试
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CubeTest1 : MonoBehaviour
{
// Cube2游戏对象
private GameObject cube2;
// Start is called before the first frame update
void Start()
{
// 获取Cube2游戏对象
cube2 = GameObject.Find("Cube2");
Debug.Log(cube2.name);
}
// Update is called once per frame
void Update()
{
if (Input.GetKeyDown(KeyCode.B))
{
cube2.active = false;
Debug.Log("Cube2 消失");
}
}
}
上面的代码非常简单,我们定义了一个GameObject游戏对象来代表立方体Cube2,然后在Start方法中通过GameObject类的静态方法Find通过游戏对象名称来获取Cube2,同时输出它的名称。然后在update方法中,我们检测字母B按钮是否按下,如果按下就让游戏对象Cube2失效,它也就是从场景中消失了。这里要注意,我们禁用游戏对象或者它的MeshRenderer组件,都会使得游戏对象不可见哦。我们Play工程查看日志输出吧。
最后说一下创建和销毁游戏对象GameObject。
在MonoBehaviour中有两个静态方法:Instantiate和Destroy,前者用户创建游戏对象,后者用于销毁游戏对象。Instantiate方法的参数列表有5个,一般情况下我们使用如下:
Instantiate (Object original, Vector3 position, Quaternion rotation);
第一参数通常是一个预制体(prefab),第二个参数是位置(向量),第三个是旋转朝向(四元数)。我们会在后面详细介绍这个方法的使用。而Destroy方法的使用比较简单,如下:
Destroy (Object obj, float t= 0.0F)
第一个参数就是需要销毁的游戏对象实例,也可以是组件,或者其他资源。
第二个参数代表延迟时间,单位是秒,也就是多少秒后才销毁该对象实例。
Destroy方法用于销毁游戏对象,该方法第二个参数可以设置延迟多次时间后再销毁。这个参数非常有用。比如我们创建子弹后,就可以调用这个方法来并设置子弹飞行多长时间后再销毁。这个方法我们也会在后面的章节中详细介绍如何使用。