在之前的三课中,我们逐行解释了一个新创建的游戏脚本中每一行代码的作用。此外,我们还从整体上介绍了Unity所提供的事件函数,以及众多的核心类。
但是只懂得理论是远远不够的,对于游戏开发来说,最重要的还是动手实操。
从这一课的内容开始,我们将学习如何在实际的游戏开发中使用C#编写游戏脚本。
还等什么呢?让我们开始吧~
04 Unity和C#的双剑合璧-访问游戏对象及其组件
之前我们已经创建了一个名为FairyLand的游戏项目,同时也导入了所需的游戏资源。
如果你还没有完成这一步操作,或者希望重新体验,那么可以再次动手搞定~
在FairyLand这个小的示例游戏中,我们将学习以下内容:
1.如何访问游戏对象及其组件
2.如何操控和修改游戏对象及其组件的属性,比如让游戏对象在场景中移动
3.如何实现基本的输入交互
4.如何实现摄像机对游戏角色的跟踪
5.如何实现不同游戏对象之间的交互
6.如何实现不同游戏对象之间的物理碰撞
7.如何实现触发事件
8.如何使用射线机制进行检测
9.如何生成和销毁游戏对象。
10.如何添加游戏UI交互界面
11.如何切换游戏场景
跟我们人生的第一款游戏不同,FairyLand不会采用第一人称视角,而将采用第三人称视角,也就是传说中的“上帝视角”~
在这一课的内容中,我们将首先完成第一项任务,也就是实现对游戏对象及其组件的访问。
为什么我们需要实现对游戏对象的访问呢?很简单,如果一款游戏中的所有角色玩家都无法操控,只能看着他们的精彩表演。那这就不是游戏了,而是电影,或者,更高级一点的互动电影,比如《隐形守护者》,《底特律变人》~
虽然也可以把这类产品归到游戏之中,但是比起玩家自己可以操控游戏角色随心所欲在游戏世界中纵横驰骋,显然其代入感和沉浸感还是要稍逊风骚~
好了,现在大家知道为什么我们需要访问游戏对象,那么接下来要做的就是如何访问。
Step1.添加角色的活动舞台
虽然我们在导入的游戏资源包中已经有设置完成的游戏场景,但是为了方便起见,这里先为玩家角色设置一个简单的活动平台~
在Unity编辑器中,从Project视图的Assets/Resources/Standard Assets/Prefabs目录中找到FloorPrototype08x01x08这个预设体,然后拖动到Hierarchy视图中,或是Scene视图中。
保持选中该预设体,在Inspector视图中的Transform组件右侧的设置小图标处点击,然后选择Reset Position。
最后,为了方便起见,可以更改它的名称为Floor。
Step2.添加游戏角色
接下来首先让我们添加玩家将要控制的游戏角色。
操作很简单,从Project视图的Assets/Resources/Characters/MeshSmith/Fantasy/Lady Fairy/Prefab目录中找到Lady Fairy这个预设体,也就是我们的主角,可爱的精灵公主~
把Lady Fairy这个预设体拖动到场景的合适位置。
Step3.为游戏角色添加碰撞体
为了方便后续的操作,我们需要为游戏角色添加一个碰撞体组件。
操作很简单,在Hierarchy视图中选中Lady Fairy,然后在Inspector视图中点击Add Component,选择Capsule Collider即可。
此时可以看到,在Inspector视图显示了三个组件,分别是Transform,Animator和Capsule Collider。
需要注意的是,默认情况下创建的空游戏对象只有Transform一个组件。
这个Transform代表的就是游戏对象在游戏世界中的坐标,旋转和比例大小。
而Animator组件代表的是角色的动画设置。
刚刚我们添加的Capsule Collider则是Collider碰撞体组件的一种特殊类型,capsule代表胶囊,通常用于人形角色。当我们给游戏对象添加了collider组件后,才有可能实现物体和物体,角色和角色,或者是角色和物体之间的物理碰撞。
好了,我们可以简单的把这三个组件看做Lady Fairy这个游戏对象的属性。
那么,我们应该如何在游戏脚本中访问这些属性呢?
Step4.创建新的游戏脚本
之前我们曾经了解过创建游戏脚本的几种方式,这里我们采取常用的一种。在Hierarchy视图中选中Lady Fairy,然后在Inspector视图中点击Add Component,选择New Script,为新的游戏脚本取名为PlayerScript,然后点击Create and Add。
这种方式创建的脚本已经自动跟特定的游戏对象(比如这里的Lady Fairy)关联在一起了。
不过这个游戏脚本默认出现在Project视图中Assets的根目录下,因此我们需要手动将其拖动到_Scripts目录下。
最后双击在Visual Studio中打开该脚本。
注意,有时候在Mac系统中可能会出现类似下面的提示:
这个没办法,只有点击Download Mono Framework,然后进行后续的操作。
当然,这里需要额外提醒下,下载的过程可能会比较缓慢,必要的时候可以FQ,而且选择全局mode,你懂的~
关于什么是Mono,之前的内容大概提过一点,简单来说,它是一个跨平台的C#编译器和通用运行时环境。至于具体的细节,考虑到大家还是newbie,就没必要过于深入了。
但是,感兴趣的童鞋也不必放过提升自己的机会,可以查看百度百科或维基百科中的详细介绍~
Step5.完成游戏脚本
欢迎再次回到代码时光~
既然我们希望访问游戏对象的属性,那么狠显然,我们需要定义一个或一堆变量来保存这些所获取的属性~
需要注意的是,通常情况下,当我们将某个游戏脚本中的变量声明前面加上public之后,就可以在Unity编辑器的Inspector视图中看到。如果是private或者不添加任何作用域相关的关键字,那么在Inspector视图中就看不到。
当然,在Unity中还有一个小小的技巧。
虽然某个变量不是public类型的,但是我们希望在Inspector视图中看到它的出现,应该怎么破呢?很简单,只需要在变量定义的上一行写上一个特殊的代码:
[SerializeField]
这里的单词serialize代表序列化。所谓的序列化就是当我们再次读取Unity时序列化过的变量默认是有值,而不需要我们再次赋值,因为它已经被保存了下来。
而Unity会自动为public类型的变量做序列化。
当然,哪怕一个变量是public类型的,但是如果我们不想在Inspector视图中看到它,那么也可以在相关变量定义的上一行写上一行特殊的代码:
[HideInInspector]
其实,在Unity中,使用方括号的代码远远不止这些。我们只需要知道,当我们看到这一类代码的时候,它们的作用是定制化Unity编辑器中的显示。
换句话说,只要你乐意,完全可以把Unity编辑器魔改成你自己喜欢的样子~
感兴趣的童鞋可以参考官方文档:
废话少说,接下来让我们创建一个变量来保存游戏对象的属性。
using Sy;
using Sy.Generic;
using UnityEngine;
public class PlayerScript : MonoBehaviour
{
//1.创建变量用来保存游戏角色的collider
public Collider playerCollider;
// Start is called before the first frame update
void Start()
{
//2.通过GetComponent方法来访问游戏对象的collider
playerCollider = GetComponent<CapsuleCollider>();
}
// Update is called once per frame
void Update()
{
}
}
这里我们只添加了两行代码,分别添加在Start()方法之前,以及Start()方法体内:
1.创建了一个Collider类型的public变量,名为playerCollider,可以用来保存游戏角色的collider组件属性。
此时返回Unity编辑器,可以看到在Inspector视图的Player Script(Script)组件中出现了Player Collider。
你可以试着去掉public,然后看看Inspector视图那里会有怎样的变化~
2.通过调用Unity的GetComponent方法来访问游戏对象的CapsuleCollider组件。
在上一课的内容中,其实我们已经提到过,Unity提供了很多系统类。
而这里我们使用的是GameObject类的GetComponent方法:
因为我们访问的是同一个游戏对象的组件,所以省掉了类对象的名称,实际上这行代码等同于:
playerCollider = gameObject.GetComponent<CapsuleCollider>();
此外,需要注意的是,在官方提供的示例中,我们可以用两种方式来传递参数。
一种是通用的方式,也就是使用圆括号,圆括号中是游戏对象的组件类型。
而另一种则使用了尖括号,也就是所谓的泛型。
这个名词看起来就有点恐怖~但是Don’t panic! 让我们来稍微解释一下。
在百度百科中对泛型的定义是:
泛型是程序设计语言的一种特性。允许程序员在强类型程序设计语言中编写代码时定义一些可变部分,那些部分在使用前必须作出指明。各种程序设计语言和其编译器、运行环境对泛型的支持均不一样。将类型参数化以达到代码复用提高软件开发工作效率的一种数据类型。泛型类是引用类型,是堆对象,主要是引入了类型参数这个概念。
如果你单独看这句话,会有一种懵逼的感觉。
泛型的表达方式通常是:
GenericFunctionName<ClassName>() 也就是:
泛型方法名<类名>()
但其实泛型没有你想象的那么恐怖,简单来说,我们这行代码等同于:
playerCollider = GetComponent(typeof(CapsuleCollider)) as CapsuleCollider;
在Unity3d中,用的最多的泛型方法就是GetComponent<T>,这里的T是个占位符,当我们需要调用的时候再使用具体的类名来替代。而这里的类名就是组件的具体类型,也就是CapsuleCollider。
除了GetComponent方法可以使用泛型,只要是Unity官方API文档中有说明的,都可以使用类似的方式,从而帮我们节省敲代码的时间~ 比如AddComponent<T>()也是如此
关于泛型的更多知识,可以参考这里:
好了,现在让我们返回Unity编辑器。
默认情况下,当选中Lady Fairy对象时,在Inspector视图的Player Script(Script)组件处,Player Collider的属性是None(Collider)。而当我们点击工具栏上的Play按钮时,会看到Player Collider的属性变成了Lady Fairy(Capsule Collider)。
OK,我们的第一个任务就搞定了~
在下一课的内容中,我们将完成第二个任务,也就是更改游戏对象及其组件的属性。
让我们下一课再见。